Python技术迷

京东面试题:说一下Scrapy如何去重?如何自定义去重规则?

在 Scrapy 爬虫框架中,数据去重是一个关键环节,直接影响爬取效率和数据质量。

默认情况下,Scrapy 主要通过 URL 作为唯一标识符 进行去重,但它的去重机制远不止于此,还可以基于请求内容、请求头、Cookies 甚至自定义规则来优化去重策略。

今天,我们就来深入探讨 Scrapy 的去重原理,并提供实战代码示例,最后给出面试中常见的去重问题及最优回答。

Scrapy 的去重是由 调度器(Scheduler) 负责的。每当一个新的请求(Request)进入 Scrapy 时,调度器会检查该请求是否已经被处理过,具体的去重机制如下:

1. Scrapy 默认的去重机制

Scrapy 默认使用 RFPDupeFilter 进行去重,该类基于 请求的 URL 进行去重,它的工作方式如下:

  • Scrapy 在 请求队列(Scheduler) 中维护了一个 已访问 URL 的哈希表。
  • 每当一个新请求进入时,Scrapy 计算该请求的 URL 哈希值,并检查是否已经存在于该哈希表中:
    • 如果存在:该请求被丢弃,避免重复爬取相同的页面。
    • 如果不存在:该 URL 会被添加到哈希表中,并进入爬取队列。

Scrapy 的默认去重基于 RFPDupeFilter,该类的核心代码位于 scrapy.dupefilters 模块,核心逻辑如下:

import hashlib

class RFPDupeFilter:
    def request_fingerprint(self, request):
        # 计算请求的唯一指纹(基于URL)
        return hashlib.sha1(request.url.encode()).hexdigest()

        def request_seen(self, request):
        fp = self.request_fingerprint(request)
        if fp in self.fingerprints:
            return True  # URL 已经访问过,丢弃请求
        self.fingerprints.add(fp)  # 记录新 URL
        return False

2. Scrapy 持久化去重

默认情况下,Scrapy 在内存中维护去重信息(即存储于 Python 的 set 结构中)。但是,如果爬虫因意外终止或重启,去重记录会丢失,导致重复爬取已访问的 URL。因此,我们可以通过 持久化去重 来解决这个问题。

Scrapy 允许使用 DUPEFILTER_CLASS 设置自定义去重过滤器。例如,可以使用 scrapy_redis 来实现 Redis 持久化去重:

# 在 settings.py 中启用 Redis 去重过滤器
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER_PERSIST = True

这样,爬取的 URL 记录会被存储在 Redis 中,即使爬虫重启也不会重复爬取相同的页面。

3. 自定义去重策略

Scrapy 默认的去重仅基于 URL,但有些场景下,仅仅依赖 URL 并不足够,比如:

  • 不同的请求方法(GET/POST)
  • 不同的请求参数
  • 相同的 URL 但返回不同的页面内容

此时,我们需要自定义去重逻辑。Scrapy 允许我们重写 request_fingerprint 方法,以不同的方式计算请求的唯一标识。

例如,假设我们要基于 URL + 请求参数 进行去重:

from scrapy.dupefilters import RFPDupeFilter
import hashlib

class CustomDupeFilter(RFPDupeFilter):
    def request_fingerprint(self, request):
        # 使用 URL + 请求参数 生成唯一指纹
        data = request.url + str(request.body)  # 结合 URL 和 body
        return hashlib.sha1(data.encode()).hexdigest()

然后,在 settings.py 中启用这个自定义去重类:

DUPEFILTER_CLASS = "myproject.custom_filters.CustomDupeFilter"

这样,即使 URL 相同,但 POST 请求的参数不同,Scrapy 也不会认为它是重复请求。

4. 如何在 Scrapy 爬虫中查看去重情况

如果你想查看 Scrapy 处理的去重数据,可以在 调试模式 下运行爬虫,并启用 DUPEFILTER_DEBUG 选项:

DUPEFILTER_DEBUG = True

这将允许 Scrapy 在日志中输出重复请求的信息,比如:

Filtered duplicate request: <GET https://example.com/page1> (referer: None)

此外,你也可以通过 stats 访问去重的数量:

from scrapy.statscollectors import StatsCollector

class MySpider(scrapy.Spider):
    name = "my_spider"

    def parse(self, response):
        duplicates = self.crawler.stats.get_value("dupefilter/filtered", 0)
        self.logger.info(f"去重的请求数量: {duplicates}")

Scrapy 的去重机制是面试中常见的考点,下面是回答:

Scrapy 默认使用 RFPDupeFilter 进行去重,主要基于 URL 进行哈希存储,如果请求的 URL 在去重集合中已存在,则会被过滤掉。默认情况下,去重记录存储在内存中,并不会持久化。

但是,如果爬虫重启,去重信息会丢失。为了支持持久化去重,我们可以使用 scrapy_redis,将去重数据存储在 Redis 中:

DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

如果默认的 URL 去重不满足需求,我们还可以自定义去重规则,比如:

  • 基于 URL + 请求参数
  • 基于 Headers
  • 基于完整的请求体

自定义去重可以通过 重写 request_fingerprint 方法 来实现:

class CustomDupeFilter(RFPDupeFilter):
    def request_fingerprint(self, request):
        data = request.url + str(request.body)  # 结合 URL 和 body 生成指纹
        return hashlib.sha1(data.encode()).hexdigest()

这样,即使 URL 相同,但请求参数不同,Scrapy 也不会认为它是重复的请求。

Scrapy 的去重机制虽然默认基于 URL,但可以通过 持久化存储 和 自定义规则 来优化。无论是应对爬取大规模网站,还是处理复杂请求,我们都可以通过灵活调整 Scrapy 的去重策略来提高爬取效率。

最后,我为大家打造了一份deepseek的入门到精通教程,完全免费:https://www.songshuhezi.com/deepseek

也可以看我写的这篇文章《DeepSeek满血复活,直接起飞!》来进行本地搭建。

对编程、职场感兴趣的同学,大家可以联系我微信:golang404,拉你进入“程序员交流群”。
🔥虎哥私藏精品 热门推荐🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》。

资料包含了《IDEA视频教程》、《最全python面试题库》、《最全项目实战源码及视频》及《毕业设计系统源码》,总量高达650GB,全部免费领取。