bigywater.tech
Scrapy · 19

RetryMiddleware:哪些响应与异常会被重试

请求失败了 Scrapy 会重试几次?哪些状态码和异常会重试?两次重试之间等多久?重试的请求排在什么位置?这篇用能随意返回状态码、断开连接、变慢的服务器把 RetryMiddleware 的每个开关实测一遍。

3 次
默认设置下一个总是 500 的请求,服务器收到的次数(原请求 + 2 次重试)
1 ms
两次重试之间的间隔(没有退避)
a b c d a a
并发 1 时 a 返回 500 的请求顺序(重试排在最后,优先级 -1)

什么会重试

实测 · 本机执行输出
  RETRY_ENABLED / RETRY_TIMES / RETRY_HTTP_CODES / RETRY_PRIORITY_ADJUST / RETRY_GIVE_UP_LOG_LEVEL = True 2 [500, 502, 503, 504, 522, 524, 408, 429] -1 ERROR
  RETRY_EXCEPTIONS = ['scrapy.exceptions.CannotResolveHostError', 'scrapy.exceptions.DownloadConnectionRefusedError', 'scrapy.exceptions.DownloadFailedError', 'scrapy.exceptions.DownloadTimeoutError', 'scrapy.exceptions.ResponseDataLossError', 'twisted.internet.error.ConnectionDone', 'twisted.internet.error.ConnectError', 'twisted.internet.error.ConnectionLost', <class 'OSError'>, 'scrapy.core.downloader.handlers.http11.TunnelError']

RETRY_HTTP_CODES 里的状态码和 RETRY_EXCEPTIONS 里的异常会触发重试;404、403、400 这类不会。

重试次数

实测 · 本机执行输出
  状态码          服务器收到 retry/count max_reached  谁被调用
  500              3           2           1  errback(HttpError)
  502              3           2           1  errback(HttpError)
  503              3           2           1  errback(HttpError)
  504              3           2           1  errback(HttpError)
  522              3           2           1  errback(HttpError)
  524              3           2           1  errback(HttpError)
  408              3           2           1  errback(HttpError)
  429              3           2           1  errback(HttpError)
  404              1           0           0  errback(HttpError)
  403              1           0           0  errback(HttpError)
  400              1           0           0  errback(HttpError)
  301              1           0           0  errback(HttpError)
  重试之间的间隔(500 的三次请求到达时间差):1 ms 1 ms(没有退避)
  stats:retry/count=2, retry/max_reached=1, retry/reason_count/500 Internal Server Error=2
  日志:[scrapy.downloadermiddlewares.retry] ERROR: Gave up retrying <GET http://127.0.0.1:9960/500> (failed 3 times):

RETRY_TIMES = 2 是重试次数,不含原请求;用尽后放弃,响应(或异常)继续交给后面的流程,通常是 HttpError 到 errback。

没有退避

实测 · 本机执行输出
  情形                        重试次数  errback 里的异常类型
  连接被拒                         2  scrapy.exceptions.DownloadConnectionRefusedError
      retry/reason_count: {'scrapy.exceptions.DownloadConnectionRefusedError': 2}
  读取超时                         2  scrapy.exceptions.DownloadTimeoutError  (服务器收到 3 次)
      retry/reason_count: {'scrapy.exceptions.DownloadTimeoutError': 2}
  收到请求后断开连接                    2  scrapy.exceptions.DownloadFailedError  (服务器收到 3 次)
      retry/reason_count: {'scrapy.exceptions.DownloadFailedError': 2}
  DNS 失败                       2  scrapy.exceptions.CannotResolveHostError
      retry/reason_count: {'scrapy.exceptions.CannotResolveHostError': 2}

重试紧接着失败发生,间隔只有 1 毫秒。需要退避就得自己写中间件或用 DOWNLOAD_DELAY / AutoThrottle。

重试请求的特性

实测 · 本机执行输出
  各请求被服务器收到的次数:default=3, dont=1, five=6, zero=1(路径里 /500?tag=… 带了 query 所以按 query 统计)

重试请求是原请求的副本:优先级 +RETRY_PRIORITY_ADJUST(默认 -1)、dont_filter = True、meta 里多了 retry_times,并回到调度器重新排队。

逐请求控制

实测 · 本机执行输出
  默认 RETRY_PRIORITY_ADJUST = -1:服务器收到的顺序 a b c d a a
  RETRY_PRIORITY_ADJUST = +10:服务器收到的顺序 a a a b c d

dont_retry、max_retry_times、priority_adjust、give_up_log_level 这些 meta 键可以覆盖设置。

回调里重试

实测 · 本机执行输出
  {'e': 'retry', 'retry_times': 0, 'new_priority': -1, 'dont_filter': True, 't': 162.4}
  {'e': 'retry', 'retry_times': 1, 'new_priority': -2, 'dont_filter': True, 't': 164.6}
  {'e': '放弃', 'retry_times': 2, 'new_priority': None, 'dont_filter': None, 't': 166.2}
  服务器收到 3 次;stats:retry/count=2, retry/max_reached=1, retry/reason_count/empty=2
  日志:[scrapy.downloadermiddlewares.retry] ERROR: Gave up retrying <GET http://127.0.0.1:9960/empty> (failed 3 times): empty

get_retry_request() 和中间件共用同一套规则与 stats,适合“200 但内容不对”的情况。

分步演示

重试
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Scrapy 2.19.0 / Twisted 26.4.0 / lxml 6.1.3 / parsel 1.12.1(临时虚拟环境);所有请求只发给本机自己起的测试服务器(127.0.0.1,端口 9960 以上,不访问任何真实网站);服务器可以按实验需要制造延迟、重定向、压缩、断连等行为,并记录收到的原始请求;每个实验在临时目录里新建 Scrapy 项目或单文件 spider,命令输出来自真实的 scrapy 进程;计时用 time.perf_counter,数字每次运行会有小幅抖动。状态码实验用只返回该状态码的服务器;断开连接用原始套接字服务器(读到请求就关闭连接);重试间隔取自服务器端到达时间;除状态码和异常实验外,顺序实验并发为 1
    • 没有验证:POST 请求的重试(请求体是否原样重发);Retry-After 头(Scrapy 不处理);代理场景下 TunnelError 的重试;重试与 HTTP 缓存的组合;stats 里 retry/reason_count 对自定义异常的命名;RETRY_EXCEPTIONS 里 OSError 的具体命中范围
    京ICP备2026067502号-1京公网安备11010502063487号