# curl --compressed：哪些客户端默认不请求压缩

Source: https://ipvolt.com/zh/guides/curl-compressed-accept-encoding
Markdown: https://ipvolt.com/zh/guides/curl-compressed-accept-encoding.md
Language: zh-CN

[ipvolt 首页](https://ipvolt.com/zh.md) / [指南](https://ipvolt.com/zh/guides.md) / curl --compressed：哪些客户端默认不请求压缩

集成
审校于: 2026-09-27
发布于: 2026-09-27
阅读约 20 分钟
作者： ipvolt

curl、Wget 1.x、urllib、node:http、PHP curl、Guzzle、Java HttpClient 默认不发送 Accept-Encoding 或只发 identity。附实测修复与字节检查。

除非传入 `--compressed`，否则 curl 不会发送 `Accept-Encoding` 请求头。GNU Wget 1.x 和 Python 的 `urllib.request` 发送 `Accept-Encoding: identity`，而 Node 的 `node:http`、PHP 的 ext-curl、Guzzle 和 Java 的 `java.net.http.HttpClient` 根本不发送这个请求头。这时，只在客户端请求时才压缩的服务器会返回完整的未压缩文本，而其中的每一个字节都要经过你的代理。Requests、httpx、aiohttp、Scrapy、Node 内置的 `fetch`、axios、got、Go 和浏览器已经会请求压缩：对它们来说没有什么需要打开的，最多是装一个可选的 br 解码器，在某些页面上再省下一点。

下面的每个默认行为都由 ipvolt 于 2026 年 9 月 27 日运行的本地实验记录，所用版本列在每张表格下方。结果是合成的：一个服务器和一个代理在 127.0.0.1 上提供一个本地测试页面，因此这里没有任何字节数来自公开站点或服务商账单。

## 哪些客户端默认请求压缩

### 不发送 Accept-Encoding，或只发送 identity

对于只在客户端请求时才压缩的服务器，这些客户端收到的是未压缩的文本。不发送该请求头：curl、node:http、node:https、PHP ext-curl、Guzzle、Laravel Http、Java HttpClient。`identity`：Wget、urllib、http.client。

| 客户端 | 修复 |
|---|---|
| curl | 添加 `--compressed` |
| Wget | 添加 `--compression=auto` |
| urllib、http.client | 改用 Requests 或 httpx |
| node:http、node:https | 改用 `fetch`、got 或 axios |
| PHP ext-curl | 把 `CURLOPT_ACCEPT_ENCODING` 设为 `''` |
| Guzzle、Laravel Http | 把 `decode_content` 设为 `'gzip'` |
| Java HttpClient | 发送 `Accept-Encoding: gzip`，用 `GZIPInputStream` 解压 |

实测版本：curl 8.7.1、8.22.0；Wget 1.25.0；urllib.request、http.client（Python 3.13.15、3.14.7）；node:http、node:https（Node.js 22.23.3、24.21.0、26.10.0）；PHP 8.5.8 ext-curl（libcurl 8.20.0）；Guzzle 8.2.0、Laravel Http 13.33.0；Java 25.0.4.1 HttpClient。

- curl：手动设置的 `-H 'Accept-Encoding: gzip'` 会被发送，但响应体仍保持压缩状态。
- Wget：使用 `--compression=auto` 时，它发送了 `gzip` 并解码了响应。
- urllib.request 和 http.client 按到达时的原样返回压缩的响应体。
- node:http 和 node:https 按到达时的原样返回压缩的响应体。
- PHP ext-curl：把 `CURLOPT_ACCEPT_ENCODING` 设为 `''` 时，这个 libcurl 发送了 `deflate, gzip, br, zstd`。手动设置的请求头会被发送，但不会被解码。
- Laravel Http：使用 `withOptions(['decode_content' => 'gzip'])`。
- Java HttpClient 从不解码：响应是 gzip 时，用 `GZIPInputStream` 包装响应体。

### 已经请求压缩

无需改动：这些客户端会请求压缩并解码响应。

| 客户端 | 发送的请求头 |
|---|---|
| Requests | Python 3.13.15：`gzip, deflate`；Python 3.14.7：`gzip, deflate, zstd` |
| httpx | `gzip, deflate` |
| aiohttp | Python 3.13.15：`gzip, deflate`；Python 3.14.7：`gzip, deflate, zstd` |
| Scrapy | `gzip, deflate, br, zstd` |
| Node.js 内置 fetch | Node.js 22.23.3/24.21.0：`br, gzip, deflate`；Node.js 26.10.0：`br, gzip, deflate, zstd` |
| Go net/http | `gzip` |

实测版本：Requests 2.34.2；httpx 0.28.1；aiohttp 3.14.3；Scrapy 2.19.0；内置 fetch（Node.js 22.23.3、24.21.0、26.10.0）；Go 1.27.1 net/http。

- Requests：安装 `brotli` 会加入 br；在 Python 3.13 上，安装 `backports.zstd` 会加入 zstd。
- httpx：安装 `brotli` 会加入 br，安装 `zstandard` 会加入 zstd。
- aiohttp：安装 `Brotli` 会加入 br；在 Python 3.13 上，安装 `backports.zstd` 会加入 zstd。
- 内置 fetch 请求 `http://` URL 时：`gzip, deflate`。
- Go：自己设置该请求头会关闭自动解码。

如果你的客户端已经请求压缩，用下文的字节检查确认一下；在手动添加任何请求头之前，先读一读关于复制浏览器请求头的那一节。无论用哪个客户端，对图片、视频和其他本身已经压缩过的内容，压缩都毫无用处。

每一行都来自实验运行，而不是阅读源码：共记录 567 个用例，没有一个标记为「源码阅读」。[tables.md](https://ipvolt.com/downloads/accept-encoding-defaults/tables.md) 列出了每一行背后的记录。这里的「Wget」指 GNU Wget 1.x。GNU Wget2 默认请求它编译时支持的所有编码（[wget.c](https://gitlab.com/gnuwget/wget2/-/blob/v2.3.0/src/wget.c#L4306-4331)），而 Fedora 40 及更高版本把 wget2 安装为 `wget` 命令（[Fedora 变更说明](https://fedoraproject.org/wiki/Changes/Wget2asWget)）；这一点来自源码阅读，实验没有运行 Wget2。

## curl：不传 --compressed 就不发送请求头

普通的 `curl URL` 完全不发送 `Accept-Encoding`。在 libcurl 中，[`CURLOPT_ACCEPT_ENCODING`](https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html) 默认为 NULL，这会「让 libcurl 不发送 `Accept-Encoding:` 请求头，也不自动解压收到的内容」，而 curl 命令行工具只在使用 `--compressed` 时才会改变它。实验中的两个 curl 构建都没有发送这个请求头，无论是直连、经 `http://` 代理还是经 CONNECT。[curl#11091](https://github.com/curl/curl/issues/11091) 用 `nc` 抓取 curl 7.88.1 的请求，展示了同样的情况。

`--compressed` 会请求你的构建能解码的每一种编码，然后解码响应：

- macOS 系统自带的 curl 8.7.1 只编译了 zlib，发送的是 `deflate, gzip`。
- Homebrew 的 curl 8.22.0 编译了 brotli 和 zstd，发送的是 `deflate, gzip, br, zstd`。

要知道你的 curl 会请求什么，看 `curl -V` 输出中的 `Features:` 一行：`libz` 表示 gzip 和 deflate，`brotli` 加上 br，`zstd` 加上 zstd。

有三个细节决定它是否有用：

- 它「是请求，而不是命令；服务器可能压缩数据，也可能不压缩」（[man 页面](https://curl.se/docs/manpage.html#--compressed)）。服务器忽略它时，curl 不报告任何错误，也没有让这种情况失败的开关（[curl#7516](https://github.com/curl/curl/issues/7516)），所以要按下文所示检查字节数。
- 手动设置的 `-H 'Accept-Encoding: gzip'` 会被发送，但 curl 不会解码响应。在实验中，curl 输出的是 17,750 字节的 gzip 流，而不是 100,129 字节的页面。请用 `--compressed`，而不是手动加请求头。
- `--no-compressed` 可以再把它关掉，例如 `.curlrc` 里已经有一行 `--compressed` 时。

当实验服务器发送 macOS 构建无法解码的 br 或 zstd 时，`curl --compressed` 以退出码 61 停止：

```text
curl: (61) Unrecognized content encoding type. libcurl understands deflate, gzip content encodings.
```

解压本身也有代价。man 页面警告说「即使极小的传输也可能被展开，产生巨量的字节」，并建议使用 `--max-filesize`。这个选项只有「自 8.20.0 起」才会中止因 `--compressed` 解压而膨胀的传输（[man 页面](https://curl.se/docs/manpage.html#--max-filesize)），所以它保护不了 macOS 系统自带的 curl 8.7.1。这个限制有文档记载；实验没有测试它。

不加 `--compressed` 时，如果服务器照样压缩，你拿到的就是原始 gzip 字节，就像 [curl#2836](https://github.com/curl/curl/issues/2836) 中那样；这就是常见的「二进制输出」情况，`--compressed` 可以解决。代理参数本身见[在 curl 中使用代理](/zh/guides/curl-proxy-setup)。

## Python：urllib 发送 identity；Requests、httpx、aiohttp 和 Scrapy 会压缩

- **urllib.request 和 http.client** 在 CPython 3.13.15 和 3.14.7 上发送 `Accept-Encoding: identity`。除非你自己设置，否则 http.client 会加上它，并附有注释「我们不支持 x-gzip 或 x-deflate 之类的编码」（[client.py](https://github.com/python/cpython/blob/v3.14.7/Lib/http/client.py#L1296-L1299)），而且这两个模块都不解码响应。在实验中，urllib.request 按到达时的原样返回了 gzip、br 和 zstd 响应；urllib.request 通过 http.client 发送请求，而 http.client 只以默认请求头运行过，所以关于 http.client 的这一点依据的是源码。自己设置请求头只会让你拿到需要手动解码的压缩字节，所以不如换一个客户端。必须只用标准库的脚本可以发送 `Accept-Encoding: gzip`，并在响应带有 `Content-Encoding: gzip` 时用 [`gzip.decompress()`](https://docs.python.org/3.14/library/gzip.html#gzip.decompress) 解码响应体；实验没有运行这条路线。
- **Requests 2.34.2**（搭配 urllib3 2.8.0）的默认值取自 urllib3（[utils.py](https://github.com/psf/requests/blob/v2.34.2/src/requests/utils.py#L91-L93)）：在 Python 3.13.15 上是 `gzip, deflate`，在 3.14.7 上是 `gzip, deflate, zstd`，此时 urllib3 使用标准库的 [`compression.zstd`](https://docs.python.org/3.14/library/compression.zstd.html)。安装 `brotli` 会加入 br；在 3.13 上，安装 `backports.zstd` 会加入 zstd。自 urllib3 2.6.0 起，单独的 `zstandard` 包不再能启用 zstd（[request.py](https://github.com/urllib3/urllib3/blob/2.8.0/src/urllib3/util/request.py#L22-L41)；源码阅读，未运行）。代理方面见[配置 Python Requests](/zh/guides/python-requests-proxy)。
- **单独使用 urllib3** 时，无论通过 `urllib3.request()` 还是 `PoolManager`，都不会添加压缩请求头。它的请求路径直接交给 http.client（[connection.py](https://github.com/urllib3/urllib3/blob/2.8.0/src/urllib3/connection.py#L525-L531)），按源码来看就是 `identity`。这是源码阅读的结论；实验运行的是 Requests，而不是单独的 urllib3。
- **aiohttp 3.14.3** 在 3.13.15 上发送 `gzip, deflate`，在 3.14.7 上发送 `gzip, deflate, zstd`；安装 `Brotli` 会加入 br，在 3.13 上安装 `backports.zstd` 会加入 zstd。它是这里唯一一个遇到无法解码的编码时会抛出异常的 Python 客户端；确切的消息见下文。
- **Scrapy 2.19.0** 在两个 Python 版本上都发送 `gzip, deflate, br, zstd`。自 Scrapy 2.18.0 起，brotli 和 Zstandard 支持成为必需，「因此请求的 `Accept-Encoding` 请求头中总是包含 `br` 和 `zstd`」（[发布说明](https://github.com/scrapy/scrapy/blob/2.19.0/docs/news.rst)）。

### Python 3.14 上的 httpx 与 zstd

httpx 0.28.1 在两个 Python 版本上都发送 `gzip, deflate`。装了 `brotli` 它会加入 br，而 zstd 只有装了第三方 `zstandard` 包才会加入；与 Requests 和 aiohttp 不同，它不使用 Python 3.14 标准库的 zstd（[_decoders.py](https://github.com/encode/httpx/blob/0.28.1/httpx/_decoders.py#L381-L393)）。[HTTPX 文档](https://www.python-httpx.org/)用 `pip install "httpx[brotli,zstd]"` 安装这两个解码器。压缩本身并不需要这些，因为 gzip 总是会被请求。

当服务器发送 httpx 无法解码的编码时，这一点就很重要：httpx 会返回压缩字节，并且不抛出任何异常。在没有 `zstandard` 的 Python 3.14.7 上，一个 zstd 响应以 19,298 字节的 zstd 流原样到达调用方。代理配置见 [HTTPX 异步代理指南](/zh/guides/httpx-async-proxy)。

## Node.js：内置 fetch 会压缩，node:http 不会

内置 `fetch`（undici）按 URL scheme 和版本线选择请求头（[源码，v26.10.0](https://github.com/nodejs/node/blob/v26.10.0/deps/undici/src/lib/web/fetch/index.js#L1562-L1576)；[v24.21.0](https://github.com/nodejs/node/blob/v24.21.0/deps/undici/src/lib/web/fetch/index.js#L1514-L1528)）：

- `https://` URL：在 Node.js 22.23.3 和 24.21.0 上是 `br, gzip, deflate`，在 26.10.0 上是 `br, gzip, deflate, zstd`。
- `http://` URL：三个版本都是 `gzip, deflate`。
- 任何带 `Range` 请求头的请求：三个版本都是 `identity`。

三个版本都能解码 gzip 和 br。区别在于 zstd，包括服务器未被请求就发来的 zstd：

- Node.js 22.23.3 的 `fetch` 没有 zstd 解码器，返回了压缩字节，没有报错（[源码](https://github.com/nodejs/node/blob/v22.23.3/deps/undici/src/lib/web/fetch/index.js#L2149-L2170)）。
- Node.js 24.21.0 的 `fetch` 会解码它并未请求的 zstd。一个由四个串联 zstd 帧组成的响应体只返回了第一帧：100,129 字节中的 25,033 字节，没有报错。axios 1.20.0 和 got 16.0.0 在 Node.js 22.23.3 和 24.21.0 上也只返回了第一帧。
- 在 Node.js 26.10.0 上，`fetch`、axios 和 got 都解码了全部四帧。

`node:http` 和 `node:https` 在三个版本上都不发送 `Accept-Encoding`，也从不解码；Node 的核心 HTTP 客户端不设置这个请求头（[_http_client.js](https://github.com/nodejs/node/blob/v26.10.0/lib/_http_client.js)）。Node 的 [zlib 文档](https://nodejs.org/api/zlib.html#compressing-http-requests-and-responses)展示了手动的做法：自己设置请求头，再把响应通过管道送进解压器。换用 `fetch`、got 或 axios 的代码更少。

### 内置 fetch 与 node-fetch 包

npm 包 `node-fetch` 是一个独立的客户端，有自己的默认值（[node-fetch#1556](https://github.com/node-fetch/node-fetch/discussions/1556)）。在 Node.js 24.21.0 上，node-fetch 3.3.2 发送了 `gzip, deflate, br`，node-fetch 2.7.0 发送了 `gzip,deflate`，两者都解码了响应。axios 1.20.0 发送 `gzip, compress, deflate, br`，只有设置 `transitional: { advertiseZstdAcceptEncoding: true }` 时才加入 zstd（[http.js](https://github.com/axios/axios/blob/v1.20.0/lib/adapters/http.js#L54-L57)）。got 16.0.0 在三个版本上都发送了 `gzip, deflate, br, zstd`（[index.ts](https://github.com/sindresorhus/got/blob/v16.0.0/source/core/index.ts#L2248-L2258)）。它们都不需要改动。内置 `fetch` 的代理 dispatcher 见[在 Node.js fetch 中使用代理](/zh/guides/nodejs-fetch-proxy)。

## Go、Wget、PHP 和 Java

### Go net/http 只请求 gzip

Go 1.27.1 发送 `Accept-Encoding: gzip`，解码响应，并从交给你的响应中移除 `Content-Encoding` 和 `Content-Length`（[transport.go](https://github.com/golang/go/blob/go1.27.1/src/net/http/transport.go#L199-L207)）。对于 `HEAD` 请求、带 `Range` 请求头的请求，或设置了 `DisableCompression: true` 时，它不发送这个请求头。由此有两个后果：

- 自己设置 `Accept-Encoding` 会关闭解码：「如果用户显式请求了 gzip，它就不会被自动解压」。使用 `req.Header.Set("Accept-Encoding", "gzip")` 时，实验中的 Go 客户端收到的是 17,750 字节的 gzip 流。
- net/http 没有 br 或 zstd 解码器。服务器未被请求就发来这两种编码时，客户端拿到的是原始字节。

### GNU Wget 1.x 发送 identity

Wget 1.25.0 发送 `Accept-Encoding: identity`。关于 `--compression`，手册对「none」的说明是「这是默认值」（[手册](https://www.gnu.org/software/wget/manual/wget.html#HTTP-Options)）；同一页面的 `.wgetrc` 列表仍把 `auto` 称为默认值，但实验运行的结果与 `none` 一致。使用 `--compression=auto` 时，Wget 发送了 `gzip` 并解码 gzip，仅此而已：未被请求就发来的 br 或 zstd 以原始形式到达。如上所述，Wget2 的行为不同。

### PHP：Guzzle、Laravel Http 和 ext-curl 不发送 Accept-Encoding

- **PHP ext-curl**（PHP 8.5.8，搭配 libcurl 8.20.0）什么都不发送，因为 `CURLOPT_ACCEPT_ENCODING`「默认为 null」（[PHP 手册](https://www.php.net/manual/en/curl.constants.php)）。`CURLOPT_ACCEPT_ENCODING => ''` 会请求所链接的 libcurl 支持的每一种编码（在这个构建中是 `deflate, gzip, br, zstd`），并进行解码。写在 `CURLOPT_HTTPHEADER` 中的 `Accept-Encoding` 行会被发送，但不会被解码。
- **Guzzle 8.2.0** 是有意去掉这个请求头的。它的 cURL handler 打开所有解码器，然后加上一行空的 `Accept-Encoding:` 阻止 curl 发送该请求头，注释说这「会被解读为 'Accept-Encoding: *'」（[CurlFactory.php](https://github.com/guzzle/guzzle/blob/8.2.0/src/Handler/CurlFactory.php#L2609-L2622)）。从 6.5.0 到 8.2.0 检查过的每个标签里都是同样的代码（源码阅读）。给 [`decode_content`](https://docs.guzzlephp.org/en/stable/request-options.html#decode-content) 传一个字符串，例如 `'decode_content' => 'gzip'`，Guzzle 就会把它作为请求头发送（[Client.php](https://github.com/guzzle/guzzle/blob/8.2.0/src/Client.php#L1582-L1586)）；在 cURL handler 下，`'gzip, deflate, br, zstd'` 也可以。默认情况下，cURL handler 仍会解码服务器未被请求就发来的 gzip、br 或 zstd。Guzzle 的 `StreamHandler` 使用 PHP 流而不是 ext-curl，它解码了 gzip，但对 br 和 zstd 返回原始字节。
- **Laravel Http**（illuminate/http 13.33.0）构建的是一个普通的 Guzzle 客户端，并继承了上述全部行为（[PendingRequest.php](https://github.com/laravel/framework/blob/v13.33.0/src/Illuminate/Http/Client/PendingRequest.php)）。`withOptions(['decode_content' => 'gzip'])` 和 `withHeaders(['Accept-Encoding' => 'gzip'])` 都发送了 `gzip` 并解码了响应。

### Java HttpClient 从不解码

Temurin 25.0.4.1 上的 `java.net.http.HttpClient` 没有发送 `Accept-Encoding`，也没有解码服务器未被请求就发来的 gzip 响应。要获得压缩，就自己发送 `Accept-Encoding: gzip`，并在响应带有 `Content-Encoding: gzip` 时通过 `java.util.zip.GZIPInputStream` 读取响应体，实验中的 [Java 客户端](https://ipvolt.com/downloads/accept-encoding-defaults/harness/clients/php-java/JavaHttpClientLab.java)就是这样做的。没有运行其他 Java 客户端。

## Accept-Encoding：identity、不发送请求头与乱码输出

[RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-accept-encoding) 区分了请求的三种状态：

- **不发送请求头**（curl、node:http、ext-curl、Guzzle、Java）：「用户代理可以接受任何内容编码」。
- **`identity`**（urllib、Wget 1.x）：「『不编码』的同义词」。
- **空值**：「用户代理不希望响应使用任何内容编码」。

所以标准允许服务器在请求不带该请求头时压缩响应，「只在被请求时才压缩」是服务器的惯例，不是 RFC 的规定。服务器和 CDN 的文档描述了这种惯例。Cloudflare 会根据请求中 `accept-encoding` 请求头的值、套餐以及任何匹配的压缩规则，选择 gzip、Brotli、Zstandard 或不压缩（[Cloudflare](https://developers.cloudflare.com/speed/optimization/content/compression/)，页面更新于 2026 年 4 月 17 日）。Apache 的 mod_deflate 发送 `Vary: Accept-Encoding`，以免压缩内容被「发送给无法理解它的客户端」（[mod_deflate](https://httpd.apache.org/docs/2.4/mod/mod_deflate.html)）。Guzzle 的注释依据的是 RFC 的解读；而对于遵循上述惯例的服务器，缺少该请求头就会得到未压缩的响应体。实验服务器也遵循同样的惯例。实际服务器在未被请求时压缩的频率没有测量。

确实有服务器这样做。nginx 的 `gzip_static always` 会「不检查客户端是否支持」就提供 gzip 压缩过的文件（[nginx](https://nginx.org/en/docs/http/ngx_http_gzip_static_module.html)），curl#2836 也报告了一个这样做的真实主机。当实验服务器在未被请求时发送 gzip，不加 `--compressed` 的 curl、Wget、urllib、`node:http`、PHP ext-curl、Java，以及设置了 `DisableCompression` 的 Go，都把 17,750 字节的 gzip 流交给了调用方；Guzzle 和 Laravel Http 则解码了它。如果这些客户端中某一个的响应看起来像二进制乱码，先检查它的 `Content-Encoding`。

## 在你自己的目标站点上验证字节数

如果这里没有适用于你的客户端的字节计数片段，可通过同一代理对同一 URL 运行下面的 curl 对比，并将它的 `Content-Encoding` 与你的客户端收到的响应头进行比较。这测量的是 curl 的响应；如果编码或服务器响应不同，就不能把它当作你的客户端的字节测量。

下面的 curl、Requests、httpx 和 `node-fetch-bytes.mjs` 代码片段打印的是响应体传输字节：响应体穿过网络时的原样字节，未经任何解码。响应头、TLS 记录、CONNECT 交换和重试都不包括在内，所以这不是服务商计费的数量。在八组实验对照中（[tables.md](https://ipvolt.com/downloads/accept-encoding-defaults/tables.md) 列出了它们），同一个客户端经 CONNECT 分别在无压缩和有压缩的情况下获取测试页面，这些额外开销在代理到客户端这一段上每个响应增加了 2,343 到 3,520 字节。它们降低了压缩比，例如 curl 8.22.0 从响应体上的 5.19× 降到代理段上的 4.56×，但没有降低节省量：在每一组对照中，代理段减少的字节数都等于响应体的差值再加 87 字节。要计量经过代理的一整次运行并按你自己的费率换算，请使用 [Scrapescope](https://ipvolt.com/guides/scraper-bandwidth-report)。

Go 片段打印解码后的大小和 `resp.Uncompressed`（它表明 Go 是否解码了响应），链接中的 `node-fetch-verify.mjs` 打印 `fetch` 发送的请求头；两者都不打印传输字节。每个片段在实验中都原样运行，只是把 `https://example.com/` 换成了回环 URL，文中展示的输出就来自这些运行；Go 片段还与一个信任实验 CA 的实验文件一起编译，方法一节中有说明。把 `PROXY_URL` 设为你的代理。如果需要信任私有 CA，请使用客户端自己的设置，例如 `--cacert`、`NODE_EXTRA_CA_CERTS` 或 Go 的 `tls.Config.RootCAs`；永远不要关闭证书验证。

### 用 curl 验证

```sh
# Wire body bytes and the Content-Encoding the server chose (curl 7.84.0+ for %header{}).
curl -sS -o /dev/null -x "$PROXY_URL" \
  -w '%{size_download} %header{content-encoding}\n' https://example.com/
curl -sS -o /dev/null -x "$PROXY_URL" --compressed \
  -w '%{size_download} %header{content-encoding}\n' https://example.com/
```

用 Homebrew curl 8.22.0 请求实验的测试页面时，第一行打印 `100129` 且没有编码，第二行打印 `19298 zstd`；macOS 系统自带的 curl 8.7.1 在第二行打印的是 `17750 gzip`。curl 的 `size_download` 是「传输的响应体/数据的大小，不包括头部」，而 `%header{}` 需要 curl 7.84.0 或更高版本（[write-out](https://curl.se/docs/manpage.html#-w)）。这个页面的压缩比就是第一个数字除以第二个数字。

### 用 Requests 和 httpx 验证

```python
import os

import requests

proxies = {"http": os.environ["PROXY_URL"], "https": os.environ["PROXY_URL"]}
with requests.get("https://example.com/", proxies=proxies, stream=True) as r:
    wire = r.raw.read(decode_content=False)  # body bytes as sent, still encoded
    print(r.headers.get("content-encoding"), len(wire))
```

Requests 在 Python 3.13.15 上打印 `gzip 17750`，在 3.14.7 上打印 `zstd 19298`。对于 httpx，`r.num_bytes_downloaded` 统计读取时的原始字节，`len(r.content)` 统计解码后的响应体：

```python
import os

import httpx

with httpx.Client(proxy=os.environ["PROXY_URL"]) as client:
    r = client.get("https://example.com/")
    # wire body bytes (as received) vs decoded bytes your code sees
    print(r.headers.get("content-encoding"), r.num_bytes_downloaded, len(r.content))
```

它在 Python 3.14.7 上打印 `gzip 17750 100129`，装了 `zstandard` 后打印 `zstd 19298 100129`。urllib 从不解码，所以从它读到的内容长度就已经是响应体的传输字节数。

### 用 Node.js 验证

对于内置 `fetch`，Node 为该 URL 生成的 resource timing 条目给出了响应体的传输大小：

```js
// Wire body bytes of a built-in fetch, from Node's resource timing entry for the URL.
// Run: NODE_USE_ENV_PROXY=1 HTTPS_PROXY="$PROXY_URL" node node-fetch-bytes.mjs
import { setImmediate } from 'node:timers/promises';

const url = new URL('https://example.com/');
const res = await fetch(url);
const body = await res.arrayBuffer(); // already decoded by fetch

// Node adds the entry after the body has been read, so give the event loop a turn.
let entry;
for (let turn = 0; !entry && turn < 100; turn++) {
  await setImmediate();
  entry = performance.getEntriesByType('resource').findLast((e) => e.name === url.href);
}
performance.clearResourceTimings(); // the buffer stops at 250 entries unless you clear it

// encodedBodySize: the body as it crossed the network, before decoding (no headers, TLS or CONNECT)
console.log('content-encoding', res.headers.get('content-encoding'),
  'wire body bytes', entry?.encodedBodySize, 'decoded bytes', body.byteLength);
```

经过实验的 CONNECT 代理，它在 Node.js 22.23.3 和 24.21.0 上打印 `content-encoding br wire body bytes 18451 decoded bytes 100129`，在 26.10.0 上打印 `content-encoding zstd wire body bytes 19298 decoded bytes 100129`，每一次都与实验服务器发送的内容一致。在这三个版本上另外 18 个实验用例中（直连 `http://`、直连 `https://` 和经 CONNECT 代理，每种都分无压缩和有压缩），`encodedBodySize` 每次都等于服务器发送的响应体字节数；[tables.md](https://ipvolt.com/downloads/accept-encoding-defaults/tables.md) 列出了这些读数。读完响应体后，该条目从来不会立即出现，只有在一次事件循环轮转之后才出现，这就是片段要等待的原因。片段随后清除这些条目，因为 Node 的 resource timing 缓冲区默认只保存 250 条（[observe.js](https://github.com/nodejs/node/blob/v26.10.0/lib/internal/perf/observe.js#L103)）。不要改用 `transferSize`：它在每个用例中都是 `encodedBodySize` 加上固定的 300 字节，而经过代理时，客户端这一段实际比响应体多传输了 3,431 到 3,520 字节。

[node-fetch-verify.mjs](https://ipvolt.com/downloads/accept-encoding-defaults/harness/snippets/node-fetch-verify.mjs) 打印 `fetch` 发送的请求头（在 Node.js 26.10.0 上是 `sent accept-encoding: br, gzip, deflate, zstd`）。对于从不解码的 `node:https`，[node-http-verify.mjs](https://ipvolt.com/downloads/accept-encoding-defaults/harness/snippets/node-http-verify.mjs) 统计到达时的响应体，这就是响应体的传输字节。

### 用 Go 验证

```go
// Did Go ask for gzip and decode it for you? resp.Uncompressed says so.
// Run: go run go-verify.go   (with PROXY_URL set in the environment)
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
)

func main() {
	proxy, err := url.Parse(os.Getenv("PROXY_URL"))
	if err != nil {
		panic(err)
	}
	t := http.DefaultTransport.(*http.Transport).Clone()
	t.Proxy = http.ProxyURL(proxy)
	resp, err := (&http.Client{Transport: t}).Get("https://example.com/")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	// true: Go sent "Accept-Encoding: gzip" itself, received gzip and decoded it,
	// then removed Content-Encoding and set ContentLength to -1.
	fmt.Printf("uncompressed=%v content-encoding=%q content-length=%d decoded-bytes=%d\n",
		resp.Uncompressed, resp.Header.Get("Content-Encoding"), resp.ContentLength, len(body))
}
```

在 Go 1.27.1 上它打印 `uncompressed=true content-encoding="" content-length=-1 decoded-bytes=100129`。`resp.Uncompressed` 只表明 Go 解码了响应（[response.go](https://github.com/golang/go/blob/go1.27.1/src/net/http/response.go#L93-L100)）；响应中已不再带有它的传输大小。要统计传输字节，就自己设置请求头（这样响应体会保持压缩状态），测量它，再用 `compress/gzip` 解码。

## 经过代理：CONNECT 与绝对形式

对于 `https://` URL，客户端打开一条 CONNECT 隧道，并在 TLS 内部发送请求。使用端到端 TLS 且没有 TLS 拦截时，代理无法读取或修改 `Accept-Encoding`，只能转发加密的响应。实验记录器没有解析 CONNECT 隧道内部的请求头。对于 `http://` URL，大多数客户端以绝对形式把请求本身发给代理，例如 `GET http://host/path`，因此代理能读到每一个请求头：实验代理看到了 curl 8.22.0 带 `--compressed` 时的 `deflate, gzip, br, zstd`、Wget 的 `identity` 和 Go 的 `gzip`。这条路径上的中间设备也可能改写请求头或响应体。实验代理两者都没有做，也没有测试任何真实代理。

有两个客户端对 `http://` URL 的处理不同：

- Node.js 22.23.3 和 24.21.0 的内置 `fetch` 在设置 `NODE_USE_ENV_PROXY=1` 时，把 `http://` URL 也通过 CONNECT 隧道发送。实验记录器没有解析内部请求头，但该 HTTP 请求仍是代理可以检查的明文：[CONNECT 本身不加密流量](https://www.rfc-editor.org/rfc/rfc9110.html#name-connect)。Node.js 26.10.0 发送的是绝对形式。
- 除非设置了 `request_fulluri`，否则 Guzzle 的 `StreamHandler` 会向代理发送源形式（origin-form）的请求行，实验代理以 400 拒绝了它。

哪些流量计入账单，由各服务商自己规定。例如，Decodo 的文档把下载流量定义为「从目标网站收到的每一个字节，包括响应头和 cookie」，并说明对于 HTTPS，「通过代理隧道传输的字节也计入代理流量用量」，还给出一个例子：它自己的测试端点经 `http://` 下载了 0.85 kB，而经 `https://` 下载了 3.96 kB（[Decodo 帮助文档](https://help.decodo.com/docs/proxy-traffic)，2026 年 9 月 26 日读取，页面修改于 2026 年 4 月 23 日）。Decodo 没有说明它统计的是压缩后的字节还是解码后的字节。对于在 CONNECT 内使用端到端 TLS 的 HTTPS，代理统计的是穿过隧道的加密字节，其中包括压缩的响应和 TLS 开销；这是推断，不是 Decodo 的说法。替你向目标站点发送请求的抓取 API 和解封服务会自行选择请求头、按自己的方式计量，所以请查阅你的服务商的文档。

## 复制浏览器的 Accept-Encoding

你可能想把浏览器的 `Accept-Encoding: gzip, deflate, br, zstd` 直接粘贴到爬虫里。这样做会向服务器请求你的客户端可能无法解码的编码，而且在好几个客户端中，手动设置请求头还会关闭解码。使用正是这个请求头时，实验服务器选择了 zstd：

- curl（两个构建）、urllib、`node:http`、Go、PHP ext-curl、Java `HttpClient` 和 Guzzle 的 `StreamHandler` 返回的是压缩字节：响应一切正常，响应体却无法阅读。
- 只要缺少对应的解码器，Requests 和 httpx 也一样。在 Python 3.13 上，未装额外包的 Requests 对 zstd 和 br 都返回原始字节。在 3.14 上它能解码 zstd，但服务器发送 br 时返回原始的 br 字节。没有 `zstandard` 的 httpx 在两个 Python 版本上都返回原始的 zstd 字节，且不抛出异常。
- Node.js 22.23.3 的 `fetch` 返回原始的 zstd 字节；24.21.0 和 26.10.0 解码了它。
- Guzzle 的 cURL handler 解码了 zstd 和 br。
- aiohttp 只要缺少解码器就抛出异常：没有 `Brotli` 时遇到 br，以及在 Python 3.13 上没有 `backports.zstd` 时遇到 zstd。在 3.14 上它解码了 zstd。

解决办法是让客户端自己写这个请求头。如果你想要 br 或 zstd，就安装你的客户端所用的解码器包，例如 `brotli`、Python 3.13 上的 `backports.zstd` 或 `httpx[brotli,zstd]`；客户端随后会自己加上相应的编码标记。

### aiohttp 3.14.3：`` Can not decode content-encoding: brotli (br). Please install `Brotli` ``

当服务器发送 br 而缺少 `Brotli` 包时，aiohttp 3.14.3 会抛出下面的第一个错误，在 Python 3.13.15 和 3.14.7 上都一样。第二个是 zstd 的版本，在没有 `backports.zstd` 的 Python 3.13.15 上抛出（回环 URL 已缩写）：

```text
aiohttp.client_exceptions.ClientResponseError: 400, message='Can not decode content-encoding: brotli (br). Please install `Brotli`', url='…'
aiohttp.client_exceptions.ClientResponseError: 400, message='Can not decode content-encoding: zstandard (zstd). Please install `backports.zstd`', url='…'
```

400 是 aiohttp 为无法解码的响应自行设定的状态码；实验服务器实际应答的是 200（[http_parser.py](https://github.com/aio-libs/aiohttp/blob/v3.14.3/aiohttp/http_parser.py#L1147-L1159)）。在实验中，安装 `Brotli`（第二个错误则安装 `backports.zstd`）解决了问题：aiohttp 随后解码了 br，也解码了 zstd，包括由四个 zstd 帧组成的响应体。更短的 `Can not decode content-encoding: br` 来自同一文件中的另一条路径：解码器已经安装，但响应体解压失败。

### 无法正确处理 zstd 声明的服务器

声明支持某种编码，本身就可能让请求出问题。当请求的 `Accept-Encoding` 包含 zstd 时（支持 zstd 的 curl 构建就会这样），OpenSearch 2.19.0 会挂起。这个问题已在 2.19.1 和 3.0.0 中修复（[OpenSearch#17339](https://github.com/opensearch-project/OpenSearch/issues/17339)）。如果某个主机只对支持 zstd 的客户端超时，发一个只请求 gzip 的请求，就能看出问题是否出在 zstd 协商上。

## 压缩帮不上忙的情况

- **本身已经压缩过的内容**，例如图片、视频和压缩包。「图片之类已经压缩过的媒体文件无法从 HTTP 压缩中获益」（[Web Almanac 2021](https://almanac.httparchive.org/en/2021/compression)）。
- **服务器或 CDN 不压缩的响应。** 例如，Cloudflare 的文档说明它只压缩 200、403 和 404 响应，gzip 的最小大小为 48 字节，Brotli 和 Zstandard 为 50 字节（[Cloudflare](https://developers.cloudflare.com/speed/optimization/content/compression/)）。
- **Range 和 HEAD 请求。** Go 对这两种请求都不发送该请求头，而 Node.js `fetch` 只要设置了 `Range` 请求头就发送 `identity`。
- **上传。** `--compressed` 及其等价选项作用于下载；对于请求体，「没有标准的压缩方式」（[Everything curl](https://everything.curl.dev/http/modify/compression.html)）。
- **浏览器自动化。** 浏览器会自己协商压缩：MDN 给出的典型浏览器取值是 `gzip, deflate, br, zstd`（[MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Encoding)），Chrome 自 123 版起默认解码 zstd（[Chrome Platform Status](https://chromestatus.com/feature/6186023867908096)），所以在 Playwright 或 Puppeteer 中，要削减的字节在浏览器下载的内容里，而不在这个请求头上。

对于反复抓取的页面，条件请求是另一个手段：`304 Not Modified` 响应完全不带响应体（[RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-304-not-modified)；[ETag 监控](/zh/blog/etag-monitoring-cache-control)）。

## 能省多少

只有第一张表中的客户端有压缩可以打开。对它们来说：

节省的 $ ≈ 页面数 × 每页未压缩文本字节数 × (1 − 1/压缩比) ÷ 10⁹ × 你每 GB 支付的 $

压缩比取自你在自己页面上做的验证步骤，即无压缩时的字节数除以有压缩时的字节数；费率取自你自己的套餐。

只有按字节付费时，压缩才能降低账单。在按 Mbps 或线程数定价的固定套餐上，价格不变，不过同一通道能传输更多压缩后的页面。[不限量住宅代理折合每 GB 多少钱](/zh/blog/unlimited-residential-proxies-cost-per-gb)比较了这两种计费方式。

一个虚构的例子，不是测量结果：100,000 个 100 KB（100,000 字节）的 HTML 页面，未压缩时共 10 GB。压缩比为 4 时，压缩可节省 7.5 GB；压缩比为 10 时，可节省 9.0 GB。按虚构的每 GB $3 到 $8 计算，抓取这些页面一次大约可省 $22.50 到 $72。

对于已经请求压缩的客户端，打开压缩省不了任何东西，因为它本来就开着。为 Requests、httpx 或 aiohttp 装一个可选的 br 解码器，可以在某些页面上再省一点：在实验的 23 个文档页面上，br 响应体合计 277,665 字节，gzip 为 325,873 字节，约少 15%；但在合成测试页面上，br 比 gzip 更大（18,451 字节对 17,750 字节）。

实验自身的压缩比不是预测。合成测试页面用 gzip 时的压缩比为 5.64×，用 zstd 时为 5.19×。在这个由 23 个 CPython 3.14.7 文档页面组成的语料中，未压缩的响应体是其 gzip 大小的 6.67×、zstd 大小的 6.61×、br 大小的 7.83×。这些数字只描述这些文件。

公式统计的是响应体字节；在实验中，响应头、TLS 和 CONNECT 交换几乎不随压缩而变化，验证一节已经说明了这一点。服务商如何把字节换算成账单（GB 还是 GiB、最低消费、失败的请求）见 [Scrapescope](https://ipvolt.com/guides/scraper-bandwidth-report)。结果是估算，不是服务商账单。

## 方法、限制与下载

ipvolt 于 2026 年 9 月 27 日在 macOS 15.7.4（Apple M4 Pro，arm64）上把记录下来的 567 个用例的矩阵运行了一次：02:43 到 02:44 UTC 为准备阶段，随后 02:44 到 02:45 UTC 运行整个矩阵。127.0.0.1 上的一个 Python 服务器提供一个 100,129 字节的合成 HTML 页面，部分用例则使用 CPython 3.14.7 文档的 23 个页面，服务器前面是 127.0.0.1 上一个会计数的正向代理，同时处理绝对形式和 CONNECT 请求。没有该请求头时，服务器发送未压缩的页面；否则它从客户端列出的编码中挑选，优先 zstd，其次是 br、gzip 和 deflate。强制模式则不管请求如何，发送 gzip、br、zstd、四个 zstd 帧或不压缩的内容。每个 HTTPS 用例都通过客户端自己的设置信任一个一次性的本地 CA，21 个不带该 CA 的对照用例全部未通过证书验证。Go 片段与一个实验文件一起编译，该文件在片段克隆 `http.DefaultTransport` 之前把这个 CA 放入它的 `RootCAs`（其文本是 [snippets.py](https://ipvolt.com/downloads/accept-encoding-defaults/harness/nodego/snippets.py) 中的 `LAB_TRUST_GO`），因此信任不依赖于 `SSL_CERT_FILE`：macOS 上的 go1.27.1 会遵循这个变量，除非设置了 `GODEBUG=x509sslcertoverrideplatform=0`，而在声明 go 1.26 的模块中默认就是这样设置的。从已发布的归档出发、在空目录中重新获取所有下载的第二次运行，在每个比较字段上都与全部 567 条记录一致（`compare.py --strict`）。

版本：curl 8.7.1（macOS，LibreSSL）和 8.22.0（Homebrew，OpenSSL 3.6.4，brotli，zstd）；GNU Wget 1.25.0；CPython 3.13.15 和 3.14.7，搭配 Requests 2.34.2（urllib3 2.8.0）、httpx 0.28.1、aiohttp 3.14.3 和 Scrapy 2.19.0；来自 nodejs.org 并已核对校验和的 Node.js 22.23.3、24.21.0 和 26.10.0，搭配 axios 1.20.0、got 16.0.0 以及 node-fetch 3.3.2 和 2.7.0；Go 1.27.1；PHP 8.5.8（静态构建，libcurl 8.20.0），搭配 Guzzle 8.2.0 和 illuminate/http 13.33.0；Temurin JDK 25.0.4.1。

实验没有覆盖 HTTP/2 或 HTTP/3（服务器只提供 HTTP/1.1）、Linux 或 Windows 构建、浏览器、Wget2、单独使用的 urllib3、curl_cffi、Apache HttpClient、OkHttp、Symfony HttpClient、.NET HttpClient、Rust reqwest、Ruby Net::HTTP 或 Faraday、其他 PHP 或 libcurl 构建，也没有覆盖 25 之后的 Java 版本。默认值会随版本变化。这些是实验运行时已安装或当时最新的版本：macOS 系统自带的 curl 8.7.1 比 curl 8.22.0 旧，PHP 的静态构建比 php.net 的 8.5.11 落后三个补丁版本，Java 25 是最新的 LTS 版本，而不是最新的功能版本。在依赖某一行之前，请重新运行实验。

所有文件都在 `https://ipvolt.com/downloads/accept-encoding-defaults/` 之下：

- [accept-encoding-lab.zip](https://ipvolt.com/downloads/accept-encoding-defaults/accept-encoding-lab.zip) 包含下面的全部文件；它的校验和在 [accept-encoding-lab.zip.sha256](https://ipvolt.com/downloads/accept-encoding-defaults/accept-encoding-lab.zip.sha256) 中。
- [README.md](https://ipvolt.com/downloads/accept-encoding-defaults/README.md) 说明如何运行实验、比较重跑结果和解读一条记录。
- [results.json](https://ipvolt.com/downloads/accept-encoding-defaults/results.json) 和 [results.csv](https://ipvolt.com/downloads/accept-encoding-defaults/results.csv) 保存全部 567 条记录；[tables.md](https://ipvolt.com/downloads/accept-encoding-defaults/tables.md) 包含所有表格，包括解码矩阵和确切的错误文本。
- [environment.txt](https://ipvolt.com/downloads/accept-encoding-defaults/environment.txt) 记录操作系统和每一个版本输出，[corpus-manifest.txt](https://ipvolt.com/downloads/accept-encoding-defaults/corpus-manifest.txt) 记录文档页面，[SHA256SUMS](https://ipvolt.com/downloads/accept-encoding-defaults/SHA256SUMS) 记录归档中其他每个文件的校验和。
- 验证片段：[curl-verify.sh](https://ipvolt.com/downloads/accept-encoding-defaults/harness/snippets/curl-verify.sh)、[requests-verify.py](https://ipvolt.com/downloads/accept-encoding-defaults/harness/snippets/requests-verify.py)、[httpx-verify.py](https://ipvolt.com/downloads/accept-encoding-defaults/harness/snippets/httpx-verify.py)、[node-fetch-bytes.mjs](https://ipvolt.com/downloads/accept-encoding-defaults/harness/snippets/node-fetch-bytes.mjs)、[node-fetch-verify.mjs](https://ipvolt.com/downloads/accept-encoding-defaults/harness/snippets/node-fetch-verify.mjs)、[node-http-verify.mjs](https://ipvolt.com/downloads/accept-encoding-defaults/harness/snippets/node-http-verify.mjs) 和 [go-verify.go](https://ipvolt.com/downloads/accept-encoding-defaults/harness/snippets/go-verify.go)。
- [render_tables.py](https://ipvolt.com/downloads/accept-encoding-defaults/render_tables.py) 可以从 `results.json` 或你自己运行得到的 `harness/out/results.json` 重新生成表格，[compare.py](https://ipvolt.com/downloads/accept-encoding-defaults/compare.py) 把你的重跑结果与已发布的结果进行比较。

要复现整次运行，解压归档，在 `harness/` 中先运行 `./setup.sh` 再运行 `./run.sh`，然后运行 `python3 ../compare.py ../results.json out/results.json`。README 列出了准备阶段会下载什么，以及每组用例需要什么。不需要代理账户。

发布本指南的 ipvolt 是一个正在开发中的代理服务，上线时按 GB 计价。文中的测量使用的是本地测试代理；这些建议适用于任何服务商，不用服务商时也同样适用。

如果你想在 ipvolt 开放访问时收到通知，请[加入早期访问名单](https://ipvolt.com/#waitlist-closing)。开放时只发一封邮件。别无其他。

## 参考来源与延伸阅读

- [RFC 9110: CONNECT tunnels and the HTTPS scheme](https://www.rfc-editor.org/rfc/rfc9110.html#name-connect)
- [Python 3.14 gzip: gzip.decompress and multi-member streams](https://docs.python.org/3.14/library/gzip.html#gzip.decompress)
- [Node.js 26.10.0: resource timing buffer limit](https://github.com/nodejs/node/blob/v26.10.0/lib/internal/perf/observe.js#L103)
- [RFC 9110 section 12.5.3: Accept-Encoding (no header, identity, empty value)](https://www.rfc-editor.org/rfc/rfc9110.html#name-accept-encoding)
- [RFC 9110 section 15.4.5: 304 Not Modified (no content)](https://www.rfc-editor.org/rfc/rfc9110.html#name-304-not-modified)
- [curl man page: --compressed (the page describes 8.23.0; this text is unchanged since the tested 8.22.0)](https://curl.se/docs/manpage.html#--compressed)
- [curl man page: --max-filesize and decompression (since 8.20.0)](https://curl.se/docs/manpage.html#--max-filesize)
- [curl man page: -w, %{size_download} and %header{} (added in 7.84.0)](https://curl.se/docs/manpage.html#-w)
- [libcurl: CURLOPT_ACCEPT_ENCODING (default NULL, no header and no decoding)](https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html)
- [curl issue #11091: no Accept-Encoding by default, captured with nc](https://github.com/curl/curl/issues/11091)
- [curl issue #7516: curl can only ask for compression](https://github.com/curl/curl/issues/7516)
- [curl issue #2836: raw gzip output when a server compresses unasked](https://github.com/curl/curl/issues/2836)
- [Everything curl: HTTP compression applies to downloads](https://everything.curl.dev/http/modify/compression.html)
- [CPython 3.14.7 http.client: Accept-Encoding identity](https://github.com/python/cpython/blob/v3.14.7/Lib/http/client.py#L1296-L1299)
- [urllib3 2.8.0 util/request.py: gzip, deflate, br and zstd tokens](https://github.com/urllib3/urllib3/blob/2.8.0/src/urllib3/util/request.py#L22-L41)
- [urllib3 2.8.0 connection.py: the request path adds no compression header](https://github.com/urllib3/urllib3/blob/2.8.0/src/urllib3/connection.py#L525-L531)
- [Requests 2.34.2 utils.py: default Accept-Encoding](https://github.com/psf/requests/blob/v2.34.2/src/requests/utils.py#L91-L93)
- [httpx 0.28.1 _decoders.py: br needs brotli, zstd needs zstandard](https://github.com/encode/httpx/blob/0.28.1/httpx/_decoders.py#L381-L393)
- [HTTPX documentation: optional brotli and zstd decoders](https://www.python-httpx.org/)
- [aiohttp 3.14.3 http_parser.py: Can not decode content-encoding](https://github.com/aio-libs/aiohttp/blob/v3.14.3/aiohttp/http_parser.py#L1147-L1159)
- [Scrapy 2.19.0 release notes: br and zstd required since 2.18.0](https://github.com/scrapy/scrapy/blob/2.19.0/docs/news.rst)
- [Python 3.14: compression.zstd](https://docs.python.org/3.14/library/compression.zstd.html)
- [Node.js 26.10.0 bundled undici fetch: Accept-Encoding by scheme and Range](https://github.com/nodejs/node/blob/v26.10.0/deps/undici/src/lib/web/fetch/index.js#L1562-L1576)
- [Node.js 24.21.0 bundled undici fetch](https://github.com/nodejs/node/blob/v24.21.0/deps/undici/src/lib/web/fetch/index.js#L1514-L1528)
- [Node.js 22.23.3 bundled undici fetch: no zstd decoder](https://github.com/nodejs/node/blob/v22.23.3/deps/undici/src/lib/web/fetch/index.js#L2149-L2170)
- [Node.js 26.10.0 core HTTP client (no Accept-Encoding)](https://github.com/nodejs/node/blob/v26.10.0/lib/_http_client.js)
- [Node.js zlib: compressing HTTP requests and responses](https://nodejs.org/api/zlib.html#compressing-http-requests-and-responses)
- [node-fetch discussion #1556: default gzip, deflate, br](https://github.com/node-fetch/node-fetch/discussions/1556)
- [axios 1.20.0 http adapter: default Accept-Encoding and zstd opt-in](https://github.com/axios/axios/blob/v1.20.0/lib/adapters/http.js#L54-L57)
- [got 16.0.0 core: default accept-encoding](https://github.com/sindresorhus/got/blob/v16.0.0/source/core/index.ts#L2248-L2258)
- [Go 1.27.1 net/http transport.go: DisableCompression and automatic gzip](https://github.com/golang/go/blob/go1.27.1/src/net/http/transport.go#L199-L207)
- [Go 1.27.1 net/http response.go: Response.Uncompressed](https://github.com/golang/go/blob/go1.27.1/src/net/http/response.go#L93-L100)
- [GNU Wget manual: --compression (none is the default)](https://www.gnu.org/software/wget/manual/wget.html#HTTP-Options)
- [GNU Wget2 2.3.0 wget.c: default Accept-Encoding](https://gitlab.com/gnuwget/wget2/-/blob/v2.3.0/src/wget.c#L4306-4331)
- [Fedora change: Wget2 as wget](https://fedoraproject.org/wiki/Changes/Wget2asWget)
- [PHP manual: cURL constants, CURLOPT_ACCEPT_ENCODING](https://www.php.net/manual/en/curl.constants.php)
- [Guzzle 8.2.0 CurlFactory.php: Accept-Encoding suppressed by default](https://github.com/guzzle/guzzle/blob/8.2.0/src/Handler/CurlFactory.php#L2609-L2622)
- [Guzzle 8.2.0 Client.php: a string decode_content becomes the header](https://github.com/guzzle/guzzle/blob/8.2.0/src/Client.php#L1582-L1586)
- [Guzzle request options: decode_content](https://docs.guzzlephp.org/en/stable/request-options.html#decode-content)
- [Laravel 13.33.0 PendingRequest.php: a plain Guzzle client](https://github.com/laravel/framework/blob/v13.33.0/src/Illuminate/Http/Client/PendingRequest.php)
- [Cloudflare: Content compression](https://developers.cloudflare.com/speed/optimization/content/compression/)
- [Apache HTTP Server 2.4: mod_deflate and proxy servers](https://httpd.apache.org/docs/2.4/mod/mod_deflate.html)
- [nginx: ngx_http_gzip_static_module (gzip_static always)](https://nginx.org/en/docs/http/ngx_http_gzip_static_module.html)
- [OpenSearch issue #17339: REST API hangs with Accept-Encoding zstd](https://github.com/opensearch-project/OpenSearch/issues/17339)
- [MDN: Accept-Encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Encoding)
- [Chrome Platform Status: Zstd Content-Encoding (Chrome 123)](https://chromestatus.com/feature/6186023867908096)
- [Web Almanac 2021: Compression](https://almanac.httparchive.org/en/2021/compression)
- [Decodo help: how proxy traffic is calculated (vendor documentation)](https://help.decodo.com/docs/proxy-traffic)

## 相关指南

- [在 curl 中使用代理：-x、环境变量、SOCKS5 与认证](https://ipvolt.com/zh/guides/curl-proxy-setup.md)
- [Python Requests 代理配置：认证与 SOCKS5](https://ipvolt.com/zh/guides/python-requests-proxy.md)
- [在 Node.js fetch 中使用代理](https://ipvolt.com/zh/guides/nodejs-fetch-proxy.md)

## 关于 ipvolt

示例使用通用的代理设置，并附上原始技术文档链接。具体产品的行为请向你的服务商确认。ipvolt 仍在开发中。

[阅读英文原文](https://ipvolt.com/guides/curl-compressed-accept-encoding.md)

## 了解何时开放体验。

ipvolt · 开发中

我们正在为开发者和数据团队打造代理基础设施。加入意向名单，ipvolt 就绪时第一时间收到通知。

开放体验时仅发一封通知邮件，不发送其他邮件。

[申请抢先体验](https://ipvolt.com/zh/guides/curl-compressed-accept-encoding#waitlist-closing)

[隐私政策](https://ipvolt.com/privacy)

