Python requests 官方 FAQ 详解:响应编码解码、User-Agent 自定义、SNI 证书错误与版本支持边界
本文基于 requests 官方文档的 FAQ 页面,系统讲解开发者高频遇到的五类问题:gzip/Brotli 响应自动解压与原始响应(raw response)的获取、自定义 User-Agent 的机制与底层来源、requests 与 httplib2 的取舍、Python 2/3 的支持边界,以及 “hostname doesn't match” SSL 报错背后的 SNI 原理。读完你将掌握每个问题的官方结论,并能对照仓库源码(src/requests/utils.py、src/requests/models.py)验证底层实现,把 FAQ 中“一句话结论”落到可运行的代码与配置上。
一、编码数据:gzip 自动解压与 Brotli 支持
FAQ 的第一问是 “Encoded Data?”(响应是压缩编码的怎么办?),官方结论是:
- Requests 会自动解压 gzip 编码的响应,并尽最大可能把响应内容解码为 unicode 字符串;
- 当
Brotli或brotlicffi包已安装时,requests 也能解码 Brotli 编码的响应; - 如确有需要,你可以直接访问原始响应,甚至底层的 socket。
1.1 自动解压的底层来源
自动解压能力来自 requests 底层的 urllib3 适配层。默认发送请求时,客户端会带上 Accept-Encoding 头声明自己支持压缩——可以从 src/requests/utils.py 中看到默认请求头的构造:
# src/requests/utils.py (L951-962)
def default_headers() -> CaseInsensitiveDict[str]:
return CaseInsensitiveDict(
{
"User-Agent": default_user_agent(),
"Accept-Encoding": DEFAULT_ACCEPT_ENCODING,
"Accept": "*/*",
"Connection": "keep-alive",
}
)
其中 DEFAULT_ACCEPT_ENCODING(src/requests/utils.py)由 urllib3 的 make_headers(accept_encoding=True) 生成,会声明 gzip、deflate 等压缩格式;若环境里安装了 Brotli 或 brotlicffi,解码能力随之扩展,无需修改任何业务代码。
1.2 拿到“未解码”的原始响应
FAQ 提到 “You can get direct access to the raw response (and even the socket)”(可以访问原始响应甚至 socket)。在 requests 中的对应入口是 Response.raw 属性,它返回底层 urllib3 的 HTTPResponse 对象。从 src/requests/models.py 的实现可以看到,iter_content、iter_lines 等方法在 decode_content=True 时会对数据做解码,而直接操作 self.raw 并传入 decode_content=False(如 src/requests/sessions.py 中重试读取响应体时所做的 resp.raw.read(decode_content=False))即可绕过解码,拿到服务端返回的原始字节。
结合 高级用法文档 中 “Body Content Workflow” 一节的说明:如果你需要的是服务端原样返回的字节(例如做二次转发、落盘缓存),应使用 Response.raw;如果走 stream=True + iter_content(),请注意未消费完的数据会导致连接无法归还连接池,官方建议在 with 语句中使用请求以确保连接释放。
二、自定义 User-Agent
FAQ 的第二问 “Custom User-Agents?” 的官方答案是:
Requests allows you to easily override User-Agent strings, along with any other HTTP Header.
即通过标准的 headers 参数即可覆盖 User-Agent 以及任意 HTTP 头,无需任何特殊 API:
url = 'https://api.github.com/some/endpoint'
headers = {'user-agent': 'my-app/0.0.1'}
r = requests.get(url, headers=headers)
2.1 默认 User-Agent 从哪里来
不显式指定时,默认值由 src/requests/utils.py 中的 default_user_agent() 生成:
def default_user_agent(name: str = "python-requests") -> str:
"""
Return a string representing the default user agent.
"""
return f"{name}/{__version__}"
也就是 python-requests/<版本号> 格式。当前仓库 src/requests/version.py 中的 __version__ 为 2.34.2,因此该版本默认发出的 User-Agent 即 python-requests/2.34.2。
2.2 自定义头的优先级注意事项
快速入门文档 的 “Custom Headers” 一节(FAQ 中 custom-headers 引用的正是此处)还给出了必须知道的优先级规则,自定义头并非在所有场景都最高优先:
- 通过
headers=设置的 Authorization 头,会被~/.netrc或NETRC环境变量指定的 netrc 凭据覆盖,而这些又被auth=参数覆盖; - 发生跨主机重定向时 Authorization 头会被移除;
- URL 中提供的代理凭据会覆盖 Proxy-Authorization 头;
- 当请求体长度可确定时,Content-Length 头会被重写;
- 所有头取值必须是
str、bytes或 unicode,但建议避免传 unicode 头的非 ASCII 值。
三、为什么选 requests 而不是 httplib2?
FAQ 第三问 “Why not Httplib2?” 引用了 Chris Adams 在 Hacker News 上的经典回答,其核心论点可归纳为:
- httplib2 作为一个 HTTP 客户端更“正规”,但文档不如 requests 完善,完成基础操作所需的代码量明显更多;
- 构建现代 HTTP 客户端有大量低层面的琐碎难点(认证、连接池、重定向、代理等),httplib2 试图全部覆盖,而 requests 把“简单的事情保持简单”做到位,更适合用来构建生产系统;
- 文中还举了一个具体的历史案例佐证维护响应速度:httplib2 某 issue #96 的 bug 影响面广,修复在 fork 中已被验证可用(通过数 TB 数据验证),但主干合并花了超过一年、进入 PyPI 更久——而 requests 作者的响应速度是其被推荐的重要原因。
原文还带有一句“免责声明”:Chris Adams 本人列在 requests 的 AUTHORS 文件中,但自称只能为项目的优秀贡献 0.0001%。
对读者的实际指导意义:若你在“httplib2 vs requests”之间做选型,以 requests 作为生产首选是官方 FAQ 明确背书的方向。
四、Python 版本支持边界
FAQ 用两问明确了 requests 的 Python 版本支持政策,这也是升级依赖、排查 ImportError 时最常踩的坑:
4.1 Python 3 支持
支持。 Requests 支持所有官方仍在支持周期内的 Python 版本,以及近期的 PyPy 版本。
4.2 Python 2 支持
不支持(自 2.28.0 起)。 原文档的结论:
- 从 Requests 2.28.0 开始,Requests 不再支持 Python 2.7;
- 尚未迁移的用户应将依赖锁定为
requests<2.28; - 官方强烈建议尽快迁移到受支持的 Python 3.x,因为 Python 2.7 自 2020 年 1 月 1 日起已不再获得 bug 修复和安全更新(该结论关联上游 issue #6023)。
当前仓库源码本身也印证了这一现状:src/requests/models.py 等模块使用 str | None 这类现代类型标注语法,仅能运行于 Python 3。
五、"hostname doesn't match" 错误:SNI 与证书校验
FAQ 最后一个问题是最有技术含量的 “What are 'hostname doesn't match' errors?”(什么是“主机名不匹配”错误?)。
5.1 错误产生条件
这类错误发生在 SSL 证书校验过程中:服务端返回的证书,与 Requests 认为正在联系的主机名对不上。SSL 证书校验文档 给出的典型异常形态正是:
>>> requests.get('https://requestb.in')
requests.exceptions.SSLError: hostname 'requestb.in' doesn't match either of
'*.herokuapp.com', 'herokuapp.com'
FAQ 指出:如果你确定服务端 SSL 配置是正确的(比如浏览器访问正常),且你当时还在用 Python 2.7,一个可能的解释是缺少 SNI(Server Name Indication)。
5.2 什么是 SNI,为什么它影响证书匹配
- SNI 是 SSL 的正式扩展:客户端在握手阶段就告诉服务端“我要访问哪个主机名”;
- 它在虚拟主机(Virtual Hosting)场景下至关重要:一台服务器同时托管多个 HTTPS 站点时,必须根据客户端要访问的主机名选择返回对应证书;
- 没有 SNI 时,服务器无法区分目标站点,可能返回默认证书,于是 requests 校验时就会发现“证书里的名字”和“我要访问的名字”不匹配——这就是报错的直接来源;
- Python 3 的 ssl 模块已原生支持 SNI,所以升级到 Python 3 往往直接消除此类问题。
5.3 排查与处置清单
基于 FAQ 结论与 SSL Cert Verification 文档 的配套说明,遇到该错误可以按以下顺序处理:
-
确认环境:若在 Python 2.7 上遇到且浏览器访问正常,优先怀疑 SNI 缺失 → 升级 Python 3;
-
核对 CA 信任链:Requests 默认使用
certifi包提供的 CA 证书束做校验,官方建议频繁升级 certifi,避免根证书过期导致的“看似主机名问题”的报错; -
自定义信任源:可以把
verify参数指向 CA bundle 文件或目录(目录需经 OpenSSL 的c_rehash工具处理),也可通过环境变量REQUESTS_CA_BUNDLE指定(未设置时回退到CURL_CA_BUNDLE):requests.get('https://github.com', verify='/path/to/certfile') # 或持久化到 Session s = requests.Session() s.verify = '/path/to/certfile' -
谨慎使用
verify=False:文档明确警告,将verify设为False会接受服务器出示的任何 TLS 证书、忽略主机名不匹配与证书过期,应用将暴露于中间人(MitM)攻击风险,仅建议在本地开发或测试中临时使用。
六、小结
| FAQ 问题 | 官方结论 | 源码/文档依据 |
|---|---|---|
| 编码数据 | gzip 自动解压;安装 Brotli/brotlicffi 后支持 Brotli;可经 Response.raw 访问原始响应 |
src/requests/utils.py、src/requests/sessions.py |
| 自定义 User-Agent | 直接用 headers 参数覆盖任意 HTTP 头;默认值为 python-requests/<version> |
src/requests/utils.py、docs/user/quickstart.rst |
| 为何不用 httplib2 | requests 对简单操作更简单、文档更完善、更适合生产系统 | docs/community/faq.rst |
| Python 3 支持 | 支持所有官方支持中的 Python 版本及近期 PyPy | docs/community/faq.rst |
| Python 2 支持 | 2.28.0 起不再支持,旧用户锁定 requests<2.28 |
docs/community/faq.rst |
| hostname doesn't match | 多为 Python 2.7 缺 SNI 导致;Python 3 原生支持 SNI | docs/community/faq.rst、docs/user/advanced.rst |
FAQ 看似只是零散问答,实际上勾勒出了 requests 三条核心设计线:传输层交给 urllib3/certifi 等成熟组件处理编码与证书校验,API 层只暴露最小必要参数(headers、verify、stream/raw),同时以清晰的版本支持边界管理用户预期。对照仓库源码阅读 FAQ,可以把每一条“结论式”回答都落到具体实现上,这正是官方 FAQ 与源码互相印证的价值所在。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0626
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00