一、增量爬虫设计原理及其实现
1、增量爬虫设计原理
增量抓取,意即针对某个站点的数据抓取,当网站的新增数据或者该站点的数据发生了变化后,自动地抓取它新增的或者变化后的数据
设计原理:
2、实现关闭请求去重
2.1 为Request对象增加属性filter
# scrapy/http/reqeust.py
class Request():
"""
框架内封装的request对象
"""
def __init__(self, url, method='GET', headers=None, params=None,
data=None, parse='parse', meta={}, filter=True): # 此处修改
......
# 此处新增
self.filter = filter # 是否进行去重,默认是True 表示去重!
2.2 修改调度器,进行判断
# scrapy_plus/core/scheduler.py
class Scheduler(object):
......
# 此处修改
def add_request(self,request):
'''
实现添加request到队列中
:param request: 请求对象
:return: None
'''
#判断请求是否需要进行去重,如果不需要,直接添加到队列
if not request.filter:#不需要去重
request.fp = self._gen_fp(request)
self.queue.put(request)
logger.info("添加不去重的请求<{} {}>".format(request.method,request.url))
return #必须return
if self._filter_request(request):
self.queue.put(request)
......
2.3 修改项目中代码
# project/spiders/baidu.py
......
class BaiduSpider(Spider):
name = 'baidu'
start_urls = ['http://www.baidu.com']
total = 0
def parse(self, response):
self.total += 1
if self.total > 10:
return
yield Request(self.start_urls[0], filter=False, parse='parse')
# project/settings.py
......
# 启用的爬虫类
SPIDERS = [
'spiders.baidu.BaiduSpider',
# 'spiders.douban.DoubanSpider'
]
......
##3 实现无限发起请求
3.1 修改scrapy_plus/core/spider.py
添加一个timed_task属性
# scrapy_plus/core/spider.py
......
class Spider():
"""
构建请求信息(初始的),也就是生成请求对象(Request)
解析响应对象,返回数据对象(Item)或者新的请求对象(Request)
"""
name = ''
start_urls = [] # 默认初始请求地址
# 定时爬虫标识
# 默认为false,表示不是定时爬虫
timed_task = False
......
3.2 修改引擎代码
修改处理初始urls的_start_request函数
但由于框架调用start_requests方法是同步的,如果设置为死循环后,那么启动后其他的爬虫的start_requests方法就不会被调用,因此需要在调用每个爬虫的start_reqeusts时设置为异步的
修改引擎的逻辑函数_start_engine的退出逻辑
把所有爬虫的timed_task组成一个列表并sum()求和,等于0就表示都是False,没有定时任务
如果不等于0就表示有定时任务,此时引擎一直执行
scrapy_plus/core/engine.py
......
# 此处修改
def _start_request(self):
"""单独处理爬虫模块中start_requests()产生的request对象"""
def _func(spider_name, spider):
for start_request in spider.start_requests():
#1. 对start_request进过爬虫中间件进行处理
for spider_mid in self.spider_mids:
start_request = spider_mid.process_request(start_request)
# 为请求对象绑定它所属的爬虫的名称
start_request.spider_name = spider_name
#2. 调用调度器的add_request方法,添加request对象到调度器中
self.scheduler.add_request(start_request)
self.collector.incr(self.collector.request_nums_key)
for spider_name, spider in self.spiders.items():
# 把执行每个爬虫的start_requests方法,设置为异步的
self.pool.apply_async(_func, args=(spider_name, spider), error_callback=self._error_callback)
......
def _start_engine(self):
'''
具体的实现引擎的细节
:return:
'''
self.is_running = True # 启动引擎,设置状态为True
# 处理strat_urls产生的request
self.pool.apply_async(self._start_request, error_callback=self._error_callback) # 使用异步线程池中的线程执行指定的函数
# 不断的处理解析过程中产生的request
for i in range(MAX_ASYNC_THREAD_NUMBER): # 控制最大并发数
self.pool.apply_async(self._execute_request_response_item, callback=self._call_back, error_callback=self._error_callback)
# 此处修改
timed_task_sum = sum([spider.timed_task for spider in self.spiders.values()]) # 对[False,True]求和
start_url_nums = sum([len(spider.start_urls) for spider in self.spiders.values()])
# 控制判断程序何时中止
while True:
time.sleep(1) # 避免cpu空转,避免性能消耗
# 有定时爬虫时,一直不退出程序; 没有定时增量爬虫,才判断是否退出!
if self.collector.request_nums+self.collector.repeat_request_nums >= start_urls_nums and timed_task_sum == 0:
if self.collector.response_nums+self.collector.repeat_request_nums >= self.collector.request_nums:
self.is_running = False
break
3.3 项目代码中新增一个baidu2.py爬虫,在start_reqeusts中改成无限循环,并设置对应请求为非去重模式。(注意filter参数)
# project/spiders/baidu2.py
import time
from scrapy_plus.core.spider import Spider
from scrapy_plus.htttp.request import Request # 注意这里是htttp
class Baidu2Spider(Spider):
name = 'baidu2_spider'
start_urls = ['https://www.baidu.com']
timed_task = True # 表示 这是一个定时爬虫
def start_requests(self):
while True:
for url in self.start_urls:
yield Request(url, parse='parse', filter=False) # 注意这个parse接收的是字符串
time.sleep(6) # 定时发起请求,此时程序不会停止!
def parse(self, response):
print(response.body)
yield response.body # 一定要写yield
3.4 并在项目的settings.py中添加baidu2爬虫
# project/settings.py
......
# 启用的爬虫类
SPIDERS = [
'spiders.baidu.BaiduSpider',
'spiders.baidu2.Baidu2Spider',
]
......
显示结果:
2018-08-06 09:30:27 scheduler.py[line:43] INFO: 添加不去重的请求<GET http://www.baidu.com>
2018-08-06 09:30:27 scheduler.py[line:43] INFO: 添加不去重的请求<GET http://www.baidu.com>
2018-08-06 09:30:27 scheduler.py[line:43] INFO: 添加不去重的请求<GET http://www.baidu.com>
2018-08-06 09:30:27 scheduler.py[line:43] INFO: 添加不去重的请求<GET http://www.baidu.com>
2018-08-06 09:30:27 scheduler.py[line:43] INFO: 添加不去重的请求<GET http://www.baidu.com>
2018-08-06 09:30:27 scheduler.py[line:43] INFO: 添加不去重的请求<GET http://www.baidu.com>
注意:每次运行之前需要先清空redis数据库,否则会因为redis的url去重而停止运行
清空指令FLUSHALL
#二、断点续爬设计原理及其实现
##1 断点续爬的原理
在《框架升级-分布式爬虫设计原理及其实现》这一节当中,我们通过redis指纹集合已经实现了断点续爬的功能:
即利用持久化的指纹集合对request进行去重。
通过配置SCHEDULER_PERSIST=True来使用redis的同时也开启了断点续爬的功能
问题:那如何关闭断点续爬的功能呢?
思路:新增一个FP_PERSIST=True默认配置项,当它等于False的时候,引擎运行结束时删除redis中的指纹集合,这样就达到关闭断点续爬功能的目的了
##2 实现关闭断点续爬的功能
2.1 修改默认配置文件
# scrapy_plus/conf/default_settings.py
......
# 指纹是否持久化
# 默认为True 即持久化请求对象的指纹,达到断点续爬的目的
# 如果为False,则在爬虫结束的时候清空指纹库
# 可以在项目的settings.py中重新赋值进行覆盖
FP_PERSIST = True
......
2.2 修改utils下stats_collector.py中的clear()函数
# scrapy_plus/utils/stats_collector.py
from scrapy_plus.conf.settings import REDIS_HOST, REDIS_PORT, REDIS_DB, FP_PERSIST, REDIS_SET_NAME
......
def clear(self):
'''程序结束后清空所有计数的值'''
self.redis.delete(self.request_nums_key, self.response_nums_key,
self.repeat_request_nums_key, self.start_request_nums_key)
# 判断是否清空指纹集合
if not FP_PERSIST: # not True 不持久化,就清空指纹集合
self.redis.delete(REDIS_SET_NAME)
......
2.3 修改项目的settings.py文件
......
# 启用的爬虫类
SPIDERS = [
'spiders.baidu.BaiduSpider',
# 'spiders.baidu2.Baidu2Spider',
# 'spiders.douban.DoubanSpider',
]
......
FP_PERSIST = True
重装后不清空redis二次运行结果:
2018-08-06 09:59:36 scheduler.py[line:70] INFO: 发现重复的请求:<GET http://www.baidu.com>
2018-08-06 09:59:37 engine.py[line:91] INFO: 爬虫结束:2018-08-06 09:59:37.932641
2018-08-06 09:59:37 engine.py[line:92] INFO: 耗时:1.00631
2018-08-06 09:59:37 engine.py[line:96] INFO: 一共获取了请求:1个
2018-08-06 09:59:37 engine.py[line:97] INFO: 重复的请求:1个
2018-08-06 09:59:37 engine.py[line:98] INFO: 成功的请求:0个
#3. 请求丢失的情况
3.1 产生请求丢失的场景
当项目运行后,Ctrl+C主动终止进程,或爬虫代码异常等程序非正常结束的情况下,某个request对象已经从队列中取出,但最终获取的数据的过程没有完成。
此时fp指纹集合中已经存在了该指纹,再启动项目运行,因该请求设置了去重,那么再也无法发出该请求。
这个情况就是请求丢失。
3.2 不光我们的scrapy_plus会出现这个情况;scrapy框架同样如此
解决请求丢失情况的需求,本身就是个伪需求,没有必要,多次一举。
如果真的要解决请求丢失也是可以的
3.3 解决请求丢失的方案(了解)
方案一
持久化fp指纹集合的同时保存fp相应的request对象,并在获取的数据中添加fp字段,保存获取该数据的request指纹
全部数据抓取完成后,删除数据中相同的fp指纹,最后指纹集合中剩下的fp对应的request对象,就是丢失的请求
方案二
添加一个请求备份容器