requests.get()超时后response.raw仍卡住,因底层socket处于“半打开”状态且未显式关闭;必须在except块中调用resp.raw.close()释放资源,仅resp.close()不够。

为什么 requests.get() 超时后 response.raw 仍可能卡住?
超时触发时,requests 确实会抛出 requests.exceptions.Timeout,但底层 socket 可能还处于“半打开”状态,尤其当服务器已发部分响应但未结束时。response.raw 是一个未读取完的 urllib3.response.HTTPResponse 对象,其内部 fp(文件指针)仍绑定着底层 socket。不显式关闭,该 socket 会滞留直到被 GC 回收——而这不可控,可能拖慢后续请求或耗尽连接池。
必须在 except 块里调用 response.raw.close()
仅靠 response.close() 不够,它只清理高层封装;真正释放 socket 的是 raw.close()。且必须在异常分支中做,因为超时后 response 对象虽不完整,但 raw 属性通常仍存在(除非构造过程就失败)。
常见错误写法:
try:
resp = requests.get(url, timeout=5)
# ... 处理 resp
except requests.Timeout:
pass # ❌ 忘了关 raw!
正确做法:
try:
resp = requests.get(url, timeout=5)
# ... 处理 resp
except requests.Timeout as e:
if hasattr(resp, 'raw') and resp.raw is not None:
resp.raw.close() # ✅ 显式关闭底层流
raise
- 务必检查
resp.raw是否存在且非None,某些早期requests版本在超时极早时可能未初始化raw - 不要依赖
finally块统一关——超时异常下resp可能根本没赋值成功 - 如果用了
stream=True,更需如此:此时resp.raw是唯一可操作的流对象
用 contextlib.closing() 或 with 包裹 response.raw 更稳妥
手动判断 + 关闭易遗漏;推荐把 response.raw 当作资源管理对象,用上下文协议确保释放。
示例(适用于已获取到 resp 的场景):
from contextlib import closing
<p>try:
resp = requests.get(url, timeout=5, stream=True)
with closing(resp.raw):</p><h1>在这里读取 resp.raw.read() 或 resp.iter_content()</h1><pre class="brush:php;toolbar:false;"> for chunk in resp.iter_content(8192):
process(chunk)except requests.Timeout:
即使迭代中途超时,closing 也会保证 raw.close()
pass
-
closing()是轻量包装,不改变行为,只补上__exit__调用.close() - 若你直接操作
resp.raw.read(),也应包在with closing(...)中 - 注意:
resp.raw不是原生支持with的对象(无__enter__),不能直接with resp.raw:
更彻底的方案:禁用连接池 + 控制 socket 生命周期
如果频繁遇到挂起,说明默认的 urllib3.PoolManager 连接复用机制在超时后未能及时清理底层 socket。可强制为单次请求创建独立连接:
import requests
from urllib3 import PoolManager
<h1>绕过 requests 内置连接池,每次新建连接</h1><p>session = requests.Session()
session.mount('http://', requests.adapters.HTTPAdapter(pool_connections=1, pool_maxsize=1))
session.mount('https://', requests.adapters.HTTPAdapter(pool_connections=1, pool_maxsize=1))</p><p>try:
resp = session.get(url, timeout=5, stream=True)
with closing(resp.raw):
...
except requests.Timeout:
...
</p>
- 设置
pool_maxsize=1和pool_connections=1能减少复用导致的状态残留 - 但性能代价明显,仅在高稳定性要求、低并发场景下考虑
- 优先排查是否服务端返回了不合规的 HTTP 头(如缺失
Content-Length且未分块),这会让客户端无法判断响应边界,加剧超时后挂起
真正棘手的是那些没有 Content-Length 也不分块、又不主动断连的服务——这时即使你关了 raw,socket 可能还在等下一个字节,而对方永远不会发。这种问题得从服务端或代理层解决,Python 客户端能做的只是尽量不泄漏资源。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











