目标
- 理解增量式爬虫的原理
- 完成增量式爬虫的实现
1 增量爬虫设计原理
增量抓取,意即针对某个站点的数据抓取,当网站的新增数据或者该站点的数据发生了变化后,自动地抓取它新增的或者变化后的数据
设计原理:
1.1 实现关闭请求去重
- 为Request对象增加属性filter
# scrapy/http/reqeust.py
'''封装Request对象'''
class Request(object):
'''请求对象,设置请求信息'''
def __init__(self, url, method='GET', headers=None, params=None, data=None, filter=True):
self.url = url # 请求地址
self.method = method # 请求方法
self.headers = headers # 请求头
self.params = params # 请求参数
self.data = data # 请求体
self.filter = filter # 是否进行去重,默认是True
- 修改调度器,进行判断
# scrapy_plus/core/scheduler.py
class Scheduler(object):
......
def add_request(self, request):
'''添加请求对象'''
# 先判断是否要去重
if request.filter is False:
self.queue.put(request)
logger.info("添加请求成功<disable去重>[%s %s]" % (request.method, request.url))
self.total_request_number += 1 # 统计请求总数
return # 必须return
# 添加请求对象前,先进性去重判断
fp = self._gen_fp(request)
if not self.filter_request(fp, request): # 如果指纹不存在,那么添加该请求
self.queue.put(request)
logger.info("添加请求成功[%s %s]"%(request.method, request.url))
self._filter_container.add_fp(fp) # 添加完请求后,将指纹也记录下来
self.total_request_number += 1 # 统计请求总数
else:
logger.info("发现重复的请求 [%s %s]" % (request.method, request.url))
self.repeat_request_number += 1
......
1.2 实现无限发起请求:
新增爬虫抓取:新浪滚动新闻
- 在start_reqeusts中改成无限循环,并设置对应请求为非去重模式。(注意)
# spiders/baidu.py
import time
from scrapy_plus.core.spider import Spider
from scrapy_plus.http.request import Request
from scrapy_plus.item import Item
import js2py
class SinaGunDong(Spider):
name = "sina_gundong"
def start_requests(self):
while True:
# 需要发起这个请求,才能获取到列表页数据,并且返回的是一个js语句
url = "http://roll.news.sina.com.cn/interface/rollnews_ch_out_interface.php?col=89&spec=&type=&ch=&k=&offset_page=0&offset_num=0&num=120&asc=&page=1&r=0.5559616678192825"
yield Request(url, parse='parse', filter=False)
time.sleep(10) # 每10秒发起一次请求
def parse(self, response):
'''响应体数据是js代码'''
# 使用js2py模块,执行js代码,获取数据
ret = js2py.eval_js(response.body.decode("gbk")) # 对网站分析发现,数据编码格式是gbk的,因此需要先进行解码
yield Item(ret.list)
但由于框架调用start_requests方法时同步,如果设置为死循环后,那么位于之后的爬虫的start_requests方法就不会被调用,因此需要在调用每个爬虫的start_reqeusts时设置为异步的
# scrapy_plus/core/engine.py
class Engine(object):
......
def _start_requests(self):
'''向调度器添加初始请求'''
# 1. 爬虫模块发出初始请求
# for spider_name, spider in self.spiders.items():
# for start_request in spider.start_requests():
# # 2. 把初始请求添加给调度器
# # 利用爬虫中间件预处理请求对象
# for spider_mid in self.spider_mids:
# start_request = spider_mid.process_request(start_request)
# start_request.spider_name = spider_name #为请求对象绑定它所属的爬虫的名称
# self.scheduler.add_request(start_request)
def _func(spider_name, spider):
for start_request in spider.start_requests():
# 2. 把初始请求添加给调度器
# 利用爬虫中间件预处理请求对象
for spider_mid in self.spider_mids:
start_request = spider_mid.process_request(start_request)
start_request.spider_name = spider_name #为请求对象绑定它所属的爬虫的名称
self.scheduler.add_request(start_request)
# 1. 爬虫模块发出初始请求
for spider_name, spider in self.spiders.items():
self.pool.apply_async(_func, args=(spider_name, spider)) # 把执行每个爬虫的start_requests方法,设置为异步的
......
让程序的主线程在,多个start_reqeusts方法都没执行完毕前,不要进行退出判断,避免退出过早:
# scrapy_plus/core/engine.py
class Engine(object):
'''
负责驱动各大组件,通过调用各自对外提供的API接口,实现它们之间的交互和协作
提供整个框架的启动入口
'''
def __init__(self):
......
self.finshed_start_requests_number = 0
......
def _callback_total_finshed_start_requests_number(self, temp):
'''记录完成的start_requests的数量'''
self.finshed_start_requests_number += 1
def _start_requests(self):
......
# 让主线程在这里阻塞
while True:
time.sleep(0.001) # 节省cpu消耗
# self.pool.apply_async(self._execute_request_response_item) # 发起一个请求,处理一个响应
# 设置退出循环的条件:
# 当处理完的响应数等于总的请求数时,退出循环:
if self.finshed_start_requests_number == len(self.spiders): # 判断是否所有爬虫的start_requests是否都执行完毕,
# 如果都执行完毕,才应该应该进行退出判断
if self.total_response_number == self.scheduler.total_request_number and self.total_response_number != 0:
self.running = False # 设为Flase, 让子线程满足判断条件,不再执行递归循环,然后退出
break
logger.info("主线程循环已经退出")
self.pool.close() # 意味着无法再向pool添加任务,,无法在调用apply_async apply
self.pool.join() #