Requests 为什么速度越来越慢?
2026/7/24 18:49:20 网站建设 项目流程

Python 生态中,Requests 凭借简洁优雅的 API 成为 HTTP 请求的首选库,几乎是所有 Python 开发者接触网络编程的第一选择。但不少人在长期使用、业务量增长后,会遇到非常直观的性能衰减:同样的接口,起初请求仅需几十毫秒,慢慢变成几百毫秒甚至数秒;单次请求尚可接受,批量调用时更是卡顿严重,耗时成倍增长。

事实上,Requests 的核心逻辑非常轻量,绝大多数性能问题并非库的底层缺陷,而是使用姿势不当、配置不合理、网络环境变化共同导致的。本文从连接管理、IO 模型、资源释放等 8 个核心维度,拆解 Requests 变慢的根因,并给出可直接落地的优化方案。

一、最容易忽略的坑:没有复用连接,每次都重新握手

1.1 TCP/TLS 握手的隐形开销

一次 HTTP 请求的总耗时里,网络握手往往占了极高比例。普通 HTTP 请求需要 TCP 三次握手,HTTPS 还要额外加上 TLS 密钥交换握手,在高延迟网络(跨地域、跨国)下,握手的耗时甚至远超接口本身的业务处理时间。

1.2 单次请求的默认行为:用完即毁

很多开发者习惯直接调用requests.get()/requests.post()发起请求,这种写法下,每一次请求都会创建全新的 TCP 连接,请求结束后立即释放连接,完全没有复用机制。单条请求的差异微乎其微,但循环调用几十上百次时,累积的握手开销会让整体速度出现肉眼可见的下降。

1.3 解决方案:用 Session 实现连接复用

Requests 提供的Session对象,本质是一个自带连接池的请求实例,会自动维护同域名的长连接。相同域名的多次请求可以直接复用已建立的 TCP/TLS 连接,省去重复握手的开销,这是性价比最高的优化手段。

python

运行

import requests # 错误写法:每次请求新建连接,握手开销重复累积 for _ in range(100): requests.get("https://example.com/api") # 正确写法:Session 复用连接,仅首次握手 session = requests.Session() for _ in range(100): session.get("https://example.com/api") session.close()

1.4 进阶:调整连接池适配高并发

Session 默认的连接池配置为pool_connections=10(最多维护 10 个不同域名的连接池)、pool_maxsize=10(单个域名最多保留 10 条连接)。如果是多线程并发请求,连接池满载后,新请求会排队等待连接释放,直接表现为请求卡顿、耗时变长。

高并发场景下可以手动放大连接池:

python

运行

from requests.adapters import HTTPAdapter session = requests.Session() # 自定义连接池大小,匹配业务并发量 adapter = HTTPAdapter(pool_connections=50, pool_maxsize=100) session.mount("http://", adapter) session.mount("https://", adapter)

二、DNS 解析的隐性延迟:解析叠加与 IPv6 回退

2.1 DNS 解析的累积耗时

Requests 本身不具备 DNS 缓存能力,完全依赖操作系统的 DNS 缓存。当系统缓存过期、或者请求大量不同域名时,每次请求都要发起完整的 DNS 查询,单次几毫秒到几十毫秒的耗时,在批量请求场景下会形成可观的性能损耗。

2.2 IPv6 优先导致的超时回退

绝大多数操作系统默认优先解析域名的 IPv6 地址,但国内很多网络环境并不具备 IPv6 连通性。客户端会先等待 IPv6 连接超时,才回退尝试 IPv4 地址,这个超时窗口通常在数百毫秒,直接让单次请求的耗时凭空增加一大截。

2.3 优化方向

  • 本地配置低延迟的公共 DNS,或搭建本地 DNS 缓存服务
  • 纯 IPv4 环境下,可在系统 hosts 文件中绑定域名与 IPv4 地址,跳过解析
  • 高频请求场景下,提前解析域名 IP,直接通过 IP 请求并手动设置 Host 请求头

三、HTTPS 场景:TLS 握手与证书验证的性能损耗

3.1 TLS 握手的算力与延迟成本

HTTPS 请求的 TLS 握手涉及非对称加密、密钥交换,算力开销远大于普通 TCP 连接。TLS 1.2 版本的完整握手需要 2 个 RTT(网络往返),跨国请求下仅握手就可能消耗数百毫秒;即使是 TLS 1.3,首次握手也需要 1 个 RTT。

3.2 证书验证的额外开销

Requests 默认开启证书验证(verify=True),会校验服务端证书的合法性、证书链完整性,部分环境下还会触发证书吊销状态检查,额外增加耗时。

3.3 优化建议

  • 必须配合 Session 使用,Session 会自动复用 TLS 会话,避免重复握手
  • 内网可信环境、测试场景下,可临时关闭证书验证verify=False(公网环境不推荐,存在中间人攻击风险)
  • 升级 Requests、urllib3 到最新稳定版,新版对 TLS 1.3 有完善支持,握手延迟可降低一半

四、大体积传输:非流式处理的内存与速度双损耗

4.1 请求端:全量加载大文件

上传大文件时,如果先把文件内容全部读入内存再发送,不仅会占用大量内存,还会增加内存拷贝、序列化的耗时,文件体积越大,性能下降越明显。

4.2 响应端:全量加载响应体

Requests 默认会把整个响应体一次性加载到内存中。当接口返回大文件、超长列表数据时,完整下载 + 内存加载的过程会让请求耗时飙升,极端情况下还会触发内存溢出,导致程序整体卡顿。

4.3 流式处理方案

python

运行

# 流式上传:直接传递文件对象,分块读取传输 with open("large_file.zip", "rb") as f: session.post("https://example.com/upload", data=f) # 流式下载:分块写入文件,不一次性加载全部响应 response = session.get("https://example.com/large_file.zip", stream=True) with open("download.zip", "wb") as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) response.close()

五、同步阻塞模型:串行请求的天然瓶颈

5.1 同步 IO 的本质缺陷

Requests 是典型的同步阻塞库,每发起一个请求,当前线程就会阻塞等待响应返回,网络 IO 等待的时间里 CPU 完全闲置。如果批量发起几十上百个请求,串行执行的总耗时是所有请求耗时的总和,请求数量越多,整体速度越慢。

5.2 优化方案:多线程并发

对于 IO 密集型的 HTTP 请求,使用线程池可以大幅提升整体吞吐,充分利用网络等待时间发起更多请求,是 Requests 场景下成本最低的并发方案。

python

运行

from concurrent.futures import ThreadPoolExecutor urls = [f"https://example.com/api/{i}" for i in range(100)] session = requests.Session() def fetch(url): return session.get(url).text # 10 线程并发,总耗时可降低至串行的 1/8 ~ 1/10 with ThreadPoolExecutor(max_workers=10) as executor: results = list(executor.map(fetch, urls))

如果是超大规模的并发请求,更推荐使用异步 HTTP 库(如 aiohttp),但 Requests 配合线程池足以覆盖绝大多数业务场景。

六、资源泄漏:连接不释放导致连接池耗尽

6.1 连接不归还的连锁反应

使用stream=True开启流式响应时,如果请求结束后不手动关闭响应对象,对应的连接不会被归还到连接池,而是一直被占用。随着请求次数增加,连接池的可用连接越来越少,新请求只能排队等待空闲连接,直观表现就是 “程序越跑越慢,请求越来越卡”。

6.2 Cookie 与请求头膨胀

长期复用同一个 Session 时,服务端返回的 Cookie 会不断累积,请求头体积越来越大。这不仅会增加传输耗时,部分服务端还会对过大的请求头做限流或延迟处理,进一步拉低请求速度。

6.3 修复方案

  • 所有流式请求都使用with上下文管理,自动关闭响应对象
  • 定期清理 Session 中的冗余 Cookie:session.cookies.clear()
  • 长生命周期的 Session 定期重建,避免连接池老化、资源碎片化

七、环境与依赖:版本、代理、系统限制的隐性影响

7.1 依赖版本过低

Requests 的底层核心依赖是 urllib3,官方会持续修复性能 bug、优化连接管理逻辑。老旧版本可能存在连接复用失效、内存泄漏等问题,直接导致性能下降。

7.2 系统代理的额外绕路

Requests 会默认读取系统的代理配置。如果环境中配置了无效、高延迟的代理,所有请求都会经过代理转发,速度会大幅下降,甚至出现偶发超时。

7.3 系统资源限制

Linux 环境下默认的文件描述符上限通常为 1024,高并发请求时连接数超过上限,会导致无法新建连接,出现卡顿、报错。

7.4 排查与修复

  • 升级 Requests 和 urllib3 到最新稳定版
  • 不需要代理时显式关闭:session.trust_env = False,禁止读取系统代理
  • 高并发场景调整系统文件描述符上限,适配连接数量

八、不要忽略服务端:限流、链路波动与接口退化

很多时候请求变慢并非客户端的问题,而是服务端或网络链路的变化:

  1. 服务端限流熔断:绝大多数接口都有频率限制,短时间内请求过多会被服务端限流,返回延迟响应、甚至直接丢包,表现为 “请求越频繁,速度越慢”。
  2. 网络链路波动:跨网链路拥塞、丢包重传、运营商路由变化,都会直接增加请求的往返耗时。
  3. 接口性能退化:服务端接口本身的业务逻辑变更、数据库压力变大,都会导致响应耗时变长。

排查时可以先用 curl、Postman 等工具测试同一接口,对比耗时,先区分是客户端问题还是服务端 / 网络问题,避免盲目优化。

优化排查的优先级建议

遇到 Requests 变慢的问题,不用盲目调整参数,按照从易到难的顺序排查即可:

  1. 优先检查是否使用了 Session 复用连接,这是性价比最高的优化
  2. 确认大文件、大数据量场景是否使用了流式处理
  3. 排查系统代理、DNS、服务端限流等环境问题
  4. 批量请求场景引入线程池并发,提升整体吞吐
  5. 最后再调整连接池参数、系统资源限制等深层配置

Requests 的性能上限远高于绝大多数业务的需求,90% 以上的慢请求问题,都源于错误的使用姿势。修正这些问题后,通常就能把请求速度提升数倍甚至数十倍。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询