首页
/ Python requests 官方 FAQ 详解:响应编码解码、User-Agent 自定义、SNI 证书错误与版本支持边界

Python requests 官方 FAQ 详解:响应编码解码、User-Agent 自定义、SNI 证书错误与版本支持边界

2026-09-05 19:31:53作者:董斯意

本文基于 requests 官方文档的 FAQ 页面,系统讲解开发者高频遇到的五类问题:gzip/Brotli 响应自动解压与原始响应(raw response)的获取、自定义 User-Agent 的机制与底层来源、requests 与 httplib2 的取舍、Python 2/3 的支持边界,以及 “hostname doesn't match” SSL 报错背后的 SNI 原理。读完你将掌握每个问题的官方结论,并能对照仓库源码(src/requests/utils.pysrc/requests/models.py)验证底层实现,把 FAQ 中“一句话结论”落到可运行的代码与配置上。

一、编码数据:gzip 自动解压与 Brotli 支持

FAQ 的第一问是 “Encoded Data?”(响应是压缩编码的怎么办?),官方结论是:

  1. Requests 会自动解压 gzip 编码的响应,并尽最大可能把响应内容解码为 unicode 字符串;
  2. Brotlibrotlicffi 包已安装时,requests 也能解码 Brotli 编码的响应;
  3. 如确有需要,你可以直接访问原始响应,甚至底层的 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_ENCODINGsrc/requests/utils.py)由 urllib3 的 make_headers(accept_encoding=True) 生成,会声明 gzip、deflate 等压缩格式;若环境里安装了 Brotlibrotlicffi,解码能力随之扩展,无需修改任何业务代码。

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_contentiter_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 头,会被 ~/.netrcNETRC 环境变量指定的 netrc 凭据覆盖,而这些又被 auth= 参数覆盖;
  • 发生跨主机重定向时 Authorization 头会被移除;
  • URL 中提供的代理凭据会覆盖 Proxy-Authorization 头;
  • 当请求体长度可确定时,Content-Length 头会被重写;
  • 所有头取值必须是 strbytes 或 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 文档 的配套说明,遇到该错误可以按以下顺序处理:

  1. 确认环境:若在 Python 2.7 上遇到且浏览器访问正常,优先怀疑 SNI 缺失 → 升级 Python 3;

  2. 核对 CA 信任链:Requests 默认使用 certifi 包提供的 CA 证书束做校验,官方建议频繁升级 certifi,避免根证书过期导致的“看似主机名问题”的报错;

  3. 自定义信任源:可以把 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'
    
  4. 谨慎使用 verify=False:文档明确警告,将 verify 设为 False 会接受服务器出示的任何 TLS 证书、忽略主机名不匹配与证书过期,应用将暴露于中间人(MitM)攻击风险,仅建议在本地开发或测试中临时使用。

六、小结

FAQ 问题 官方结论 源码/文档依据
编码数据 gzip 自动解压;安装 Brotli/brotlicffi 后支持 Brotli;可经 Response.raw 访问原始响应 src/requests/utils.pysrc/requests/sessions.py
自定义 User-Agent 直接用 headers 参数覆盖任意 HTTP 头;默认值为 python-requests/<version> src/requests/utils.pydocs/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.rstdocs/user/advanced.rst

FAQ 看似只是零散问答,实际上勾勒出了 requests 三条核心设计线:传输层交给 urllib3/certifi 等成熟组件处理编码与证书校验,API 层只暴露最小必要参数(headersverifystream/raw,同时以清晰的版本支持边界管理用户预期。对照仓库源码阅读 FAQ,可以把每一条“结论式”回答都落到具体实现上,这正是官方 FAQ 与源码互相印证的价值所在。

登录后查看全文
热门项目推荐
相关项目推荐