requests默认timeout为None易致线程卡死,须显式设为(connect_timeout, read_timeout);urllib3 Retry需开启connect/read/status并限制方法;应精细捕获异常,避免重试客户端错误;aiohttp需用ClientTimeout分项设置并手动实现带退避的异步重试。

requests 默认超时行为为什么不能直接用
requests 发起 HTTP 请求时,timeout 参数默认是 None,意味着底层 socket 会一直等下去,直到服务器响应或网络断开。这在生产环境极易导致线程卡死、连接堆积、服务雪崩。必须显式设置 timeout,且建议拆分为 (connect_timeout, read_timeout) 两个值——前者控制建立 TCP 连接的最长等待时间,后者控制从 socket 读取响应体的单次等待上限。
常见错误是只写一个数字,比如 timeout=5,它等价于 timeout=(5, 5),但实际中连接慢和响应慢的场景完全不同:DNS 解析失败、目标服务器宕机通常卡在 connect 阶段;而大文件流式返回、后端计算耗时则卡在 read 阶段。应按需分离,例如 timeout=(3, 10)。
urllib3 Retry 对象怎么配才不白配
requests 底层用 urllib3,其 Retry 类控制重试逻辑。但直接传 retry=Retry(total=3) 很可能无效——因为默认只对 status(如 502/503/504)和 connect 错误重试,而不会重试 read 超时(ReadTimeoutError)或 DNS 失败(MaxRetryError caused by NameResolutionError)。
要覆盖常见网络异常,需显式开启:
立即学习“Python免费学习笔记(深入)”;
-
connect=True:重试连接失败(含 DNS、拒绝连接等) -
read=True:重试读取超时(ReadTimeoutError) -
status=True:重试服务端返回的 5xx 响应 -
allowed_methods=frozenset(['GET', 'HEAD', 'OPTIONS']):避免对 POST 等非幂等方法盲目重试 -
backoff_factor=0.3:每次重试前按指数退避,例如第 1 次延 0.3s,第 2 次延 0.6s,第 3 次延 1.2s
完整示例:
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
retry_strategy = Retry(
total=3,
connect=True,
read=True,
status=True,
allowed_methods=frozenset(['GET', 'HEAD', 'OPTIONS']),
backoff_factor=0.3
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session = requests.Session()
session.mount("http://", adapter)
session.mount("https://", adapter)
如何识别并跳过不该重试的错误
不是所有异常都适合重试。比如 ConnectionError 中的 CertificateError(证书过期)、InvalidURL(URL 格式错误)、TooManyRedirects(重定向环),重试只会重复失败。同样,400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found 属于客户端问题,重试无意义。
推荐做法是在 except 块中精细捕获:
- 对
requests.exceptions.Timeout、requests.exceptions.ConnectionError(仅限底层网络类子类,如ConnectTimeout、ReadTimeout、NewConnectionError)做重试 - 对
requests.exceptions.HTTPError判断response.status_code // 100 == 5再决定是否重试 - 其他异常直接抛出,不吞掉也不重试
注意:requests.exceptions.RequestException 是顶层基类,直接捕获它会掩盖本该终止的错误。
异步场景下 aiohttp 的 timeout 和 retry 怎么设
aiohttp 不内置重试,timeout 必须通过 aiohttp.ClientTimeout 实例传入,且不能只设一个总时间。它有四个关键字段:total(整个请求生命周期)、connect(TCP 连接建立)、sock_read(socket 读取单块数据)、sock_connect(DNS + TCP 握手)。其中 total 和 sock_read 最常用,例如 ClientTimeout(total=15, sock_read=10)。
重试需手动实现,常见模式是用 async for 或 for 循环 + try/except,但要注意:
- 每次重试都应新建
aiohttp.ClientSession()或确保 session 未关闭 - 避免在重试循环中使用
time.sleep(),改用await asyncio.sleep() - 不要对
ClientConnectorError(如 DNS 失败)和ServerDisconnectedError盲目重试,它们可能反映下游服务已不可达
真正容易被忽略的是:aiohttp 的 timeout 在 DNS 解析阶段就生效,而某些 DNS 缓存失效或递归查询慢的环境,sock_connect 可能卡住数秒——这个时间会计入 total,导致还没发包就超时。必要时可单独调大 sock_connect。










