5月31日 00:57

Scrapy 请求去重是怎么判断重复的?

直接答案

Scrapy 的请求去重由调度器调用 dupefilter 完成,默认实现是 RFPDupeFilter。它会为请求生成 fingerprint,通常由规范化后的 URL、请求方法、请求体组成;指纹已经出现过,就认为这个请求重复,不再入队。它解决的是“同一个请求不要重复抓”,不是“同一条业务数据不要重复入库”。所以列表页、详情页、翻页链接能靠它减少浪费,但商品 ID、文章 ID、用户 ID 的业务级去重,还应该放在 pipeline 或存储层。

python
# settings.py DUPEFILTER_CLASS = "scrapy.dupefilters.RFPDupeFilter" DUPEFILTER_DEBUG = False JOBDIR = ".job/article_spider" # 需要断点续爬时启用 # spider.py yield scrapy.Request(url, callback=self.parse_detail) yield scrapy.Request(url, callback=self.parse_detail, dont_filter=True) # 只给确实需要重复访问的请求

默认去重对 URL 会做规范化,比如参数顺序不同但含义相同的 URL,通常会生成相同指纹。请求方法和 body 也参与计算,所以同一个接口的 GET 和 POST 不会被当成同一个请求。dont_filter=True 是绕过去重的开关,适合登录、分页入口刷新、重试某个状态页,但不能随手加;一旦在列表链接上滥用,调度队列会膨胀,甚至把站点反复打穿。

追问

Scrapy 去重和数据库唯一索引有什么区别?

Scrapy 去重发生在请求入队前,目标是少发重复请求,节省带宽和时间。数据库唯一索引发生在数据写入时,目标是避免重复数据污染结果。两者最好同时存在,因为 URL 不重复不代表数据不重复,同一商品可能有 PC、移动端、活动页三个 URL。取舍上,请求去重提升爬取效率,业务去重保证数据质量,不能互相替代。

为什么有些看起来一样的页面没有被去重?

最常见原因是 URL 里有追踪参数、时间戳、随机数或不同排序参数,Scrapy 认为它们是不同请求。另一个原因是 POST body 不同,哪怕接口地址一样,也会生成不同指纹。遇到这种情况,不要先怪 dupefilter,应该先确认哪些参数影响内容,哪些只是噪声。噪声参数可以在生成请求前清理,或者自定义指纹规则,但清理过度会把真正不同的页面合并掉。

什么时候需要自定义去重规则?

当默认 URL 指纹无法表达业务唯一性时才需要自定义。比如搜索接口里 page、keyword 决定内容,而 _tcallbackutm_source 不决定内容,就可以在指纹里忽略后者。分布式爬虫也常把指纹放到 Redis,让多个 worker 共享已抓请求集合。边界是维护成本:规则越业务化,越容易在目标站改版后误杀请求,所以要给命中去重的样本留日志。

python
from scrapy.dupefilters import RFPDupeFilter from w3lib.url import canonicalize_url class CleanQueryDupeFilter(RFPDupeFilter): def request_fingerprint(self, request): url = canonicalize_url(request.url, keep_blank_values=False) return self.fingerprinter.fingerprint(request.replace(url=url)).hex()

dont_filter=True 应该怎么用才安全?

它适合少量入口型或状态型请求,比如每次启动都要访问首页拿 cookie,或者轮询一个会变化的任务状态页。不要把它放在详情页和翻页请求上,否则同一个链接会被反复入队。一个实用边界是:如果这个请求的返回内容依赖时间、登录态或外部状态,可以考虑跳过去重;如果只依赖 URL,本该让去重生效。踩坑最多的是复制登录请求代码时把 dont_filter=True 一起复制到了所有请求。

断点续爬时去重状态会保留吗?

只有配置 JOBDIR 后,队列和去重指纹才会持久化到磁盘,爬虫重启后可以继续使用。没有 JOBDIR 时,默认去重集合在内存里,进程结束就没了。这个能力适合长任务,但不适合频繁变动的短任务,因为旧指纹可能让你误以为“怎么不爬了”。如果目标站内容更新很快,应该定期清理 jobdir,或把增量策略改成按更新时间、业务 ID 控制。

还要注意,请求去重不是越激进越好。新闻站、论坛和电商列表经常会出现同一个 URL 在不同时间返回不同内容的情况,例如首页、热榜页、库存接口和价格接口。如果这些页面被默认指纹长期挡住,增量爬虫就会漏掉更新。更稳妥的做法是把“稳定详情页”和“会变化的入口页”分开:详情页交给默认去重,入口页按调度周期允许重复访问,再在解析出的业务 ID 上做增量判断。这样既不浪费大量详情请求,也不会因为去重太早而错过新数据。

在分布式场景里,去重还会影响任务分配公平性。多个节点共享 Redis 指纹集合时,一个节点先写入指纹,其他节点就不会再抓同一请求,这能减少重复劳动。但如果指纹规则里混入了节点本地状态,结果就会变得不可预测。建议把指纹生成逻辑做成纯函数,只依赖 URL、方法、body 和明确保留的参数,部署前用一批样例 URL 做回归测试。

标签:Scrapy