Python 爬虫网络层调优:超时、重试与连接池配置实践
做数据采集的朋友应该都经历过这种场景:代码逻辑没问题,请求头也伪装了,单次测试都能通过,但一上量就开始出现各种网络层的报错——`ConnectionTimeout`、`ReadTimeout`、`ConnectionResetError`,或者更隐蔽的:请求返回 200,但响应体是空的。 这些问题大多不是目标网站的反爬策略,而是**网络层配置没做到位**。本文从超时、重试、连接池三个角度,整理一套可落地的调优方案。 ### 一、超时设置:连接超时和读取超时是两回事 很多人在 `requests.get()` 里只写一个 `timeout=10`,以为就设了 10 秒超时。实际上,`requests` 的 `timeout` 参数如果传单个数值,会同时作用于**连接超时**和**读取超时**。但这两种超时的含义完全不同: - **连接超时**:从发起 TCP 连接到建立连接的时间。这个值应该设短一点,3-5 秒足够。如果 5 秒还连不上,说明网络出口有问题,重试比干等更有效。 - **读取超时**:连接建立后,等待服务器返回第一个字节的时间。这个值需要设长一点,尤其是流式响应或大文件下载,模型“思考”阶段可能几十秒不输出数据。 正确写法是传一个元组: ``` import requests # 连接超时 5 秒,读取超时 60 秒 response = requests.get( url, timeout=(5, 60) ) ``` 如果是流式响应,读取超时要设得更长,建议 120 秒以上。 ### 二、重试机制:别让一次抖动毁掉整个任务 网络抖动是常态,尤其是跨区域请求。没有重试机制的爬虫,稳定性完全靠运气。 `requests` 本身不提供自动重试,需要借助 `urllib3` 的 `Retry` 类: ``` import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session = requests.Session() retry = Retry( total=3, # 最多重试 3 次 backoff_factor=1, # 重试间隔:1s, 2s, 4s status_forcelist=[500, 502, 503, 504], # 这些状态码触发重试 allowed_methods=["GET", "POST"] ) adapter = HTTPAdapter(max_retries=retry) session.mount("http://", adapter) session.mount("https://", adapter) ``` 注意 `backoff_factor` 的指数退避效果:第一次重试等 1 秒,第二次等 2 秒,第三次等 4 秒。这样能避免短时间内密集重试把目标网站打挂。 ### 三、连接池:并发上不去,问题往往在这里 `requests` 底层依赖 `urllib3` 的连接池,默认每个主机最多保持 10 个连接。如果你开了 50 个线程同时请求同一个域名,多出来的线程就在等空闲连接,等不到就卡死。 解决方案是根据并发数调整连接池大小: ``` adapter = HTTPAdapter( pool_connections=20, # 连接池数量 pool_maxsize=50, # 每个池最大连接数 max_retries=3 ) session.mount("http://", adapter) session.mount("https://", adapter) ``` `pool_maxsize` 建议不小于并发线程数。如果并发是 50,就设 50 或更大。 ### 四、出口 IP 的影响:为什么同样的代码在不同环境下表现不同 以上配置都调好后,如果仍然出现大面积超时或连接重置,问题可能出在**网络出口**上。 数据中心 IP(云服务器 IP)在访问部分海外站点时,容易被目标服务器的防护策略限速或拒绝。表现为:连接能建立,但读取阶段频繁超时,或者大文件下载中途被重置。这是因为目标站点对机房 IP 段有更严格的速率限制。 此时可以尝试更换网络出口为住宅 IP。住宅 IP 的 ASN 归属于当地运营商,在目标站点的访问策略中优先级更高,连接稳定性通常有明显改善。 以下是配置住宅 IP 出口的代码示例: ``` # ============================================ # 使用住宅IP出口(示例:1024Proxy) # 官网:https://1024proxy.com/?kwd=hyj-txy # ============================================ import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session = requests.Session() # 配置住宅IP出口 proxy_url = "http://用户名:密码@gateway.example.com:端口" session.proxies.update({ "http": proxy_url, "https": proxy_url }) # 同时配置重试和连接池 retry = Retry(total=3, backoff_factor=1, status_forcelist=[500, 502, 503, 504]) adapter = HTTPAdapter(pool_connections=20, pool_maxsize=50, max_retries=retry) session.mount("http://", adapter) session.mount("https://", adapter) response = session.get(url, timeout=(5, 60)) ``` 注意代码中的 `gateway.example.com` 是占位符,实际使用时替换为服务商提供的网关地址即可。 ### 五、几个容易忽略的细节 **随机延迟比固定延迟更安全**:`time.sleep(1)` 不如 `time.sleep(random.uniform(0.5, 2.0))`。固定间隔的请求模式容易被识别为自动化流量。 **异常捕获要区分类型**:`ConnectionError`、`Timeout`、`HTTPError` 要分开处理。`Timeout` 可以重试,`HTTPError` 要看状态码决定是否重试。 **先小规模测试**:任何网络配置调整后,先用 100 个请求验证稳定性,再上量。不要一上来就跑几千条,出了问题不好定位。 ### 六、小结 爬虫的稳定性,一半靠代码逻辑,一半靠网络层配置。超时设置、重试机制、连接池大小,每一项都直接影响任务的成功率。把这些细节调好,比盲目换 IP 更有效。 如果目标是海外站点,且本地出口是云服务器 IP,可以尝试将出口切换为住宅 IP 观察效果。具体的服务商选择需要根据实际业务场景测试,建议先用少量请求验证稳定性。 --- *本文仅做技术交流,请遵守相关平台规则与法律法规。*