requests.get() 默认不超时,必须显式设置 timeout 参数,推荐用元组形式如 timeout=(3, 10) 分设连接与读取超时,并捕获 requests.exceptions.Timeout 异常。

requests.get() 超时参数必须显式设置
默认情况下 requests.get() 不会超时,遇到网络卡死或服务无响应时会无限等待。这不是 bug,是 requests 的设计选择——它把超时控制权完全交给你。
必须用 timeout 参数指定秒数,且推荐拆分为连接超时和读取超时:
requests.get(url, timeout=(3, 10)) # (connect_timeout, read_timeout)
-
(3, 10)表示最多等 3 秒建立 TCP 连接,连接成功后最多再等 10 秒接收响应体 - 只传单个数字如
timeout=5会同时作用于连接和读取,容易掩盖慢连接但快响应的问题 - 设为
timeout=None等同于不设,依然可能永久阻塞
捕获 requests.exceptions.Timeout 而不是通用 Exception
超时抛出的是明确的异常类型 requests.exceptions.Timeout,它继承自 requests.exceptions.RequestException,但和 ConnectionError、HTTPError 是并列关系。
错误写法是用裸 except Exception: 捕获,会吞掉本该暴露的其他问题(比如 JSON 解析失败):
立即学习“Python免费学习笔记(深入)”;
try:
resp = requests.get(url, timeout=(3, 10))
resp.raise_for_status()
except requests.exceptions.Timeout:
print("请求超时,准备重试")
except requests.exceptions.HTTPError as e:
print(f"HTTP 错误: {e}")
except requests.exceptions.ConnectionError:
print("无法连接服务器")
- 不要用
except requests.exceptions.RequestException:一锅端,会模糊超时和其他网络故障的处理策略 -
resp.raise_for_status()必须在超时捕获之后调用,否则 4xx/5xx 响应会触发HTTPError,和超时不属于同一类问题
用 tenacity 库实现带退避的重试,别手写 while 循环
手动写 while retry_count 容易漏掉状态重置、退避策略、停止条件,还难以测试。直接用 <code>tenacity 更可靠:
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type <p>@retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10), retry=retry_if_exception_type(requests.exceptions.Timeout) ) def fetch_with_retry(url): return requests.get(url, timeout=(3, 10))
-
wait_exponential让重试间隔按 1s → 2s → 4s → 8s 指数增长,避免雪崩式重试 -
retry_if_exception_type精确限定只对超时重试,连接失败或 503 错误可另设策略 - 注意
tenacity默认重试所有异常,不加retry=...会导致 HTTP 404 也被重试,这是常见误用
重试时要检查是否幂等,避免重复提交
GET 请求天然幂等,重试安全;但 POST/PUT/PATCH 如果带业务数据(如创建订单、扣款),重复发送可能造成脏数据。
解决方法不是禁用重试,而是从协议层规避:
- 对非幂等请求,服务端应支持
Idempotency-Key请求头,客户端每次生成唯一值(如 UUID) - 客户端重试时复用同一个
Idempotency-Key,服务端据此去重 - 若无法改服务端,至少在重试前加日志:
logger.warning("即将重试非幂等请求 %s", url),方便事后审计
超时本身不保证请求没发出去——TCP 可能已发出 SYN 或部分 body,只是没收到 ACK。这点常被忽略。


















