架构介绍
Engine:引擎,处理整个系统的数据流处理,触发事物,是整个框架的核心
Item:项目,他定义了爬取结果的数据结构,爬取的数据会被赋值成该Item对象
Scheduler:调度器,接受引擎发过来的请求并将其加入到队列中,在引擎再次请求的时候将请求提供给引擎
Downloader:下载器,下载网页内容,并将网页内容返回给spider
Spider:蜘蛛,其内部定义了爬取的逻辑和网页的解析规则,他负责解析响应并生成提取结果和新的请求
Item Pipline:项目管道,负责处理由蜘蛛从网页中抽取的项目,他的主要任务是清洗,验证和存储数据
Downloader Middlewares:下载器中间件,位于引擎和下载器之间钩子框架,主要处理引擎和下载器之间的请求和响应
Spider Middlewares:蜘蛛中间件,位于引擎和蜘蛛之间的钩子框架,主要处理蜘蛛输入的响应和输出的结果及新的请求
数据流
Scrapy中的数据流由引擎控制,数据流的过程如下:
1.Engine首先打开一个网站,找到处理该网站的Spider,并向该Spider请求第一个要爬取的URL.
2.Engine从Spider中获取到第一个要爬取的URL,并通过Scheduler以Request的形式调度.
3.Engine向Scheduler请求下一个要爬取的URL
4.Scheduler返回下一个要爬取的URL给Engine,Engine将URL通过Downloader Middlewares转发给Downloader下载
5.一旦页面下载完毕,Downloader生成该页面的Response,并将其通过Downloader Middlewares发送给Engine
6.Engine从下载器中接收到Response,并将其通过Spider Middlewares发送给 Spider处理
7.Spider处理Response,并返回爬取到的Item及新的Request给Engine
8.Engine将Spider返回的Item给Item Pipline,将新的Request给Scheduler
9.重复2-8,知道Scheduler中没有更多的Request,Engine关闭该网站,爬取结束
通过多个组件的相互协作,不同组件完成工作的不同,组件对异步处理的支持,Scrapy最大限度的利用了网络带宽,大大提高了数据爬取和处理的效率
雀氏纸尿裤
创建项目:
scrapy startproject 项目名(可以在几乎任意文件夹下创建,创建的项目就在该文件夹下)在命令行中执行
2:创建Spider:
Spider是自己定义的类,Scrapy用它从网页中抓取内容,并解析抓取的结果.不过这个类必须继承Scrapy提供的Spider类scrapy.Spider,还要定义Spider的名称和起始请求,以及怎样处理爬取结果的方法:(不如命令行方便)
也可以使用命令行创建一个Spider,比如要生成sb这个spider,(示例爬取校花网图片)
执行如下命令:
cd 项目名
scrapy genspider sb news.daxues.cn
进入刚才创建的项目文件夹,然后执行上诉命令,第一个参数是Spider的名称,第二个参数是网站域名,执行完毕之后,spiders文件夹中就多了一个sb.py,他就是刚刚创建的Spider,内容如下:
import scrapy
class SbSpider(scrapy.Spider): name = 'sb' allowed_domains = ['news.daxues.cn'] start_urls = ['http://news.daxues.cn/xiaohua/ziliao/'](这里有所改动xiaohua/ziliao/为后来添加的方便爬取) def parse(self, response): pass
这里有三个属性--name,allowed_domains和start_urls,还有一个parse方法
name:他是项目中唯一的名字,用于区分不同Spider
allowed_domains,他是允许爬取的域名,如果初始或后续的请求链接不是这个域名下的,则请求链接会被过滤掉
start_urls:它包含了Spider在启动时爬取的url列表,初始请求是由他定义的
parse:他是Spider的一个方法,默认情况下,被调用时start_url里面的链接构成的请求完成下载执行后,返回的响应就会作为唯一的参数传递给这个函数.该方法负责解析返回的响应,提取数据或者进一步生成要处理的请求.
3.创建Item
Item是保存爬取数据的容器,他的使用方法和字典类似,创建Item需要继承scrapy.Item类,并且定义类型为scrapy.Field的字段
定义Item,此时将items.py修改如下:
class NewsItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() name = scrapy.Field() info = scrapy.Field() img = scrapy.Field()
这里定义了三个字段,接下来的爬取将会使用这个Item
4.解析Response
parse()方法的参数response是start_urls里面的链接爬取后的结果,所以在parse()方法中.我们可以直接对response变量包含的内容进行解析,比如浏览请求结果的源代码,或者进一步分析源代码内容,或找出结果中的链接,得到下一个请求
有时网页中既有我们想要的结果又有下一页的链接,都需要进行处理.:
可以在浏览器上查看网页结构:
在类名为xh_list的div下有多个dl标签,里面包含我们想要的数据
提取内容的方式可以是CSS选择器或Xpath选择器,这里使用CSS选择器进行选择
parse()方法改写如下:
class SbSpider(scrapy.Spider): name = 'sb' allowed_domains = ['news.daxues.cn'] start_urls = ['http://news.daxues.cn/xiaohua/ziliao/'] def parse(self, response): div_list = response.css('.xh_list').css('dl') url = 'https://news.daxues.cn' for div in div_list: name = div.css('dt').css('a::text').extract_first() info = div.css('.d::text').extract_first() img = url+div.css('img::src').extract_first()
5.使用Item
上文定义Item,接下来就要使用它.Item可以理解为一个字典,不过在声明的时候需要实例化,然后依次用刚才解析的结果赋值Item的每一个字段,最后将Item返回即可
SbSpider的改写如下所示:
import scrapyfrom news.items import NewsItemclass SbSpider(scrapy.Spider): name = 'sb' allowed_domains = ['news.daxues.cn'] start_urls = ['http://news.daxues.cn/xiaohua/ziliao/'] def parse(self, response): div_list = response.css('.xh_list').css('dl') url = 'https://news.daxues.cn' for div in div_list: item = NewsItem() item['name'] = div.css('dt').css('a::text').extract_first() item['info'] = div.css('.d::text').extract_first() item['img'] = url+div.css('img::attr(src)').extract_first() yield item
如此一来,首页的所有内容被解析出来,并被赋值成了一个个NewsItem
6.后续Request
上面的操作只实现了从初始页面抓取内容,那么下一页的内容该如何抓取,这需要我们从当前页面中找到信息来生成下一个请求,然后在下一个请求的页面里找到信息再构造再下一个请求,这样循环往复迭代,从而实现整站的爬取.
将页面拉到底部,这里有一个下一页按钮,查看他的源代码发现,他的链接是/xiaohua/ziliao/index_2.html,全链接就是:
http://news.daxues.cn/xiaohua/ziliao/index_2.html
通过这个链接我们就可以构造下一个请求.
构造请求时需要用到scrapy.Request.这里我们传入两个参数---url和callback,这两个参数的说明如下.
url:他是请求链接
callback:它是回调函数,当指定了该回调函数的请求完成之后,获取到响应,引擎会将该响应作为参数传递给这个回调函数.回调函数进行解析或生成下一个请求,回调函数如上文的parse()所示:
由于parse()就是解析name,info,img的方法,而下一页的结构和刚才已经解析的页面结构是一样的,所以我们可以再次使用parse(0方法来做页面解析.
接下来就是利用选择器得到下一页链接并生成请求,在parse()方法后追加如下的代码:
next = response.css('.page a::attr(href)').extract_first()(选择器语法可能不正确,后续需要改动)url2 = url+nextyield scrapy.Request(url=url2,callback=self.parse)
第一句代码首先通过CSS选择器获取下一个链接,,即要获取a超链接中的href属性.这里用到了::attr(href)操作.然后再调用extract_first()方法获取内容
第二句代码生成一个url路径
第三句代码通过url和callback变量构造了一个新的请求,回调函数callback依然使用parse()方法,这个请求完成后,响应会重新经过parse方法处理,得到第二页的解析结果,然后生成第二页的下一页,也就是第三页的请求,这样爬虫就进入了一个循环,直到最后一页.
改写后的整个Spider类如下所示:
import scrapyfrom news.items import NewsItemclass SbSpider(scrapy.Spider): name = 'sb' allowed_domains = ['news.daxues.cn'] start_urls = ['http://news.daxues.cn/xiaohua/ziliao/'] def parse(self, response): div_list = response.css('.xh_list').css('dl') url = 'https://news.daxues.cn' for div in div_list: item = NewsItem() item['name'] = div.css('dt').css('a::text').extract_first() item['info'] = div.css('.d::text').extract_first() item['img'] = url+div.css('img::attr(src)').extract_first() yield item next = response.css('.page a::attr(href)').extract_first() url2 = url+next yield scrapy.Request(url=url2,callback=self.parse)
7运行:
接下来进入目录,运行如下命令:
scrapy crawl news
就可以看到运行结果了
首先Scrapy输出了当前的版本号以及正在启动的项目名称,接着输出了当前settings.py中一些重写后的配置,然后输出了当前所应用的Middlewares和Piplines.Middlewares默认是启用的,可以在settings中修改,Piplines默认为空,同样可以在settings.py中配置.
接下来就是整个页面的抓取结果,可以看到爬虫一边解析,一边翻页,直至将所有内容抓取完毕,然后终止.
最后Scrapy输出了整个抓取过程的统计信息,如请求的字节数,请求次数,响应次数,完成原因等等
整个Scrapy程序成功运行.
由于本人对于Scrapy的CSS选择器不熟悉,因此除了点bug,但问题不大
8保存到文件
Scrapy提供的Feed Exports可以轻松的抓取结果输出
如果想要将输出结果保存成json文件,可以执行以下命令:
scrapy crawl sb -o sb.jl
或
scrapy crawl sb -o sb.jsonlines
输出格式还支持很多种,例如csv,xml,pickle,marshal等,还支持ftp,s3等远程输出,另外还可以通过自定义ItemExporter来实现其他的输出.
例如下面的命令对应的输出分别为csv,xml,,pickle,marshal格式以及ftp远程输出:
scrapy crawl sb -o sb.csv
scrapy crawl sb -o sb.xml
scrapy crawl sb -o sb.pickle
scrapy crawl sb -o sb.marshal
scrapy crawl sb -o ftp://user:pass@ftp.example.com/path/to/sb.csv
其中,ftp输出需要正确配置用户名,密码,地址,输出路径,否则会报错
通过Scrapy提供的Feed Exports我们可以轻松的输出抓取结果到文件,如果想要更复杂的输出,我们可以使用Item Pipline来完成
9使用Item Pipeline
如果想进行更复杂的操作,如将结果保存到MongoDB数据库,或者筛选某些有用的Item,我们可以定义Item Pipeline来实现
Item Pipline为项目管道,当Item生成后,他会自动被送到Item Pipline进行处理,我们常用Item Pipline来做如下操作:
清理HTML数据
验证爬取数据,检查爬取字段
查重并丢弃重复内容
将爬取结果保存到数据库.
要实现Item Pipeline很简单只需要定义一个类并实现process_item()方法即可,启用Item Pipeline后,Item Pipeline会自动调用这个方法,process_item()方法必须返回包含数据的字典或Item对象,或者抛出DropItem异常.
process_item()方法有两个参数,一个参数是item,每次Spider生成的Item都会作为参数传递过来,另一个参数是spider,就是Spider的实例.
接下来实现一个Item Pipeline,筛掉info长度大于200的Item,并将结果保存到MongoDB.
修改项目里的pipelines.py文件,之前用命令行自动生成的文件内容可以删掉,增加一个TextPipeline类,内容如下所示:
from scrapy.exceptions import DropItemclass InfoPipeline(): def __init__(self): self.limit = 200 def process_item(self,item,spider): if item['info']: if len(item['info']) > self.limit: item['info'] = item['info'][0:self.limit].rstrip()+'...' return item else: return DropItem('Missing Info')
这段代码在构造方法里定义了限制长度为200,实现了process_item()方法,其参数是item和spider,首先该方法判断item的info属性是否存在,不存在则抛出DropItem异常,存在则判断是否大于200,大于则删减,然后返回item
接下来将处理后的item存入MongoDB,定义另外一个Pipeline,同样在pipelines.py中,我们实现零一个类MongoPipeline,内容如下所示:
import pymongoclass MongoPipeline(): def __init__(self,mongo_uri,mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db @classmethod def from_crawler(cls,crawler): return cls(mongo_uri=crawler.settings.get('MONGO_URI'), mongo_db=crawler.settings.get('MONGO_DB')) def open_spider(self,spider): self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] def process_item(self,item,spider): name = item.__class__.__name__ self.db[name].insert(dict(item)) return item def close_spider(self,spider): self.client.close()
MongoPipeline类实现了API定义的另外几个方法.
from_crawler:他是一个类方法,用@classmethod标识,是一种依赖注入的方式,他的参数就是crawler,通过crawler我们可以拿到全局配置的每个配置信息,在全局配置settings.py中,我们定义MONGO_URI和MONGO_DB来指定MongoDB连接需要的地址和数据库名称,拿到配置信息后返回类对象即可,所以这个方法主要用于获取settings.py中的配置的
open_spider:当Spider开启时,这个方法被调用,上文程序主要进行了一些初始化操作
close_spider:当Spider关闭时,这个方法被调用,上文程序中将数据库连接关闭
最主要的process_item()方法则执行了数据插入操作
定义好InfoPipeline和MongoPipeline这两个类后,我们需要在settings.py中使用他们,MongoDB的连接信息还需要定义.
我们在settings.py中加入如下内容:
ITEM_PIPELINES={'sb.pipelines.InfoPipeline':300,
'sb.pipelines.MongoPipeline':400,}
MONGO_URI = 'localhost'
MONGO_DB = 'sb'
赋值ITEM_PIPELINES字典,键名是Pipeline的类名称,键值是调用优先级,是一个数字,数字越小则对应的Pipeline越先被调用.
再重新爬取,命令如下所示:
scrapy crawl sb
爬取结束后,MongoDB中创建了一个news的数据库,sbitem的表,