Scrapy与分布式开发:框架原生去重机制源码解析与不足分析

框架原生去重机制源码解析与不足分析

导语

在网络爬虫和数据采集领域,去重机制是一个至关重要的环节。随着互联网的迅速发展,数据量呈爆炸式增长,如何在海量数据中高效地筛选出有价值且唯一的信息,成为了一个亟待解决的问题。去重机制正是为了解决这一问题而诞生的。

Scrapy原生去重机制源码解析与不足分析

Scrapy怎么使用去重机制的?

在构建scrapy.Request请求时设置参数dont_filter=Falsesettings不需要设置DUPEFILTER_CLASS,除非我们重构了原生方法

Scrapy原生去重机制源码解析

我们先找到实现去重的源码位置,从下图可以知道是在scrapy.dupefilters.RFPDupeFilter

在这里插入图片描述

RFPDupeFilter类大致的框架与功能就是如下图所示,通过此图我们需要深入了解request_seenrequest_fingerprint两个方法,它们是去重原理的实现。
在这里插入图片描述

定位到request_seen,代码也就几行,它的作用是调用request_fingerprint生成指纹,然后根据该指纹存在self.fingerprints与否来实现去重
在这里插入图片描述

再来看一下request_fingerprint方法,它是调用了其它方法,跟进去就看到实际的调用位置
在这里插入图片描述
在这里插入图片描述

仔细看上图,self._fingerprint可以有两种方式,通过REQUEST_FINGERPRINTER_IMPLEMENTATION来控制,这里面其实就是2.6版本和2.7版本。这两版本其实都是处理同样的信息(请求头,请求体、请求方法)生成的指纹(hashlib.sha1),官方推荐2.7版本,但是默认配置的是2.6版本
在这里插入图片描述

总结:Scrapy原生去重机制是,初始化生成一个指纹集合,对request对象携带的信息(请求头,请求体、请求方法)进行hashlib.sha1得到唯一指纹,若指纹存在于集合中,则去重,若不存在则进行请求。

不足与改进

面试者:首先这个去重原理其实没啥问题,基本都是通过生成唯一指纹来进行判断去重,但是Scrapy原生去重机制的指纹集合是存在于内存的,一般情况下去重场景都是针对全局的,而不是单个爬虫单个任务,一旦出现事故那么指纹就全没了,所以需要持久化的指纹集合。所以我用的是Scrapy-Redis的去重机制。
注意:这里要强调一下,不一定非得Scrapy-Redis,我们直接在Scrapy新增一个redis去重器扩展就行了)

Scrapy-Redis原生去重机制源码解析与不足分析

Scrapy-Redis原生去重机制源码解析

Scrapy-Redis项目需要在setting文件中指定DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter",指定定位到这个文件

在这里插入图片描述

通过上图,我们可以知道:

  1. BaseDupeFilter调用的还是Scrapy的,方法和属性变化不大
  2. 看源码我们知道request_fingerprint其实也是直接使用了Scrapy
  3. 主要变化是request_seen,这里判断机制变了

这是request_seen的源码,生成指纹的方式没变,唯一变的是判断重复的方式,sadd在向redis写入元素时,如果元素存在则返回0,反之返回1。
在这里插入图片描述

总结:Scrapy-Redis原生去重机制是,初始化生成一个redis对象,对request对象携带的信息(请求头,请求体、请求方法)进行hashlib.sha1得到唯一指纹,若指纹存在于redis对象中,则去重,若不存在则进行请求。

不足与改进

如何才算能去重

大家看到无论是Scrapy还是Scrapy-Redisrequest_seen源码时,有没有发现只要是request请求都会进行去重判断,而且只要判断不存在都会写入,但是如果出现以下情况:

  1. 请求因未知原因最终失败
  2. 请求虽然成功但是在管道写入时出现异常导致数据丢了

遇到这些情况我们需要重新添加名单去请求补数据,但是我们会发现名单被去重了,这就是这个去重机制的必然,怎么才能避免这个问题呢?

方案是:在确保数据成功存储的时候将名单加入去重队列,一般的业务场景都是采集数据最终都会上传到数据库或者对应存储机制,那我们可以在上传且保证上传成功时将名单加入去重队列。

自定义唯一指纹

通过前面的讲解,我们了解到了唯一指纹的生成机制,但是如果时GET这种固定参数请求倒还好,像POST这种传参的,如果还要加上时间戳或者破解参数的,岂不是根本没法友好的实现去重了。

方案:自定义去重组件,继承RFPDupeFilter类,在request_seen中实现根据业务确定的唯一指纹的生成机制即可,然后配置setting中的DUPEFILTER_CLASS即可生效。

posted @ 2024-02-28 16:57  七夜魔手  阅读(26)  评论(0)    收藏  举报  来源