requests需显式检查429状态码并解析Retry-After头:若为数字则转秒数,若为HTTP日期则计算差值,无此头时fallback至1秒;禁用urllib3默认退避,避免与Retry-After叠加。

requests 怎么识别 429 并触发重试
requests 本身不自动重试 429,必须显式检查 response.status_code。常见错误是只捕获 requests.exceptions.ConnectionError 或 Timeout,却忽略 429 是 HTTP 成功响应(状态码 429 属于 4xx,但请求已送达服务器并被明确拒绝)。response.headers 中的 Retry-After 字段才是关键信号——它可能返回秒数(如 "10")或 HTTP-date(如 "Wed, 21 Oct 2024 07:28:00 GMT"),必须解析后等待。
用 urllib3 的 Retry 配合 requests 处理 429
requests 底层用 urllib3,可通过 urllib3.util.Retry 注册自定义重试逻辑。但注意:默认 Retry 不包含 429,需手动添加到 status_forcelist;且必须禁用内置的退避逻辑(backoff_factor=0),否则会叠加 Retry-After 和指数退避,导致等待时间远超预期。
- 设置
status_forcelist=(429,) - 设
backoff_factor=0,避免干扰Retry-After - 重试前手动读取
response.headers.get("Retry-After"),再调用time.sleep() - 若无
Retry-After,fallback 到固定延迟(如 1 秒),防止无限循环
自己封装一个带 429 感知的 session
比改 urllib3 更直接:继承 requests.Session,重写 send() 方法,在收到 429 后提取 Retry-After、sleep、再重发。这样能完全控制流程,也方便注入日志或监控。
import time from datetime import datetime from requests import Sessionclass Retry429Session(Session): def send(self, request, kwargs): response = super().send(request, kwargs) if response.status_code == 429: retry_after = response.headers.get("Retry-After") wait = 1.0 if retry_after and retry_after.isdigit(): wait = float(retry_after) elif retry_after: try: dt = datetime.strptime(retry_after, "%a, %d %b %Y %H:%M:%S %Z") wait = (dt - datetime.utcnow()).total_seconds() wait = max(0, wait) except ValueError: pass time.sleep(wait) return self.send(request, **kwargs) # 递归重试 return response
为什么不能只靠 requests.adapters.HTTPAdapter 的 max_retries
HTTPAdapter 的 max_retries 默认只处理连接失败、超时、重定向循环等底层异常,对 429 这类“服务器明确拒绝”的响应完全静默。即使你传入 Retry(total=3, status_forcelist=[429]),它也不会读取 Retry-After,而是直接按指数退避等待(如 1s → 2s → 4s),和 API 提供的限流策略脱节。更危险的是:某些服务在 429 响应体里还带 {"error": "rate limit exceeded"},但 Retry 不解析 body,无法做额外判断(比如是否要降级请求频率)。
真正可靠的方案,永远是把 Retry-After 解析权握在自己手里——它才是服务端给你的唯一权威调度指令。










