tenacity精细化重试需分层拦截:先筛connectionerror/timeout等异常类型,再判5xx响应状态,最后控指数退避节奏;漏任一层易误重试404或延迟响应503。

直接结论:tenacity 的精细化重试不靠堆参数,而靠分层拦截——先筛异常类型,再判响应状态,最后控退避节奏。漏掉任一层,都会把 404 当成可重试错误,或让 503 等太久才重试。
requests.get 失败时怎么只重试 ConnectionError 和 Timeout?
requests.adapters.HTTPAdapter 默认的重试机制对网络层失败(如 DNS 解析失败、连接被拒、超时)完全不生效,只处理 5xx 响应码。必须显式指定异常类型,否则 requests.get() 抛出的 requests.exceptions.ConnectionError 或 requests.exceptions.Timeout 会被直接冒泡,tenacity 根本捕获不到。
- 用
retry_if_exception_type((requests.exceptions.ConnectionError, requests.exceptions.Timeout)),别写成Exception或requests.RequestException——后者会把 401/403 也卷进来 -
timeout=5必须显式传给requests.get(),否则超时不会触发异常,tenacity 就没机会介入 - 装饰器必须作用在独立函数上,不能直接套在
requests.get(url)表达式上
HTTP 状态码 4xx 和 5xx 怎么差异化处理?
tenacity 默认只看异常,不看 response.status_code。如果服务返回 200 + {"code": 500} 或直接返回 503,你得自己在函数体内判断并主动 raise,才能触发重试。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- 收到
response后立刻检查:if response.status_code == 404: raise FileNotFoundError;elif response.status_code >= 500: response.raise_for_status() - 绝不能无脑调
response.raise_for_status(),它会对所有 4xx 抛HTTPError,导致 400/401/403 全部被重试 - 对 403 这类权限错误,建议
logger.warning()后直接返回None,不抛异常、不重试
如何避免重试雪崩又不让请求卡死?
固定间隔(wait_fixed(1))在高并发下容易打爆目标服务;而纯指数退避(wait_exponential(multiplier=1))第三次重试要等 4 秒,对短时抖动反应太慢。
- 推荐组合:
wait_exponential(multiplier=1, min=1, max=10)—— 首次至少等 1 秒,上限压到 10 秒防卡死 - 生产环境加
stop_after_delay(15)双保险,防止因max设置过大导致单次请求耗时失控 - 别设
multiplier=0.1试图“更快重试”,0.1s → 0.2s → 0.4s 的节奏反而容易触发目标站的频率限流
遇到 {"code": 500} 这种假 200 怎么办?
tenacity 没法自动解析 JSON 响应体,必须靠 retry_if_result 钩子手动检查返回值结构。
- 写一个判断函数:
def is_server_error(result): return isinstance(result, dict) and result.get("code") in (500, 502, 503, 504) - 装饰器里加
retry=retry_if_result(is_server_error),注意这和异常重试是“或”关系,要同时配retry_if_exception_type - 返回值为
None或空响应时,也要在钩子里覆盖判断,否则可能跳过重试
真正难的不是配置几个参数,而是想清楚:哪类失败是临时的、哪类失败该立刻放弃、哪类失败需要人工介入。tenacity 只是执行器,决策逻辑得你亲手写进函数体里——尤其是状态码和业务 code 的判断,最容易被忽略,也最影响稳定性。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










