requests 2.34 实用手册:连接复用、超时与四个真实报错
用 Python 调一个 HTTP 接口,多数人第一反应是 requests。它把 URL 拼接、表单编码、Cookie、重定向、TLS 校验这些琐事收进一个 get() 里,脚本里写三行就能拿到 JSON。当需求变成"同时发几百个请求"或者"对接只支持 HTTP/2 的服务"时,它就不合适了:requests 是同步阻塞的,也没有 HTTP/2 支持,这两点得换 httpx 或 aiohttp。
安装与实际版本
python -m venv D:\ai\cache\temp\py-requests
D:\ai\cache\temp\py-requests\Scripts\python.exe -m pip install requests
安装到的是 2.34.2,依赖里的 urllib3 是 2.8.0:
Name: requests
Version: 2.34.2
Location: D:\ai\cache\temp\py-requests\Lib\site-packages
import requests
print(requests.__version__)
print(requests.packages.urllib3.__version__)
2.34.2
2.8.0
最小可运行示例
import requests
r = requests.get("https://example.com", timeout=10)
print("status:", r.status_code)
print("content-type:", r.headers["content-type"])
print("len(text):", len(r.text))
print("elapsed:", round(r.elapsed.total_seconds(), 3), "s")
本机跑出来的输出(走的是系统代理,所以 1.6 秒偏慢):
status: 200
content-type: text/html; charset=utf-8
len(text): 577
elapsed: 1.674 s
带上查询参数和 JSON 请求体也很直接,params 负责拼 query string,json= 负责序列化并设置 Content-Type:
r = requests.get("https://httpbin.org/get", params={"q": "kernelranch", "page": 2}, timeout=15)
print(r.url)
print(r.json()["args"])
r = requests.post("https://httpbin.org/post", json={"title": "笔记", "tags": ["python", "http"]}, timeout=15)
body = r.json()
print(body["headers"]["Content-Type"])
print(body["json"])
https://httpbin.org/get?q=kernelranch&page=2
{'page': '2', 'q': 'kernelranch'}
application/json
{'tags': ['python', 'http'], 'title': '笔记'}
注意 args 里的值都是字符串,page 传的是数字 2,服务端收到的还是 '2'。query string 本身没有类型。
常用 API 与参数
| 名称 | 作用 | 要点 |
|---|---|---|
requests.get/post/put/delete | 顶层便捷函数 | 每次调用新建一个临时 Session,连接不复用 |
requests.Session() | 持久的会话对象 | 复用 TCP 连接、保持 Cookie、可预置 headers 与 auth |
timeout= | 超时 | 单个数字同时作用于 connect 与 read;可传 (3.05, 27) 元组 |
params= | 拼 query string | 值为 None 的键会被丢掉 |
json= | 发 JSON 体 | 自动 json.dumps 并设 application/json |
data= | 发表单体 | 字典会被表单编码;传字符串则原样发送 |
headers= | 请求头 | 与 Session 级别的头合并,方法级的覆盖同名字段 |
r.raise_for_status() | 4xx/5xx 抛异常 | 不会自动调用,需要自己写 |
r.json() | 解析响应体 | 非 JSON 会抛 JSONDecodeError |
stream=True + iter_content() | 流式下载 | 不读完数据连接不会还回连接池 |
四个坑
第一个坑是不写 timeout。requests 默认没有超时,服务端不响应时进程会一直挂着,只写 requests.get(url) 的脚本在弱网下能卡几分钟。给个 2 秒超时打到 httpbin.org/delay/5,报错是:
ReadTimeout: ReadTimeout HTTPSConnectionPool(host='httpbin.org', port=443): Read timed out. (read timeout=2)
官方文档把 connect 超时和 read 超时分开解释,也提醒两者都不是墙钟时间:DNS 解析、多个地址依次重试都会让实际等待超过设定值。
第二个坑是每次调用顶层函数。requests.get() 每次都会新建连接、重新握手,在本地环回上都能测出差距。对着一个本地 ThreadingHTTPServer 发 30 次请求:
30 次 /json:Session 0.018s(0.58 ms/次)
30 次 /json:每次新建 0.352s(11.74 ms/次)
20 倍。跨机房时这个比例只会更大,因为握手要过 RTT。官方《Advanced Usage》里对 Session 的说明是它会"使用 urllib3 的连接池,对同一主机的多次请求复用底层 TCP 连接"。
第三个坑是以为状态码会自动报错。raise_for_status() 不存在"默认开启"这回事:
status_code: 503 ok: False
HTTPError: 503 Server Error: Service Unavailable for url: https://httpbin.org/status/503
r.ok 是 False,但代码不写 raise_for_status() 就接着往下走,后面 r.json() 才在别处炸掉,排查时线索就断了。
第四个坑是把 .json() 当万能解析。拿 HTML 页面调它,抛的是解析错误而不是编码错误:
JSONDecodeError: Expecting value: line 1 column 1 (char 0)
同一类问题还有 URL 少写 scheme:
MissingSchema: Invalid URL 'example.com': No scheme supplied. Perhaps you meant https://example.com?
这两个报错都很短,看到 Expecting value: line 1 column 1 就该先怀疑响应根本不是 JSON,而不是去改编码。
小结
requests 的成本主要不在发请求,而在有没有复用连接。30 次本地请求,复用 0.58ms 一次,不复用 11.74ms 一次,差 20 倍;不写 timeout 则是另一个方向的代价,从"慢"变成"挂住"。这两件事写进团队模板比事后排查便宜得多。
剩下两个坑都关于错误处理的位置:状态码要显式 raise_for_status(),响应体要显式判断是不是 JSON。把这两步放在解析之前,报错会停在离原因最近的那一行。
数据来源与检索时间
检索时间 2026-11-10。版本号与运行输出为本机实测,接口语义依据官方文档。
| 来源 | URL | 对应数据 |
|---|---|---|
| PyPI《requests》项目页 | https://pypi.org/project/requests/ | 当前版本 2.34.2;Python 3.10+;Keep-Alive、连接池、超时、流式下载等特性清单 |
| requests 官方文档《Advanced Usage》 | https://requests.readthedocs.io/en/latest/user/advanced/ | Session 复用 urllib3 连接池的说明;timeout 单值与 (connect, read) 元组语义;超时不是墙钟时间的提醒;proxies 会被环境代理覆盖的告警 |
| 本机运行记录 | D:\ai\cache\temp\py-requests | 2.34.2 / urllib3 2.8.0;example.com 与 httpbin.org 的响应;Session 与新建连接的 30 次对照;四段报错原文 |
核验说明
- 虚拟环境
D:\ai\cache\temp\py-requests,Python 3.11.9,pip install requests装到 2.34.2。 - 真跑的示例:example.com 的 GET、httpbin.org 的
params与json=POST、503 的raise_for_status()、/delay/5配 2 秒超时、HTML 上调.json()、裸域名触发MissingSchema、本地ThreadingHTTPServer上 30 次 Session 与新建连接对照。 - 本机开着系统代理(127.0.0.1:7898),
example.com的 1.674s 包含了代理转发;127.0.0.1的请求被 Windows 的 ProxyOverride<local>规则绕过,所以本地对照的数字是直连的。 - 没测的部分:重试(
urllib3.util.Retry挂HTTPAdapter)、stream=True的流式下载、客户端证书、SOCKS 代理。这几项文档里给了写法,但没有在本机验证,文章里没有给结论。