Scrapy-redis实现分布式爬取的过程与原理

Scrapy是一个比较好用的Python爬虫框架,你只须要编写几个组件就能够实现网页数据的爬取。可是当咱们要爬取的页面很是多的时候,单个主机的处理能力就不能知足咱们的需求了(不管是处理速度仍是网络请求的并发数),这时候分布式爬虫的优点就显现出来。html

而Scrapy-Redis则是一个基于Redis的Scrapy分布式组件。它利用Redis对用于爬取的请求(Requests)进行存储和调度(Schedule),并对爬取产生的项目(items)存储以供后续处理使用。scrapy-redi重写了scrapy一些比较关键的代码,将scrapy变成一个能够在多个主机上同时运行的分布式爬虫。python

原生的Scrapy的架构是这样子的:git

加上了Scrapy-Redis以后的架构变成了:github

scrapy-redis的官方文档写的比较简洁,没有说起其运行原理,因此若是想全面的理解分布式爬虫的运行原理,仍是得看scrapy-redis的源代码才行,不过scrapy-redis的源代码不多,也比较好懂,很快就能看完。redis

scrapy-redis工程的主体仍是是redis和scrapy两个库,工程自己实现的东西不是不少,这个工程就像胶水同样,把这两个插件粘结了起来。数据库

scrapy-redis提供了哪些组件?

scrapy-redis所实现的两种分布式:爬虫分布式以及item处理分布式。分别是由模块scheduler和模块pipelines实现。json

connection.py服务器

负责根据setting中配置实例化redis链接。被dupefilter和scheduler调用,总之涉及到redis存取的都要使用到这个模块。网络

connect文件引入了redis模块,这个是redis-python库的接口,用于经过python访问redis数据库,可见,这个文件主要是实现链接redis数据库的功能(返回的是redis库的Redis对象或者StrictRedis对象,这俩都是能够直接用来进行数据操做的对象)。这些链接接口在其余文件中常常被用到。其中,咱们能够看到,要想链接到redis数据库,和其余数据库差很少,须要一个ip地址、端口号、用户名密码(可选)和一个整形的数据库编号,同时咱们还能够在scrapy工程的setting文件中配置套接字的超时时间、等待时间等。数据结构

dupefilter.py

负责执行requst的去重,实现的颇有技巧性,使用redis的set数据结构。可是注意scheduler并不使用其中用于在这个模块中实现的dupefilter键作request的调度,而是使用queue.py模块中实现的queue。当request不重复时,将其存入到queue中,调度时将其弹出。

这个文件看起来比较复杂,重写了scrapy自己已经实现的request判重功能。由于自己scrapy单机跑的话,只须要读取内存中的request队列或者持久化的request队列(scrapy默认的持久化彷佛是json格式的文件,不是数据库)就能判断此次要发出的request url是否已经请求过或者正在调度(本地读就好了)。而分布式跑的话,就须要各个主机上的scheduler都链接同一个数据库的同一个request池来判断此次的请求是不是重复的了。

在这个文件中,经过继承BaseDupeFilter重写他的方法,实现了基于redis的判重。根据源代码来看,scrapy-redis使用了scrapy自己的一个fingerprint接request_fingerprint,这个接口颇有趣,根据scrapy文档所说,他经过hash来判断两个url是否相同(相同的url会生成相同的hash结果),可是当两个url的地址相同,get型参数相同可是顺序不一样时,也会生成相同的hash结果(这个真的比较神奇。。。)因此scrapy-redis依旧使用url的fingerprint来判断request请求是否已经出现过。这个类经过链接redis,使用一个key来向redis的一个set中插入fingerprint(这个key对于同一种spider是相同的,redis是一个key-value的数据库,若是key是相同的,访问到的值就是相同的,这里使用spider名字+DupeFilter的key就是为了在不一样主机上的不一样爬虫实例,只要属于同一种spider,就会访问到同一个set,而这个set就是他们的url判重池),若是返回值为0,说明该set中该fingerprint已经存在(由于集合是没有重复值的),则返回False,若是返回值为1,说明添加了一个fingerprint到set中,则说明这个request没有重复,因而返回True,还顺便把新fingerprint加入到数据库中了。 DupeFilter判重会在scheduler类中用到,每个request在进入调度以前都要进行判重,若是重复就不须要参加调度,直接舍弃就行了,否则就是白白浪费资源。

queue.py

其做用如dupefilter.py所述,可是这里实现了三种方式的queue:FIFO的SpiderQueue,SpiderPriorityQueue,以及LIFI的SpiderStack。默认使用的是第二种,这也就是出现以前文章中所分析状况的缘由(连接)。

该文件实现了几个容器类,能够看这些容器和redis交互频繁,同时使用了咱们上边picklecompat中定义的serializer。这个文件实现的几个容器大致相同,只不过一个是队列,一个是栈,一个是优先级队列,这三个容器到时候会被scheduler对象实例化,来实现request的调度。好比咱们使用SpiderQueue最为调度队列的类型,到时候request的调度方法就是先进先出,而实用SpiderStack就是先进后出了。

咱们能够仔细看看SpiderQueue的实现,他的push函数就和其余容器的同样,只不过push进去的request请求先被scrapy的接口request_to_dict变成了一个dict对象(由于request对象实在是比较复杂,有方法有属性很差串行化),以后使用picklecompat中的serializer串行化为字符串,而后使用一个特定的key存入redis中(该key在同一种spider中是相同的)。而调用pop时,其实就是从redis用那个特定的key去读其值(一个list),从list中读取最先进去的那个,因而就先进先出了。

这些容器类都会做为scheduler调度request的容器,scheduler在每一个主机上都会实例化一个,而且和spider一一对应,因此分布式运行时会有一个spider的多个实例和一个scheduler的多个实例存在于不一样的主机上,可是,由于scheduler都是用相同的容器,而这些容器都链接同一个redis服务器,又都使用spider名加queue来做为key读写数据,因此不一样主机上的不一样爬虫实例公用一个request调度池,实现了分布式爬虫之间的统一调度。

picklecompat.py

这里实现了loads和dumps两个函数,其实就是实现了一个serializer,由于redis数据库不能存储复杂对象(value部分只能是字符串,字符串列表,字符串集合和hash,key部分只能是字符串),因此咱们存啥都要先串行化成文本才行。这里使用的就是python的pickle模块,一个兼容py2和py3的串行化工具。这个serializer主要用于一会的scheduler存reuqest对象,至于为何不实用json格式,我也不是很懂,item pipeline的串行化默认用的就是json。

pipelines.py

这是是用来实现分布式处理的做用。它将Item存储在redis中以实现分布式处理。另外能够发现,一样是编写pipelines,在这里的编码实现不一样于文章中所分析的状况,因为在这里须要读取配置,因此就用到了from_crawler()函数。

pipeline文件实现了一个item pipieline类,和scrapy的item pipeline是同一个对象,经过从settings中拿到咱们配置的REDIS_ITEMS_KEY做为key,把item串行化以后存入redis数据库对应的value中(这个value能够看出出是个list,咱们的每一个item是这个list中的一个结点),这个pipeline把提取出的item存起来,主要是为了方便咱们延后处理数据。

scheduler.py

此扩展是对scrapy中自带的scheduler的替代(在settings的SCHEDULER变量中指出),正是利用此扩展实现crawler的分布式调度。其利用的数据结构来自于queue中实现的数据结构。

scrapy-redis所实现的两种分布式:爬虫分布式以及item处理分布式就是由模块scheduler和模块pipelines实现。上述其它模块做为为两者辅助的功能模块。

相关文章
相关标签/搜索