被限流的时候,服务器通常会用 429 加 Retry-After 告诉你该等多久。你的爬虫是听,还是立刻重试?这篇用一个固定窗口限流服务器,量一量听与不听的差别,并弄清 urllib3 到底怎么解析这个头、会重试哪些响应。
Retry-After: '5' → 5.0 秒
Retry-After: ' 5 ' → 5.0 秒
Retry-After: '0' → 0.0 秒
Retry-After: '-3' → InvalidHeader: Invalid Retry-After header: -3
Retry-After: '1.5' → InvalidHeader: Invalid Retry-After header: 1.5
Retry-After: '3600' → 3600.0 秒
Retry-After: '86400' → 21600.0 秒
Retry-After: 'Mon, 12 Oct 2026 02:49:59 GMT' → 9.6 秒
Retry-After: 'Mon, 12 Oct 2026 02:49:39 GMT' → 0.0 秒
Retry-After: 'soon' → InvalidHeader: Invalid Retry-After header: soon
Retry-After: '' → InvalidHeader: Invalid Retry-After header:
Retry.sleep_for_retry 的实现:
def sleep_for_retry(self, response: BaseHTTPResponse) -> bool:
retry_after = self.get_retry_after(response)
if retry_after:
time.sleep(retry_after)
return True
return Falseurllib3 的 Retry.parse_retry_after 接受整数秒(允许前后空白)和 HTTP-date;小数、负数、乱写的值抛 InvalidHeader;日期早于现在是 0 秒;值超过 retry_after_max 就取上限(默认 21600 秒)。
客户端 成功 最终失败 总耗时 服务器收到 其中 429 不重试(默认 max_retries=0) 5 15 0.0 秒 20 个 15 重试,忽略 Retry-After,固定 backoff 0(立刻重试) 5 15 0.0 秒 110 个 105 重试,忽略 Retry-After,backoff_factor=0.2 20 0 2.8 秒 28 个 8 重试,遵守 Retry-After(urllib3 默认) 20 0 3.0 秒 23 个 3
每个自然秒 5 个请求的限流下,20 个请求:不重试成功 5 个;重试但立刻重来,6 次重试在同一秒里用光,服务器收到 110 个请求却仍有 15 个失败;遵守 Retry-After 全部成功,服务器只多收到 3 个 429。即使忽略头,backoff_factor=0.2 的指数等待也成功了(28 个请求),但不如直接听服务器的。
12 个请求:成功 12,最终失败 0,总耗时 1.6 秒,服务器收到 14 个请求
Retry-After 也可以写成 HTTP-date,urllib3 同样遵守,换算成距现在的秒数。
Retry().parse_retry_after("86400") = 21600 秒;Retry(retry_after_max=60).parse_retry_after("3600") = 60 秒
服务器回 429 + Retry-After: 3,retry_after_max=默认:重试后得到 200,总耗时 3.0 秒
服务器回 429 + Retry-After: 3,retry_after_max=1:重试后得到 200,总耗时 1.0 秒服务器能决定你的客户端睡多久。默认上限 6 小时,太长了:按能接受的最长等待设置 retry_after_max(演示里 3 秒的等待被压到 1 秒)。
响应 503 + Retry-After: 1,status_forcelist=[500]:最终 200,服务器收到 2 个请求,耗时 1.0 秒 响应 503(无 Retry-After),status_forcelist=[500]:最终 503,服务器收到 1 个请求,耗时 0.0 秒 响应 429 + Retry-After: 1,status_forcelist=[]:最终 200,服务器收到 2 个请求,耗时 1.0 秒 响应 500 + Retry-After: 1,status_forcelist=[]:最终 500,服务器收到 1 个请求,耗时 0.0 秒 响应 503 + Retry-After: 1,status_forcelist=[503]:最终 200,服务器收到 2 个请求,耗时 1.0 秒 响应 302 + Retry-After: 1,status_forcelist=[]:最终 302,服务器收到 1 个请求,耗时 0.0 秒
只要给 Retry 设了 total,带 Retry-After 的 413/429/503 就会被重试,不论是否在 status_forcelist 里;不带头的 503、带头的 500、302 都不会。要关掉就设 respect_retry_after_header=False(上面第二节用过)。