You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scrapy:触发重复过滤器时终止爬虫进程的技术问询

这确实是个爬虫开发中挺常见的棘手问题——不想硬编码易变的总页码,又得避免爬虫在重复内容上无限空转。我整理了几个实用的方案,你可以根据自己的场景选择:

方法1:自定义重复过滤器,触发重复时终止爬虫

Scrapy自带的RFPDupeFilter只会过滤重复请求,但不会主动终止爬虫。我们可以继承它,添加“检测到重复就关闭爬虫”的逻辑:

首先在项目的middlewares.py里写自定义过滤器:

from scrapy.dupefilters import RFPDupeFilter
from scrapy.crawler import Crawler

class TerminatingDupeFilter(RFPDupeFilter):
    def __init__(self, path=None, debug=False):
        super().__init__(path, debug)
        self.crawler = None

    @classmethod
    def from_crawler(cls, crawler: Crawler):
        instance = cls(crawler.settings.get('DUPEFILTER_DEBUG'))
        instance.crawler = crawler
        return instance

    def request_seen(self, request):
        # 先调用父类方法判断是否重复
        seen = super().request_seen(request)
        if seen:
            # 检测到重复请求,直接关闭爬虫
            self.crawler.engine.close_spider(
                self.crawler.spider, 
                reason='Duplicate request detected, terminating spider'
            )
        return seen

然后在settings.py里替换默认的重复过滤器:

DUPEFILTER_CLASS = '你的项目名.middlewares.TerminatingDupeFilter'

这个方案适合请求URL重复的场景——比如网站把/page/26的请求重定向到/page/1,此时/page/26的URL会被识别为重复,触发终止。

方法2:跟踪已爬取条目,检测全重复页面后终止

如果网站超页后返回的是第一页内容,但URL还是/page/26(不会被请求重复过滤器拦截),那更可靠的方式是跟踪已爬取的条目唯一标识,当新页面的所有条目都已存在时终止爬虫:

import scrapy
from scrapy.exceptions import CloseSpider

class MySpider(scrapy.Spider):
    name = 'my_spider'
    start_urls = ['https://example.com/page/1']
    # 存储已爬取的条目唯一ID(比如item的data-id、详情页URL等)
    seen_item_ids = set()

    def parse(self, response):
        items = response.css('.item-container')
        current_page_item_ids = set()

        for item in items:
            # 提取条目唯一标识,这里用data-id举例
            item_id = item.css('::attr(data-id)').get()
            current_page_item_ids.add(item_id)
            
            if item_id not in self.seen_item_ids:
                self.seen_item_ids.add(item_id)
                # 处理并输出item
                yield {
                    'id': item_id,
                    'title': item.css('.title::text').get().strip(),
                    # 其他字段...
                }

        # 检查当前页面的所有条目是否都已爬过
        if current_page_item_ids.issubset(self.seen_item_ids):
            raise CloseSpider(reason='All items on this page are already scraped, terminating')

        # 构造下一页请求
        next_page_num = int(response.url.split('/')[-1]) + 1
        next_page_url = f'https://example.com/page/{next_page_num}'
        yield scrapy.Request(next_page_url, callback=self.parse)

这个方案更精准,因为它直接基于内容重复判断,不受URL变化的影响,推荐优先考虑这种方式。

方法3:设置重复阈值,达到次数后终止

如果担心单次重复就终止太激进(比如偶尔的网络波动导致重复请求),可以统计重复次数,达到阈值再终止:

修改自定义重复过滤器:

from scrapy.dupefilters import RFPDupeFilter
from scrapy.crawler import Crawler

class ThresholdTerminatingDupeFilter(RFPDupeFilter):
    def __init__(self, path=None, debug=False):
        super().__init__(path, debug)
        self.crawler = None
        self.duplicate_count = 0
        # 默认重复3次就终止
        self.duplicate_threshold = 3

    @classmethod
    def from_crawler(cls, crawler: Crawler):
        instance = cls(crawler.settings.get('DUPEFILTER_DEBUG'))
        instance.crawler = crawler
        # 可以从settings里配置阈值
        instance.duplicate_threshold = crawler.settings.getint('DUPLICATE_THRESHOLD', 3)
        return instance

    def request_seen(self, request):
        seen = super().request_seen(request)
        if seen:
            self.duplicate_count += 1
            if self.duplicate_count >= self.duplicate_threshold:
                self.crawler.engine.close_spider(
                    self.crawler.spider, 
                    reason=f'Duplicate requests reached threshold ({self.duplicate_threshold}), terminating'
                )
        return seen

然后在settings.py里配置:

DUPEFILTER_CLASS = '你的项目名.middlewares.ThresholdTerminatingDupeFilter'
DUPLICATE_THRESHOLD = 3

内容的提问来源于stack exchange,提问作者gunesevitan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:09:06