5月31日 00:57

Scrapy 请求失败后怎么重试?错误处理机制该怎么配?

Scrapy 的重试不是越多越好

Scrapy 自带 RetryMiddleware,能处理连接超时、DNS 错误、部分 HTTP 状态码等失败场景。它的价值不是“保证每个请求都成功”,而是在短暂网络抖动、服务端偶发 500、代理临时不可用时给请求一次恢复机会。真正需要注意的是:重试会消耗队列、带宽和时间,配置不当还会把目标站的压力继续放大。

常见配置如下:

python
RETRY_ENABLED = True RETRY_TIMES = 2 RETRY_HTTP_CODES = [408, 429, 500, 502, 503, 504, 522, 524] DOWNLOAD_TIMEOUT = 15 RETRY_PRIORITY_ADJUST = -1

RETRY_TIMES=2 表示失败后最多再试 2 次,不是总共请求 2 次。RETRY_PRIORITY_ADJUST=-1 会让重试请求优先级略降低,避免失败请求一直插队。429 通常代表限流,是否重试要看目标站策略;如果没有退避,只是马上重发,可能更快被封。

对业务可预期的失败,最好配合 errback 单独处理。比如详情页失败时记录 URL、来源页和错误类型,后续可以补采,而不是只靠日志里一行 traceback。

python
def start_requests(self): for url in self.urls: yield scrapy.Request(url, callback=self.parse, errback=self.on_error) def on_error(self, failure): request = failure.request self.logger.warning("request failed: %s reason=%r", request.url, failure.value) yield {"url": request.url, "status": "failed"}

如果要对限流做得更稳,可以自定义中间件读取 Retry-After,或者在调度层暂停该域名一段时间。不要把所有状态码都塞进 RETRY_HTTP_CODES,这会让 404、权限失败和反爬页面反复进入队列。失败原因先分类,再决定是否重试,是 Scrapy 错误处理里最容易被忽略的一步。

还要区分“重试”和“补采”。重试适合马上再试的短暂失败,补采适合任务结束后再单独处理的 URL,比如目标站夜间维护、接口阶段性限流、某批代理质量差。把补采 URL 写入单独队列或表,比在主任务里一直加大 RETRY_TIMES 更可控。这样主任务能按时结束,失败样本也不会丢,后续排查还能看到失败发生在哪个批次。

如果项目接入了代理池,还要把代理错误和目标站错误分开统计。代理连接失败、认证失败、目标站返回 5xx,处理方式完全不同,混在一起只会误判。一个简单做法是在代理中间件里给 request.meta 记录代理来源,失败时把来源写进日志或失败 item。这样你能判断是某个代理供应商质量差,还是目标站真的在限流。

追问

哪些错误应该重试,哪些不该重试?

临时网络问题、超时、502、503、504 一般可以重试,因为它们可能只是短暂抖动。404、410 多数表示资源不存在,通常不应该重试,否则只是浪费请求。403 要看情况:如果是登录失效、IP 被拦或权限不足,盲目重试没有意义,应该先修 cookie、代理或请求头。边界最难的是 429,它可能适合延迟后重试,也可能说明当前策略已经触发风控。

errback 和 RetryMiddleware 有什么区别?

RetryMiddleware 是框架层的自动补救,先判断失败是否符合重试条件,符合就重新入队。errback 更像业务兜底,当请求最终失败或某些异常冒出来时,你可以记录、补偿或产出失败 item。不要把所有失败都塞进 errback 手动重试,否则容易绕开 Scrapy 的统计和优先级机制。一般做法是让 RetryMiddleware 负责通用重试,errback 负责业务可观测性。

重试次数应该怎么设置?

默认思路是少量重试,通常 1 到 3 次就够了。次数太少会丢掉偶发失败,次数太多会拖慢任务,还可能让失败 URL 长时间占住队列。需要高完整率的采集可以增加补采任务,而不是在主任务里无限重试。生产环境应该看 retry/countretry/max_reached 和错误码分布,再决定是否调整。

遇到反爬导致的失败怎么办?

如果错误集中在 403、429、验证码页或异常跳转,问题通常不在重试次数,而在访问策略。可以先降低并发、开启 AutoThrottle、检查 cookie 和 header,再考虑代理或登录态维护。踩坑最多的是把 403 加进重试列表,结果同一个被拦请求重复打过去,封禁更严重。遇到这类情况,应该先暂停放量,确认目标站允许的访问边界。

怎么监控重试是否已经失控?

Scrapy stats 会记录重试次数、失败原因和最终放弃数量,这些指标比单看日志可靠。可以在扩展或任务收尾阶段读取 retry/countdownloader/response_status_count/429downloader/exception_type_count/*,超过阈值就告警。边界是告警不能只看绝对值,大任务天然错误更多,最好同时看比例。比如重试率超过 10% 或 429 持续上升,就该降速或停止任务排查。

python
def closed(self, reason): retry_count = self.crawler.stats.get_value("retry/count", 0) max_reached = self.crawler.stats.get_value("retry/max_reached", 0) self.logger.info("retry=%s max_reached=%s", retry_count, max_reached)

小结

Scrapy 错误处理的核心是分层:通用网络抖动交给 RetryMiddleware,业务失败交给 errback,异常趋势交给 stats 和告警。重试能提高完整率,但它不是反爬、权限和页面不存在的解药。先识别失败类型,再决定是否重试,爬虫才会稳定。

标签:Scrapy