Scrapy爬虫改为Scrapy-Redis增量式爬虫

如何把一个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}

猜你喜欢

转载自blog.csdn.net/Hepburn_li/article/details/81480616