如何把一个Scrapy项目改造成Scrapy-Redis增量式爬虫

前提: 安装Scrapy-Redis

* 1.原有的爬虫代码不用改动
* 2 在setting配置文件中添加如下配置 1. 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据,
从而实现请求去重的持久化
DUPEFILTER_CLASS = “scrapy_redis.dupefilter.RFPDupeFilter”
2. 增加了调度的配置, 作用: 把请求对象存储到Redis数据, 从而实现请求的持久化.
SCHEDULER = “scrapy_redis.scheduler.Scheduler”
3. 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储,
就不清空数据, 否则清空数据
SCHEDULER_PERSIST = True

4. redis_url配置

REDIS_URL = ‘reds://127.0.0.1:6379/2’

5. 如果需要把数据存储到Redis数据库中, 可以配置RedisPipeline

ITEM_PIPELINES = {
# 把爬虫爬取的数据存储到Redis数据库中
‘scrapy_redis.pipelines.RedisPipeline’: 400,
}

源码分析
# 调度配置,把请求对象存储到Redis数据, 从而实现请求的持久化 # TODO: add SCRAPY_JOB support. class
Scheduler(object): def close(self, reason): # 如果不持久化就调用flush if not self.
persist: # SCHEDULER_PERSIST = True self.flush() def flush(self): # 清空指纹容器:
清空Redis中存储指纹的set集合 self.df.clear() # 清空请求队列: 请求Redis中存储请求对象二进制数据的zset集合 self
.queue.clear()def enqueue_request(self, request): """把引擎交给调度器的请求, 存储到Redis数据库中"
"" # 如果请求是过滤的 并且 该请求重复; 也就是在去重容器中已经有这个请求了 if not request.dont_filter and self
.df.request_seen(request): # 记录去重日志, 然后就结束了,return false表示没有入队 self
.df.log(request,self.spider) return False # return会终止return下边代码的执行 #
走到下一步的两个条件, 只要满足其中一个即可 # 1. 如果请求不过滤就直接进来了,入队 # 2. 如果请求需要去重, 但是它是一个全新请求 # 把请求对象入队
self.queue.push(request) return True def next_request(self): ""
"redis的请求队列中取出一个请求对象: Request对象""" # 默认是 0,默认取第一个 request = self.queue.pop(0)
return request # 去重容器类配置,使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化 # TODO: Rename
class to RedisDupeFilter. class RFPDupeFilter(BaseDupeFilter): def request_seen(
self, request): # 根据Request对象, 生成一个请求的指纹字符串 fp = self
.request_fingerprint(request)# 向redis的用于存储请求指纹的set集合中添加该请求对应的指纹 #
如果添加成功了就返回一个大于0数, 添加失败了就返回0 added = self.server.sadd(self.key, fp) # 如果added ==
0就说明添加失败了, 也就是当set集合已经有这个数据了才会添加失败 # 也就是这个请求已经请求过了(即重复了)), 此时就返回True, 否则就返回False
return added == 0 def request_fingerprint(self, request): return
request_fingerprint(request)## - 生成指纹的代码在scrapy.utils.request.py文件中 def
request_fingerprint(request, include_headers=None): """
http://www.example.com/query?id=111&cat=222
http://www.example.com/query?cat=222&id=111 # 规范化处理: 起始对URL后的参数进行一个排序.
保证上面的URL是同一个. """ # 用于缓存请求指纹的 cache = {} # 通过hashlib库获取一个sha1()算法的对象. #
sha1()是一个数字摘要的算法, 一般不同的数据通过sha1生成的数字摘要就不同的. 所以可以用来做去重. # 好处: 节省存储空间. fp =
hashlib.sha1()# 更加sha1算法中原始二进制数据 # 把请求的方法名添加sha1算法中
fp.update(to_bytes(request.method))# 把请求的URL进行规范化处理后(就是对请求参数进行排序)添加到sha1算法中
fp.update(to_bytes(canonicalize_url(request.url)))#
对POST请求的请求体数据,如果没有这个数据就是添加一个b'' 添加到sha1算法中 fp.update(request.body or b'') #
sha1算法会根据内部数据生成一个十六进制的指纹,赋值 cache[include_headers] cache[include_headers] =
fp.hexdigest()# 返回了ha1算法会根据内部数据生成一个十六进制的指纹 return cache[include_headers] #
把爬虫爬取的数据存储到Redis数据库中,以list形式 class RedisPipeline(object): def process_item(self
, item, spider): # 把_process_item的任务分发到线程中完成. return deferToThread(self
._process_item, item, spider)def _process_item(self, item, spider): #
根据爬虫名称生成一个key,存储到Redis数据库中。 # 格式: 爬虫名称:items key = self.item_key(item, spider)
# 把 item 序列化为 json格式的数据 data = self.serialize(item) # 把数据存储到Redis队列中 self
.server.rpush(key, data)return item def item_key(self, item, spider): ""
"Returns redis key based on given spider. """ return self.key % {'spider':
spider.name}