requests 必须用 timeout=(connect, read) 元组显式设置超时,单 timeout 数值仅作用于读取阶段;连接阶段无限等待易致雪崩,需配 HTTPAdapter 重试并分捕获 ConnectTimeout/ReadTimeout 异常。

requests 默认不设超时,连接卡住几小时都不会报错——必须显式用 timeout=(connect, read) 元组控制,再配 HTTPAdapter 重试,否则爬虫或服务端调用极易雪崩。
为什么单个 timeout 数值对 requests 完全无效
传 timeout=10 只作用于读取阶段(等待响应体),连接阶段(DNS 解析、TCP 握手、TLS 协商)仍可能无限等待。常见现象是:日志静默、CPU 归零、进程无响应,但 requests.exceptions.Timeout 就是不抛——因为根本没走到读取那步。
-
requests.exceptions.ConnectTimeout:说明连 IP 都没通,优先查 DNS、代理、防火墙或目标是否被屏蔽 -
requests.exceptions.ReadTimeout:连接已建好,但服务端迟迟不发第一个字节,可能是后端卡住、限流、或页面含大量 JS 渲染逻辑 - 绝对避免
timeout=None或timeout=0:前者等于放弃防御,后者直接抛ValueError
怎样用元组形式设置合理的 timeout
连接超时和读取超时必须分开设,不能混用同一数值。典型组合:timeout=(3, 7) —— 前者防 DNS 慢/握手失败,后者保正常页面加载;若目标延迟高(如跨境 API、动态渲染页),可放宽至 (5, 15),但上限建议不超过 (10, 30)。
- 内网服务:
(1, 5) - 公网 REST API:
(3, 10)或(3, 15) - 大文件下载:
(5, 300)(注意:requests 不支持write_timeout,上传大文件建议换httpx) - 别在每次
get()都重复写 timeout,统一挂到Session上更可靠
如何正确配置 HTTPAdapter 重试策略
手动写 for 循环重试容易失控:无退避、无状态、无幂等判断。应该交由 urllib3 底层的 Retry 管理,并显式挂载到 Session。
立即学习“Python免费学习笔记(深入)”;
- 必须调用
session.mount("http://", adapter)和session.mount("https://", adapter),否则重试不生效 -
total=3是安全起点,避免对已失效域名反复消耗资源 -
backoff_factor=1启用指数退避:第 1 次重试延迟 1s,第 2 次 2s,第 3 次 4s -
status_forcelist=[429, 500, 502, 503, 504]补充服务端错误重试,别漏掉429(限流) - 记得把
allowed_methods显式设为["HEAD", "GET", "OPTIONS"],避免 POST 被意外重发
捕获 Timeout 异常时最常踩的坑
只写一个 except requests.exceptions.Timeout: 会掩盖问题根源——它无法区分是连不上还是卡在传输中。线上故障排查时,你得知道到底是网络层断了,还是服务端慢了。
- 必须拆开捕获:
ConnectTimeout和ReadTimeout分开处理,修复方向完全不同 - 别忽略
ConnectionError:它可能包裹 DNS 失败、SSL 错误、代理不可达等底层问题 - 所有异常最终应落到
RequestException兜底,但不能只靠它——精细分类才有诊断价值 - 重试 + 超时组合下,日志里要记录「第几次尝试」「用了哪个 timeout 值」「触发哪种异常」,否则复盘无从下手
真正难的不是写对这几行代码,而是理解:超时不是“多等一会儿”,而是主动放弃;重试不是“再试一次”,而是有节奏、有边界、有依据的坚持。生产环境里,timeout 和 Retry 必须成对出现,且参数得按真实链路测出来,而不是抄别人博客里的数字。


















