requests超时必须分connect和read两阶段设置并单独捕获timeout异常,配合指数退避重试与连接池优化。例如timeout=(3.0, 10.0),分别控制dns/tcp握手与响应读取,避免混用导致调试困难。

Requests连接超时异常(requests.exceptions.Timeout)不是网络不稳定就该忍着的问题,而是必须显式控制超时参数、分层处理、并配合重试策略才能稳定跑起来。
timeout参数必须拆成connect和read两部分设
只写timeout=5看似简单,实际会掩盖连接慢和响应慢的差异。比如DNS解析卡住3秒、TCP握手又拖2秒,还没发请求就超时了;或者服务器已建立连接但迟迟不返回数据,timeout=5会把这两种情况混为一谈,调试困难。
正确做法是分开设置:
-
timeout=(3.0, 10.0):前一个值是connect超时(DNS+TCP握手),后一个是read超时(等待响应体) - connect超时建议≤3秒,超过说明目标域名/网络路径有问题
- read超时需结合业务——静态页可设2~5秒,API接口或含计算逻辑的页面建议≥8秒
- 设成
timeout=(3, None)虽能避免read超时,但可能卡死线程,不推荐
超时异常必须单独捕获,不能和ConnectionError混在一起处理
很多人用一个except requests.exceptions.RequestException:兜底,结果Timeout、ConnectionError、InvalidURL全被当成同一类错误重试,既浪费资源又可能触发反爬限流。
应该明确区分:
- 捕获
requests.exceptions.Timeout:说明网络通但响应慢,适合指数退避重试 - 捕获
requests.exceptions.ConnectionError:可能是DNS失败、目标宕机或防火墙拦截,立即重试意义不大 - 捕获
requests.exceptions.TooManyRedirects:属于逻辑异常,不是超时问题,不该进重试流程
示例片段:
try:
resp = requests.get(url, timeout=(3, 10))
except requests.exceptions.Timeout:
# 记录超时URL和耗时,按需重试
pass
except requests.exceptions.ConnectionError:
# 跳过或标记为不可达,不重试
pass
单次请求超时不是终点,得配合理性的重试机制
超时发生后直接抛错,等于放弃这个请求;但盲目无限重试,又容易被封IP。关键在“有限+退避+可观察”。
推荐方案:
- 最多重试2次,间隔从1秒开始,每次×1.5(即1s → 1.5s → 2.25s)
- 重试前检查是否为
Timeout异常,其他异常不重试 - 记录每次重试的
url、timeout值、重试次数,便于后续分析瓶颈点 - 避免用
requests.adapters.HTTPAdapter(max_retries=...)全局配置——它对Timeout无效,只对ConnectionError等生效
代理或Session复用不当会放大超时问题
用了代理却没设代理超时,或长期复用同一个Session对象,在高并发下容易因底层连接池阻塞导致看似“超时”的假象。
注意这些细节:
- 代理请求必须显式传
proxies和timeout,代理本身也可能慢,不能依赖全局timeout -
Session对象要配mount('http://', HTTPAdapter(pool_connections=10, pool_maxsize=10)),否则默认连接池太小,大量请求排队等连接 - 长时间运行的爬虫,定期
session.close()再重建,防止底层TCP连接泄漏或僵死 - 若用
urllib3.util.retry.Retry定制重试,记得排除ReadTimeoutError——它和requests.Timeout语义不同,容易误判
超时从来不是孤立的异常,它是网络、目标服务、本地配置、重试逻辑共同作用的结果。调大timeout数值只是掩耳盗铃,真正要盯住的是每一次超时发生的上下文:是connect卡住,还是read不动?重试后是否改善?同一IP下是否集中爆发?这些信息比“加个try-except”重要得多。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











