
当系统中安装了 zstandard 库时,Requests 会自动在 Accept-Encoding 请求头中加入 zstd,导致服务器返回 zstd 压缩的响应体;若代码未显式解压,response.content 将呈现乱码二进制数据——本文详解其机制并提供安全、全局、无需修改业务代码的禁用方案。
当系统中安装了 `zstandard` 库时,requests 会自动在 `accept-encoding` 请求头中加入 `zstd`,导致服务器返回 zstd 压缩的响应体;若代码未显式解压,`response.content` 将呈现乱码二进制数据——本文详解其机制并提供安全、全局、无需修改业务代码的禁用方案。
Requests 库自 v2.31.0 起(2023年发布)引入了对 zstd 压缩编码的自动协商支持:只要 Python 环境中存在已安装的 zstandard 第三方库(pip install zstandard),Requests 就会在默认请求头中将 zstd 加入 Accept-Encoding 字段(顺序通常为 'gzip, deflate, br, zstd')。这一设计初衷是提升传输效率,但带来一个隐蔽的兼容性陷阱:Requests 仅负责发送 zstd 请求头,却不会自动解压 zstd 响应体——解压逻辑需开发者手动调用 zstandard 接口完成。因此,当脚本直接读取 response.content(如 requests.get(url).content)时,拿到的是未经解压的原始 zstd 字节流,表现为不可读的乱码二进制,进而导致解析失败、XML/HTML 解析器报错、CSV 写入异常等连锁问题。
✅ 根本原因定位
该行为并非 Requests 内置逻辑,而是由 urllib3(Requests 的底层 HTTP 引擎)的自动编码协商机制触发。具体路径如下:
-
requests→urllib3→ 检测可用压缩模块 → 若zstandard可导入,则启用zstd编码支持 → 自动注入请求头。
可通过以下代码快速验证是否受此影响:
import requests
print("zstandard available:", bool(__import__('zstandard', raise_on_error=False)))
print("Default Accept-Encoding:", requests.utils.default_headers()['Accept-Encoding'])
? 安全禁用 zstd 的三种推荐方式(按优先级排序)
✅ 方案一:卸载 zstandard(最简洁、最彻底)
适用于无需 zstd 压缩能力的生产环境(如 Synology DSM7 等嵌入式场景):
pip uninstall -y zstandard
⚠️ 注意:执行后需重启 Python 进程(如重启 Synology 的 Task Scheduler 中的脚本服务),确保 urllib3 重新检测模块状态。此方案零侵入、无副作用,且避免了潜在的 zstd 版本兼容性问题(如 DSM7 内置 Python 3.9 与新版 zstandard 的 ABI 冲突)。
✅ 方案二:全局禁用 zstd 编码(推荐用于多项目共享环境)
在所有 requests 调用前,一次性重置 urllib3 的编码协商白名单:
import urllib3
# 禁用 zstd —— 仅保留 gzip/deflate/br(现代主流编码)
urllib3.util.request._default_accept_encodings = ['gzip', 'deflate', 'br']
# 后续所有 requests 请求均不再发送 'zstd'
import requests
resp = requests.get("https://www.23andmedatasettlement.com/")
print(resp.text[:100]) # ✅ 正常输出 HTML 文本
? 原理:
urllib3.util.request._default_accept_encodings是控制默认Accept-Encoding值的私有列表,直接覆盖即可。此方式无需卸载包,适合需保留zstandard供其他模块(如日志压缩、离线归档)使用的场景。
✅ 方案三:会话级配置(适合精细化控制)
对特定 Session 实例统一禁用,兼顾灵活性与安全性:
import requests
session = requests.Session()
# 移除 zstd,强制使用经典编码
session.headers['Accept-Encoding'] = 'gzip, deflate, br'
# 所有通过该 session 发起的请求均生效
resp = session.get("https://example.com")
? 提示:若使用
requests.adapters.HTTPAdapter自定义连接池,也可在init_poolmanager()中注入该 header,实现更底层的统一管控。
⚠️ 不推荐的错误做法
- ❌
verify=False或stream=True:无法解决编码协商问题; - ❌
headers={'Accept-Encoding': 'identity'}:可能被服务器拒绝或降级为无压缩,且需逐处修改; - ❌ 依赖
response.text自动解码:text属性仅处理字符编码(UTF-8/GBK),不处理传输层压缩。
? 总结建议
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| Synology / NAS / 轻量服务器 | 卸载 zstandard
|
环境纯净、维护成本最低 |
| 企业内网多项目共用 Python 环境 | 全局重置 _default_accept_encodings
|
一次配置,全域生效,不影响其他功能 |
| 需要部分请求启用 zstd(如上传日志) | Session 级配置 + 按需启用 | 最大化灵活性,但需额外开发成本 |
✅ 终极提醒:Python 3.14(2025年10月发布)已将
compression.zstd纳入标准库,未来 Requests 可能原生支持自动解压。但在当前主流版本(3.9–3.13)及 DSM7 等受限环境中,主动禁用 zstd 请求头仍是稳定运行的必要实践。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











