目录
1.分布式爬虫框架
Scrapy中有一个本地爬取队列Queue,这个队列是利用depue模块实现的,新的Repuest生成就会被放到队列里,随后被调度器Scheduler调度,交给Downloader执行爬取,简单的调度框架如图:
如果两个Scheduler同时从队列里去Repuest,每个Scheduler都有对应的Downloader,那么在宽带足够,正常爬取不考虑队列压力情况下,爬取效应会有什么变化,答案是爬取效率翻倍。
这样,Scheduler和Downloader都可以扩展多个,而爬取队列Queue必须从始至终为1个,也就是所谓的共享队列爬取,这样才能保证Scheduler从队列里调度每个Repuest后,其他Scheduler不会重复调度Repuest,就可以做到多个Repuest同步爬取了,这就是分布爬虫的基本雏形
我们要做的就是多台主机上同时运行爬虫任务协同爬取,协同爬取前提就是共享爬取队列,这样各台主机就不需要各自维护爬取队列,从共享爬取队列中存取Repuest就行了,但是各台主机还是有各自的Scheduler和Dwonloder,所以调度和下载功能分别完成,如果不考虑队列存取性能消耗,爬取效率还会成倍提高。
2.维护爬取队列
爬取队列怎样维护比较好呢?首先考虑就是性能问题,什么数据库存取效率高?我们自然就能想到基于内存存储的Redis,而且Redis支持多种数据结构,例如列表[list]、集合(set)、等,存储的操作也非常简单,所以我们这里采用Redis来维护爬取队列,
实际上这几种数据库存储结构各有千秋
● 列表数据结构有lpush、lpop、rpush、方法,我们可以用它实现一个先进先出式爬取队列,也可 以实现一个先进后出 得到栈式爬取队列。
● 集合的元素是无序且不重的,我们可以非常方便的实现随机排序不重复的爬取队列。
● 有序集合带有分数表示,而Scrapy和Repuest也有优先的控制,所以有序集合我们可以实现一个带有优先级调度的队列。
3.去重
Scrapy有自动去重功能,他的去重使用了python中的集合,这个集合记录了Scrapy和每个Repuest的指纹,这个指纹实际上就是Repuest散列值,我们可以看下Scrapy代码,
import hashlib
def repuest_fingerprint(repuest, include_headers=None):
if include_headers:
vif include_headers = tuple(to_bytes(h.lower())
for h in soted(if include_headers))
cache = _fingerprint_cache.setdefault(repuest,{})
if include_headers not in cache:
fp = hashlib. sha1()
fp. update(to_ bytes (request . method))
fp. update(to_ bytes(canonicalize url(request.uIl)))
fp. update(request . bodyor b'')
if include_ headers:
for hdr in include_ headers:
if hdr in request .headers:
fp.update(hdr)
for v in request.heders.getlist(hdr);
fp.update(v)
cache[include_headers ]fp.hexdigest()
return cache[include_headers ]
repuest_fingerpront就是计算Repuest指纹的方法,内部颁发就是使用hashlib的sha1方法。计算的字段包括Repuest的URL、Headers这几部分内容,只要有一点不同,那么计算的结果就不同,计算得到的结果加密后的字符串,也就是指纹,每个Repuest都有独有的指纹,指纹就是一个字符串,判断字符串比判定Repuest对象重复是否容易的多,所以指纹可以判定为Repuest是否重复的依据。
我们该如何判定呢?Scrapy是这样实现的
def__init__(self):
self.fingerprints = set()
def repuest_seen(self,repuest;
tp = self.repuest_fingerprint(repuest)
if fp in self.fingerprints:
return True
self.fingerprint.add(fp)
在去重的类RFPDupeFilter中,有一一个request_ seen 方法,该方法有一个参数request,它的作用就是检测Request对象是否重复。这个方法调用request_ fingerprint 获取该Request的指纹,检测这个指纹是否存在于fingerprints变量中,而fingerprints是一 个集合,集合的元素都是不重复的。如果指纹存在,就返回True,说明该Request是重复的,否则就将这个指纹加入集合。如果下次还有相同的Request 传递过来,指纹也是相同的,指纹就已经存在于集合中了,那么Request对象就会直接判定为重复。这样,去重的目的就实现了。
Scrapy的去重过程就是,利用集合元素的不重复特性来实现Request的去重。
对于分布式爬虫来说,我们肯定不能再用每个爬虫各自的集合来去重了。因为这样还是每个主机单独维护自己的集合,不能做到共享。多台主机如果生成了相同的Request, 只能各自去重, 各个主机之间就无法做到去重了。
那么要实现去重,这个指纹集合也需要是共享的。Redis 正好有集合的存储数据结构,我们可以利用Redis的集合作为指纹集合,那么这样去重集合也是利用Redis共享的。每台主机新生成Request后,把该Reques的指纹与集合比对,如果指纹已经存在,说明该Request是重复的,否则将Requet的指纹加人这个集合。利用同样的原理,我们在不同的存储结构中实现了分布式Reqeust的去重。
4.防止中断
在Scrapy中。爬虫运行时的Request队列放在内存中。爬虫运行中断后,这个队列的空间就被释放,此队列就销毁了。所以一且爬虫运行中断,爬虫再次运行就相当于全新的爬取过程。
要做到中断后继续爬取,我们可以将队列中的Request 保存起来,下次爬取直接读取保存数据即可获取上次爬取的队列。我们在Scrapy中指定一个爬取队列的存 储路径即可,这个路径使用J0B DIR变量来标识,可以用如下命令来实现:
scrapy crawl spider -S JOBDIR=crawls/spider
在Scrapy中,我们实际是把爬取队列保存到本地,第二次爬取直接读取并恢复队列。那么在分布式架构中,我们还用担心这个问题吗?不需要。因为爬取队列本身就是用数据库保存的,如果爬虫中断了,数据库中的Request依然存在,下次启动就会接着上次中断的地方继续爬取。
所以,当Redis的队列为空时,爬虫会重新爬取;当Redis的队列不为空时,爬虫便会接着上次中断之处继续爬取。
本期内容就到这里啦!打字不易,如果此文章对你有帮助的话,点个赞收个藏来个关注,给作者一个鼓励。也方便你下次能够快速查找!