Scrapy-redis的RFPDupeFilter仅对URL等请求指纹去重,跨会话需Redis持久化;增量采集须在start_requests中动态查DB生成since参数;JS渲染需自定义指纹逻辑;去重与增量逻辑应分层设计。

用 scrapy-redis 的 dupefilter 做去重,但只对 URL 有效
Scrapy 默认的去重机制基于请求指纹(request_fingerprint),只在单次运行中生效,重启后清空。想跨会话去重,必须把指纹存到持久化存储里。scrapy-redis 是最常用方案,它把 RFPDupeFilter 指纹写进 Redis,每次请求前查一次。
关键配置如下:
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' SCHEDULER = 'scrapy_redis.scheduler.Scheduler' SCHEDULER_PERSIST = True REDIS_URL = 'redis://localhost:6379'
注意:SCHEDULER_PERSIST = True 必须设为 True,否则爬虫结束时 Redis 中的请求队列和指纹会被清空;另外,这个去重仅判断 url + method + body + headers 等生成的指纹,不感知页面内容是否真变了。
按业务字段(如 ID、时间戳)做增量判断,得自己写 start_requests
很多网站内容更新是追加式(比如新文章 ID 更大、发布时间更晚),这时不能只靠 URL 去重,而要在发起请求前查数据库或缓存,过滤掉已存在的记录。
立即学习“Python免费学习笔记(深入)”;
典型做法是在 Spider 类中重写 start_requests 方法:
- 从 MySQL/Redis 读出最新已采集的
article_id或publish_time - 构造带条件的起始请求,例如
https://api.example.com/articles?since_id=12345 - 避免直接遍历全量列表页——那容易漏掉中间删除或跳号的情况
示例片段:
def start_requests(self):
latest_id = self.get_latest_id_from_db() # 自定义方法
yield scrapy.Request(
url=f'https://example.com/api/items?after={latest_id}',
callback=self.parse_api
)
用 scrapy-splash 或 JS 渲染时,指纹可能失效
如果目标页面依赖 JS 渲染,且你用 scrapy-splash 或 scrapy-playwright 抓取,request_fingerprint 默认不包含渲染后的状态,会导致同一 URL 多次抓取(比如分页按钮点击后 URL 不变但内容变)。
这时需手动增强指纹逻辑:
- 在
Request中传入meta标识渲染上下文,例如meta={'splash': {'args': {...}}} - 自定义
DupeFilter子类,在request_fingerprint中加入meta关键字段(如page_state、scroll_offset) - 慎用
cache_args:Splash 的缓存默认开启,但若页面行为依赖随机 token 或时间戳,缓存反而导致漏数据
增量逻辑和去重机制要分层设计,别混在一起
URL 去重(dupefilter)解决的是“别重复发同一个请求”,增量逻辑(比如查 DB 后拼 since 参数)解决的是“别重复采同一批数据”。两者目标不同,强行合并容易出问题。
常见错误包括:
- 在
parse里查 DB 判断是否入库过,再决定是否 yield item —— 这无法阻止重复请求被调度,浪费带宽和 IP - 把
last_crawl_time写死在start_urls里,没做运行时动态计算,导致每次启动都从固定时间开始 - 用 Redis
SET存已处理 ID,但没设 TTL,长期运行后内存暴涨
真正健壮的增量爬虫,得让“请求生成”“请求去重”“结果落库”三层各自负责,且每层失败都有日志可追溯。尤其是时间/ID 边界值处理——比如某条记录刚好在两次爬取间隔中发布,边界没对齐就会漏掉。


















