笔记-scrapy-深入学习-sheduler
笔记-scrapy-深入学习-sheduler
1. scheduler.py
source code:scrapy/core/scheduler.py:
1.1. 初始化的开始
在分析engine的open_spider函数时,讲过scheduler对象是通过类的from_cralwer方法生成的,代码如下:
@classmethod
def from_crawler(cls, crawler):
settings = crawler.settings
dupefilter_cls = load_object(settings['DUPEFILTER_CLASS'])
dupefilter = dupefilter_cls.from_settings(settings)
pqclass = load_object(settings['SCHEDULER_PRIORITY_QUEUE'])
dqclass = load_object(settings['SCHEDULER_DISK_QUEUE'])
mqclass = load_object(settings['SCHEDULER_MEMORY_QUEUE'])
logunser = settings.getbool('LOG_UNSERIALIZABLE_REQUESTS')
return cls(dupefilter, jobdir=job_dir(settings), logunser=logunser,
stats=crawler.stats, pqclass=pqclass, dqclass=dqclass, mqclass=mqclass)
在最后实际还是调用类的__init__()。
class Scheduler(object):
def __init__(self, dupefilter, jobdir=None, dqclass=None, mqclass=None,
logunser=False, stats=None, pqclass=None):
self.df = dupefilter
self.dqdir = self._dqdir(jobdir)
self.pqclass = pqclass
self.dqclass = dqclass
self.mqclass = mqclass
self.logunser = logunser
self.stats = stats
在这里指定了各种队列类型,但并没有构造队列,在下面会讲到。
实际上调度器默认只定义了2种队列类型,但并不是在这里构造的:
dqs:基于磁盘的任务队列:在配置文件可配置存储路径,每次执行后会把队列任务保存到磁盘上;
mqs:基于内存的任务队列:每次都在内存中执行,下次启动则消失;
1.2. 核心对象
前面共创建了4个对象,分别是dupefilter,pqclass,dqclass,mqclass.
1.dupefilter:
DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'
这个类的含义是"Request Fingerprint duplicates filter",请求指纹重复过滤。
它对每个request请求生成指纹,对指纹重复的请求进行过滤。具体实现见文档:scrapy-去重
2.pqclass
SCHEDULER_PRIORITY_QUEUE = 'queuelib.PriorityQueue'
从名字上也可以看出这个一个优先级队列,使用的是queuelib.它的作用应该不说也明白就是对request请求按优先级进行排序。
如何指定优先级?
前面讲spider时,讲述过可以在spider中定义Rule规则来过滤我们需要跟进的链接形式,我们只要定义规则时指定一个process_request关键字参数即可,这个参数是一个函数,会传递给我们将要继续跟进的Request,我们直接对其设置priority属性即可。
优先级是一个整数,虽然queuelib使用小的数做为高优化级,但是由于scheduler再入队列时取了负值,所以实际上数值越大优先级越高,代码如下:
def _dqpush(self, request):
if self.dqs is None:
return
try:
reqd = request_to_dict(request, self.spider)
self.dqs.push(reqd, -request.priority)
3.dqclass
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleLifoDiskQueue'
从名字上看,这是一个支持序列化的后进先出的磁盘队列。主要用来帮助我们在停止爬虫后可以接着上一次继续开始爬虫。
序列化要指定一个目录,用于存储序列化文件。这个目录在命令行上通过'-s JOBDIR=XXX'来指定。scheduler会在这个目录下创建active.json文件,用来序列化队列的优先级。
def _dq(self):
activef = join(self.dqdir, 'active.json')
if exists(activef):
with open(activef) as f:
prios = json.load(f)
else:
prios = ()
q = self.pqclass(self._newdq, startprios=prios)
if q:
logger.info("Resuming crawl (%(queuesize)d requests scheduled)",
{'queuesize': len(q)}, extra={'spider': self.spider})
return q
_dq在engine open_spider时调用scheduler的open时调用,可以看到如果命令指定了JOBDIR参数,则从目录下寻找active.json,这个文件存储的上一次指定的优先级集合,然后用它和_newdq一起构造磁盘队列,这样就可以接着上次停止时的状态继续爬取了。
其中_newdq会使用JOBDIR和优先级作为参数初始化磁盘队列对象。
def _newdq(self, priority):
return self.dqclass(join(self.dqdir, 'p%s' % priority))
最后在scheduler关闭时会将优化级存入文件active.json文件,用于下次反序列化。
def close(self, reason):
if self.dqs:
prios = self.dqs.close()
with open(join(self.dqdir, 'active.json'), 'w') as f:
json.dump(prios, f)
return self.df.close(reason)
4.mqclass
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.LifoMemoryQueue'
从名字上看,是后进先出的内存队列。这个队列是为了构造优先级队列而存在的,在构造优先级队列时,需要传递一个队列工厂类,用它来构造每个不同的优先级队列,构造时会向这个队列工厂类传递优先级作为唯一的参数。
默认情况下实际上就是queuelib.LifoMemoryQueue.
1.3. 入列及出列
前面了解了内存队列和磁盘队列,下面看下scheduler怎样出列入列:
请求的获取和存入流程:
def next_request(self):
request = self.mqs.pop()
if request:
self.stats.inc_value('scheduler/dequeued/memory', spider=self.spider)
else:
request = self._dqpop()
if request:
self.stats.inc_value('scheduler/dequeued/disk', spider=self.spider)
if request:
self.stats.inc_value('scheduler/dequeued', spider=self.spider)
return request
取请求时优先使用内存队列,如果内存队列没有请求再使用磁盘队列。
在请求入队列时,优先存入磁盘队列,如果没有磁盘队列再存入内存队列。
def enqueue_request(self, request):
if not request.dont_filter and self.df.request_seen(request):
self.df.log(request, self.spider)
return False
dqok = self._dqpush(request)
if dqok:
self.stats.inc_value('scheduler/enqueued/disk', spider=self.spider)
else:
self._mqpush(request)
self.stats.inc_value('scheduler/enqueued/memory', spider=self.spider)
self.stats.inc_value('scheduler/enqueued', spider=self.spider)
return True
Scheduler在入列前调用过滤器进行过滤,如果request对象没有定义dont_filter选项,则用df来过滤,关于去重方法,见文档:scrapy-去重。.
调度器的核心基本就这些了。
另外还有一个LOG_UNSERIALIZABLE_REQUESTS参数,它是用来指定如果一个请求序列化失败,是否要记录日志。
1.4. 队列实现
前面定义了队列,但并没有实例化,
def open(self, spider):
self.spider = spider
self.mqs = self.pqclass(self._newmq)
self.dqs = self._dq() if self.dqdir else None
return self.df.open()
使用self.pqclass(self._newmq),默认情况下等效于PriorityQueue(scrapy.squeues.LifoMemoryQueue)
class PriorityQueue(object):
"""A priority queue implemented using multiple internal queues (typically,
FIFO queues). The internal queue must implement the following methods:
"""
def __init__(self, qfactory, startprios=()):
self.queues = {}
self.qfactory = qfactory
for p in startprios:
self.queues[p] = self.qfactory(p)
self.curprio = min(startprios) if startprios else None
def push(self, obj, priority=0):
if priority not in self.queues:
self.queues[priority] = self.qfactory(priority)
q = self.queues[priority]
q.push(obj) # this may fail (eg. serialization error)
if self.curprio is None or priority < self.curprio:
self.curprio = priority
def pop(self):
if self.curprio is None:
return
q = self.queues[self.curprio]
m = q.pop()
if len(q) == 0:
del self.queues[self.curprio]
q.close()
prios = [p for p, q in self.queues.items() if len(q) > 0]
self.curprio = min(prios) if prios else None
return m
这里是优先级队列的初始化代码,queues是一个dict,dict元素是priority:qfactory形式。
结合起来的结果就是scrapy待爬队列初始化都是优先级队列,如果以后所有request的优先级相同(默认0),那么待爬队列就时一个FIFO/LIFO。如果使用不同的优先级,那么就是一个优先级队列了。
qfactory指定二级队列类型,它来自SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.LifoMemoryQueue',
修改它有两种结果:
- 所有请求优先级为0或相同,那么待爬队列就是一个LIFO或FIFO;
- 请求的优先级不同,那么待爬队列就是一个优先级队列,优等级队列的元素值也是队列(LIFO或FIFO)。
1.5. 爬取方式
从上一章节可以看出scrapy默认使用LIFO,那么它的爬取方式是深度优先,如果想使用广度优先,把SCHEDULER_MEMORY_QUEUE 改为FIFO就可以了。
笔记-scrapy-深入学习-sheduler的更多相关文章
- HTML+CSS学习笔记 (6) - 开始学习CSS
HTML+CSS学习笔记 (6) - 开始学习CSS 认识CSS样式 CSS全称为"层叠样式表 (Cascading Style Sheets)",它主要是用于定义HTML内容在浏 ...
- scrapy爬虫学习系列五:图片的抓取和下载
系列文章列表: scrapy爬虫学习系列一:scrapy爬虫环境的准备: http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_python_00 ...
- scrapy爬虫学习系列四:portia的学习入门
系列文章列表: scrapy爬虫学习系列一:scrapy爬虫环境的准备: http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_python_00 ...
- scrapy爬虫学习系列二:scrapy简单爬虫样例学习
系列文章列表: scrapy爬虫学习系列一:scrapy爬虫环境的准备: http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_python_00 ...
- scrapy爬虫学习系列一:scrapy爬虫环境的准备
系列文章列表: scrapy爬虫学习系列一:scrapy爬虫环境的准备: http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_python_00 ...
- scrapy爬虫学习系列三:scrapy部署到scrapyhub上
系列文章列表: scrapy爬虫学习系列一:scrapy爬虫环境的准备: http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_python_00 ...
- scrapy再学习与第二个实例
这周对于Scrapy进一步学习,知识比较零散,需要爬取的网站因为封禁策略账号还被封了/(ㄒoㄒ)/~~ 一.信息存储 1.log存储命令:scrapy crawl Test --logfile=tes ...
- 【笔记】MySQL学习之索引
[笔记]MySQL学习之索引 一 索引简单介绍 索引,是数据库中专门用于帮助用户快速查询数据的一种数据结构.类似于字典中的目录,查找字典内容时可以根据目录查找到数据的存放位置,然后直接获取即可. 普通 ...
- 笔记-redis深入学习-1
笔记-redis深入学习-1 redis的基本使用已经会了,但存储和读取只是数据库系统最基础的功能: 数据库系统还得为可靠实现这两者提供一系列保证: 数据.操作备份和恢复,主要是持久化: 高可用:主要 ...
随机推荐
- 转:ArcGIS10.1正式版安装与破解
一.准备文件 ArcGIS10.1安装包:ArcGIS_Desktop_10.1_129026(en) 认证服务:Pre-release_license_manager 注册机:arcgis10.1K ...
- 浅谈移动优先的跨终端Web 解决方案
1.基准 我们定义测试基准和开发基准,也就是说我们定义我们在哪些浏览器上去进行调试. 左侧图主要是定义PC上的基准,其中A级项目中必须支持,B级可选,C级观察. 2.检测 主要是终端检测 这是一张架构 ...
- Android开发由eclipse转Android Studio中一些常见出错问题解决方法
1.给一个Activity添加了一个Dialog主题,结果出现了下面的问题,在eclipse却没有出错 <activity android:name=".DialogActivity& ...
- JavaScript基础:字符串转换函数——String()和toString()
1..toString()可以将所有的的数据都转换为字符串,但是要排除null 和 undefined 例如将false转为字符串类型 <script> var str = false ...
- HTML-JS-CSS基础
HTML-JS-CSS基础 1.html hyper text markup language,超文本标记语言,所见即所得.web开发中用于展示功能的部分,浏览器可对其进行渲染.产生各种可视化组件,比 ...
- c++的bind1st()与bind2nd() 二元算子转一元算子
bind1st()和bind2nd()是两个函数,用于将二元算子转成一元算子. 何谓二元算子? 比如< > =等等这些就是二元算子,即需要两个操作数的运算符. 何谓一元算子? 比如++ - ...
- vs下如何调试Dll
1.首先需要一个exe加载你的dll 2.dll项目的属性设置 3.将dll设为启动项 4.在dll中设置断点 F5就可以调试了
- eclipse快捷键(个人经常使用的)
周末闲来无聊,就把一直以来eclipse使用的快捷键梳理了下,希望对有需要的朋友有所帮助,绝对原创! 1.快速复制一行或者多行 首先选中你要复制的行,如果是一行,则鼠标焦点在那一行即可,如果是多行,可 ...
- 【HHHOJ】ZJOI2019模拟赛(十二)03.03 解题报告
点此进入比赛 得分: \(0+77+20=97\) 排名: \(Rank\ 5\) \(Rating\):\(+46\) \(T1\):[HHHOJ178]依神(点此看题面) 这套题目中的唯一一道传统 ...
- 【CCPC-Wannafly Winter Camp Day3 (Div1) D】精简改良(状压DP)
点此看题面 大致题意: 给你一张图,定义\(dis(i,j)\)为\(i\)与\(j\)的最短距离,现要求删去若干条边,使得图仍然联通,且\(\sum_{i=1}^n\sum_{j=i+1}^ndis ...