默认 RetryMiddleware 不够用,因其仅对特定 HTTP 状态码和连接异常重试,且配置写死;无法处理 429 响应头、JSON 内错误码或动态退避等场景。

为什么默认的 RetryMiddleware 不够用?
Scrapy 默认的重试逻辑只对特定 HTTP 状态码(如 500、502、503、504、408)和连接异常触发,且重试次数、延迟、是否忽略响应内容都写死在配置里。实际爬取中常遇到:API 返回 429 Too Many Requests 但没被重试;JSON 接口返回 200 但 body 里是 {"code": 429};或需要按响应头 X-RateLimit-Remaining 动态退避——这些都无法靠默认中间件处理。
如何继承并覆盖 RetryMiddleware 的核心方法?
关键不是重写整个中间件,而是精准替换 process_response() 中的判断逻辑。Scrapy 调用链是:收到响应 → process_response() → 决定是否重试 → 调用 _retry()。你只需控制“是否重试”这个开关:
- 在
middlewares.py中定义类,继承scrapy.downloadermiddlewares.retry.RetryMiddleware - 重写
process_response(self, request, response, spider),开头先调用父类逻辑获取原始判定结果result = super().process_response(...) - 若
result是Request(即父类已决定重试),可直接返回;若为Response,再检查自定义条件,满足则手动调用self._retry(request, reason, spider) - 注意:不要漏掉
reason参数,它影响RETRY_HTTP_CODES统计和日志,建议用字符串如"custom_429_json"
示例片段:
def process_response(self, request, response, spider):
result = super().process_response(request, response, spider)
if isinstance(result, Response):
# 检查 JSON 响应体里的错误码
try:
data = json.loads(response.text)
if data.get("code") in [429, 401]:
return self._retry(request, f"json_code_{data['code']}", spider)
except (json.JSONDecodeError, KeyError):
pass
return result
如何配置启用自定义中间件并禁用默认重试?
Scrapy 中间件按 DOWNLOADER_MIDDLEWARES 字典的值排序执行,值越小越靠前。要让自定义中间件生效,必须确保默认的 RetryMiddleware 不再运行:
立即学习“Python免费学习笔记(深入)”;
- 在
settings.py中显式关闭默认中间件:DOWNLOADER_MIDDLEWARES = {'scrapy.downloadermiddlewares.retry.RetryMiddleware': None} - 再添加你的中间件,值建议设为
543(默认RetryMiddleware是543,设同值或略低即可,避免顺序错乱):'myproject.middlewares.CustomRetryMiddleware': 543 - 务必删除或注释掉
RETRY_ENABLED、RETRY_TIMES等配置——它们只对默认中间件生效,对你写的类无作用
重试时容易忽略的细节:请求指纹与去重冲突
Scrapy 默认用 request_fingerprint() 判重,而重试请求是全新构造的 Request 对象,默认会生成新指纹,导致重复请求不被过滤。如果你的业务要求“同一请求最多重试 N 次”,必须手动传递重试次数标识:
- 在
_retry()前,给request.meta加字段,如request.meta["retry_times"] = request.meta.get("retry_times", 0) + 1 - 在
process_request()或process_response()开头检查request.meta.get("retry_times", 0) >= MAX_RETRY,超限则直接return response或抛异常 - 更稳妥的做法是复写
request_fingerprint()函数(通过DUPEFILTER_CLASS配置),把retry_times也纳入指纹计算,否则可能因重试产生实际重复抓取
重试逻辑一旦脱离默认路径,指纹、调度、统计三者就容易脱节,这点在调试时最常被忽略。


















