# Python 3.15 网页抓取：哪些包能装上，哪些会出问题

Source: https://ipvolt.com/zh/blog/python-3-15-web-scraping
Markdown: https://ipvolt.com/zh/blog/python-3-15-web-scraping.md
Language: zh-CN

[ipvolt 首页](https://ipvolt.com/zh.md) / [博客](https://ipvolt.com/zh/blog.md) / Python 3.15 网页抓取实测：哪些能装，哪些会坏

分析
发布于: 2026-10-16
更新于: 2026-10-16
作者： ipvolt
阅读约 4 分钟

在没有 C 编译器的 Python 3.15 上，pip install scrapy 会失败，因为 brotli 没有 cp315 wheel。我们用 pip 和 uv 安装了 19 个抓取常用包。

在 Python 3.15.0 上，抓取技术栈的大部分包都能从现成的 wheel 安装，并通过了冒烟测试。凡是依赖 brotli 或 zstandard 的包，在没有 C 编译器的机器上都会安装失败，因为这两个包都没有 3.15 的 wheel，连最普通的 `pip install scrapy` 也不例外。最快的解决办法是在 `pip install` 之前装上 `gcc`（见下文方案 1），或者在 brotli 发布 cp315 wheel 之前继续使用 Python 3.14（方案 2）。

**最后检查时间：2026 年 10 月 16 日 10:09（UTC）。** 当时 PyPI 上 brotli 1.2.0 和 zstandard 0.25.0 仍然没有 cp315 wheel。10 月 15 日发布的 zope.interface 8.7 已经提供 cp315 wheel。

| 包 | 版本 | cp315 wheel | 有编译器 | 无编译器 | 备注 |
| --- | --- | --- | --- | --- | --- |
| `scrapy` | 2.19.0 | 纯 Python; 无（brotli 1.2.0） | 能装 | brotli 编译失败 | 自 2.18.0 起在 CPython 上依赖 brotli |
| `requests[socks]` | 2.34.2 + PySocks 1.7.1 | 纯 Python | 能装 | 能装 |  |
| `httpx[socks]` | 0.28.1 + socksio 1.0.0 | 纯 Python | 能装 | 能装 |  |
| `httpx[brotli]` | 0.28.1 | 无（brotli 1.2.0） | 能装 | brotli 编译失败 | `httpx brotlicffi` 无需编译器即可安装 |
| `httpx[zstd]` | 0.28.1 | 无（zstandard 0.25.0） | 能装 | zstandard 编译失败 | 编译最慢 |
| `aiohttp` | 3.14.5 | 有 | 能装 | 能装 |  |
| `aiohttp[speedups]` | 3.14.5 + aiodns 4.0.4 | 无（Brotli 1.2.0） | 能装 | Brotli 编译失败 | `aiohttp aiodns brotlicffi` 可以安装 |
| `aiohttp-socks` | 0.12.0 | 纯 Python | 能装 | 能装 |  |
| `urllib3[brotli]` | 2.8.0 | 无（brotli 1.2.0） | 能装 | brotli 编译失败 | `urllib3 brotlicffi` 可以安装 |
| `curl_cffi` | 0.16.3 | abi3 | 能装 | 能装 |  |
| `playwright` | 1.64.0 | 不区分 Python 版本 | 能装 | 能装 | 未测试浏览器 |
| `lxml` | 6.1.3 | 有 | 能装 | 能装 |  |
| `beautifulsoup4` | 4.15.0 | 纯 Python | 能装 | 能装 |  |
| `parsel` | 1.12.1 | 纯 Python | 能装 | 能装 |  |
| `selectolax` | 1.0.0 | 有 | 能装 | 能装 | 1.0：`selectolax.parser` 抛出 ImportError，请改用 `selectolax.lexbor` |
| `pandas` | 3.0.6 + numpy 2.5.4 | 有 | 能装 | 能装 |  |
| `pydantic` | 2.14.1 + pydantic-core 2.50.1 | pydantic-core: 有 | 能装 | 能装 |  |
| `orjson` | 3.13.0 | 有 | 能装 | 能装 |  |
| `tenacity` | 9.2.1 | 纯 Python | 能装 | 能装 |  |

“有编译器”和“无编译器”指官方 `python:3.15-slim` 镜像装与不装 `gcc` 两种情况。每个包都分别用 pip 和 uv 安装过，两者在所有用例中结果一致。纯 Python 包没有编译代码，所以不需要 cp315 wheel。每次成功的安装都通过了各自的冒烟测试。

## 在 Python 3.15 上 pip install scrapy 失败：Failed building wheel for brotli

在 slim 镜像里，pip 26.2.1 卡在 brotli：

```text
  × Building wheel for brotli (pyproject.toml) did not run successfully.
  │ exit code: 1
      error: [Errno 2] No such file or directory: 'gcc'
  ERROR: Failed building wheel for brotli
error: failed-wheel-build-for-install

× Failed to build installable wheels for some pyproject.toml based projects
╰─> brotli
```

uv 0.13.1 在同一个包上失败，并指出了依赖链：

```text
error: Failed to build `brotli==1.2.0`
  cause: The build backend returned an error
hint: `brotli` (v1.2.0) was included because `scrapy` (v2.19.0) depends on `brotli`
```

这条依赖链很短。Scrapy 2.19.0 是纯 Python 包，但它要求 `brotli>=1.2.0; implementation_name != "pypy"`，所以在 CPython 上的每次安装都会带上 brotli。这个依赖是 Scrapy 2.18.0（2026 年 8 月 20 日）加入的，2.17.0 还没有。brotli 1.2.0 发布于 2025 年 11 月 5 日，提供 CPython 2.7 和 3.6 到 3.14 的 wheel，没有更新的版本。找不到匹配的 wheel 时，pip 和 uv 会下载源码包并编译 `_brotli` 扩展，而这需要 `gcc`。`httpx[brotli]`、`aiohttp[speedups]` 和 `urllib3[brotli]` 在 CPython 上同样需要 `brotli` 包，`httpx[zstd]` 需要 zstandard 0.25.0，所以它们也会遇到同样的问题。

### 办法一：在镜像里装上编译器

在 slim 镜像中运行下面这一行，所有失败的包就都能装上了：

```sh
apt-get update && apt-get install -y --no-install-recommends gcc libc6-dev
```

它装上了 gcc 14.2.0。之后 `pip install scrapy` 用 28 秒完成而不再失败，uv 用了 19 秒，爬虫也抓取了测试页面，其中包括一个 brotli 压缩的页面。zstandard 编译最慢：用 pip 安装 `httpx[zstd]` 花了 63 秒。在 Dockerfile 里，把这一行放在 `pip install` 之前。

只测试了这一行 Debian 命令。在 Windows 上，这类编译所需的编译器包含在微软的 [C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) 中；我们没有测试 Windows。

### 办法二：在 wheel 发布前继续使用 Python 3.14

Python 3.14 有 brotli 和 zstandard 的 wheel，所以不存在这个问题。想知道 brotli 是否已经跟上，用 Python 3.15 运行：

```sh
python3 -m pip download --only-binary :all: --no-deps -d /tmp/wheel-check brotli
```

10 月 16 日运行时，它仍然以 `ERROR: No matching distribution found for brotli` 结束。一旦它下载到 `.whl` 文件，就说明 cp315 wheel 已经发布，`pip install scrapy` 不再需要编译器。

上游方面，[google/brotli-wheels 的 issue #25](https://github.com/google/brotli-wheels/issues/25)“Python 3.15 wheels missing”于 10 月 9 日创建，截至 10 月 16 日没有任何回复。[google/brotli 的 pull request #1583](https://github.com/google/brotli/pull/1583) 增加了一个构建 cp315 wheel 的 CI 任务。截至 10 月 16 日，它仍处于打开状态、没有审查，唯一的评论来自 CLA 机器人。它的说明中写道，仍需要维护者把 wheel 发布到 PyPI。

### 不用 Scrapy？brotlicffi 有 wheel

brotlicffi 1.2.0.2 提供 `abi3` wheel，pip 在 3.15 上安装成功。HTTPX、urllib3 和 aiohttp 都接受用它代替 `brotli`，所以在没有编译器的情况下，下面几种组合都能安装，并解压了 brotli 压缩的响应：

```sh
pip install httpx brotlicffi
pip install urllib3 brotlicffi
pip install aiohttp aiodns brotlicffi
```

最后一行相当于去掉 `Brotli` 依赖的 `aiohttp[speedups]`。Scrapy 走不通这条路：在 CPython 上它要求的就是 `brotli` 包本身。

zstd 比看上去简单。在 Python 3.14 及更新版本上，urllib3 2.8.0 和 aiohttp 3.14.5 使用标准库的 `compression.zstd` 解压 zstd，所以两者都向服务器声明了 `zstd`，并在不装任何额外包的情况下解压了 zstd 响应。只有 `httpx[zstd]` 需要 zstandard。

## 不要盲目使用 --only-binary

`--only-binary :all:` 让 pip 永远不从源码构建。它看起来是最直接的变通办法，而且不会报错：

```sh
pip install --only-binary :all: "httpx[brotli]" "aiohttp[speedups]"
```

pip 在回溯时输出了 `INFO: This is taking longer than usual`，最后装上的是 httpx 0.18.2（2021 年 6 月）和 aiohttp 0.13.1（2014 年 12 月）。唯一的其他提示只有一行：

```text
WARNING: aiohttp 0.13.1 does not provide the extra 'speedups'
```

解析器一路回退到旧版本，直到找到所有依赖都能只用 wheel 满足的版本。`uv pip install --no-build` 选中了同样的两个版本，并输出 ``warning: The package `aiohttp==0.13.1` does not have an extra named `speedups` ``。

只对一个包使用这个参数也不安全。`pip install --only-binary brotli scrapy` 同样以退出码 0 结束，装上的是 Scrapy 2.17.0，也就是最后一个不依赖 brotli 的版本。uv 的结果相同。这个 Scrapy 抓取了测试页面，但它只向服务器声明 `gzip, deflate`，而带 brotli 的 2.19.0 声明的是 `gzip, deflate, br, zstd`。如果你的爬虫使用 `async def start()`，2.17.0 仍然可以运行它们。

要发现这种悄无声息的降级，每次安装后都看一下实际装上的版本：

```sh
pip list
uv pip list
```

要让它明确报错，就在参数旁边锁定你想要的版本：

```sh
pip install --only-binary brotli "scrapy==2.19.0"
```

```text
ERROR: Could not find a version that satisfies the requirement brotli>=1.2.0; implementation_name != "pypy" (from scrapy) (from versions: none)
ERROR: No matching distribution found for brotli>=1.2.0; implementation_name != "pypy"
```

uv 在同样锁定版本时停在 `error: No solution found when resolving dependencies`。在 `requirements.txt` 里为顶层包写明确切版本，每次构建都能得到同样的保护。

## 10 月 15 日已修复：没有 C 加速的 zope.interface

Twisted 以及依赖它的 Scrapy 都依赖 zope.interface。zope.interface 8.6 的 wheel 只到 CPython 3.14。10 月 14 日，没有编译器时，pip 和 uv 仍然在 3.15 上装上了它：C 扩展无法编译时，它的构建会退回到纯 Python。安装输出里既没有错误，也没有警告。

代价体现在基本的接口检查上。在实验中，`IThing.providedBy(obj)` 没有 C 扩展时每次调用耗时 271 纳秒，有 C 扩展时为 101 纳秒。实验没有测量这对一次完整抓取意味着什么。

10 月 15 日发布的 zope.interface 8.7 提供了 cp315 wheel。10 月 16 日，在没有编译器的环境中全新安装时，pip 和 uv 都拿到了 8.7 的二进制 wheel，C 扩展可以导入，同样的检查耗时 102 纳秒。10 月 15 日之前创建的虚拟环境仍是纯 Python 的 8.6，直到你升级 zope.interface。

查看你用的是哪一种：

```sh
python -c "import zope.interface._zope_interface_coptimizations"
```

出现 `ModuleNotFoundError` 就说明用的是纯 Python 后备实现。使用 8.7 的 wheel 或装上 gcc 后，导入都成功了。

## 3.15 中对抓取有影响的其他变化

大多数抓取程序最先注意到的是 UTF-8 模式，它现在默认开启（[PEP 686](https://peps.python.org/pep-0686/)）。[What's New in Python 3.15](https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-utf8-default) 写道，没有显式指定编码的 I/O 操作，例如 `open('flying-circus.txt')`，现在无论系统环境如何都使用 UTF-8。在实验中，设置 `LANG=C` 时，3.15 上不带 `encoding` 的 `open()` 写出的是 UTF-8；加上 `-X utf8=0` 后则退回 `ANSI_X3.4-1968`，也就是 ASCII。

对抓取来说，这主要体现在导出上。用 `open(path, "w")` 写出的 CSV 或 JSON 文件现在在任何机器上都是 UTF-8，包括 3.14 会使用区域设置代码页的 Windows 系统。这通常正是你想要的。如果下游工具需要旧编码，传入 `encoding="locale"` 或设置 `PYTHONUTF8=0`；更好的做法是在写入和读取时都显式指定编码。

## 如果你同时升级 Scrapy

升级到 Python 3.15 时往往也会换上新版 Scrapy。从 2.16 起，Scrapy 不再调用 `start_requests()`：只定义了这个方法的爬虫会打开、什么都不发送，然后以完成状态关闭，没有任何警告。[升级后 Scrapy 抓取 0 个页面](/zh/guides/scrapy-start-requests-not-called)给出了一行就能解决的办法 `async def start()`，本实验中的爬虫用的正是它。

## 方法与局限

记录于 2026 年 10 月 14 日，在一台 x86_64 Ubuntu 主机上，使用官方 `python:3.15-slim` 镜像的文件系统（Debian 13 trixie，索引摘要 `sha256:ea4f1f29…`，构建于 2026 年 10 月 12 日），在独立的 Linux 命名空间而不是 Docker 中运行。解释器报告为 `Python 3.15.0 (main, Oct 12 2026, 22:33:31) [GCC 14.2.0]`，自带 pip 26.2.1，另装 uv 0.13.1。有编译器的副本用上面的 apt 命令装上了 gcc 14.2.0。表格来自这些运行。10 月 16 日，我们在没有编译器的环境中重新运行了 Scrapy、`--only-binary brotli` 和 zope.interface 这几项：除了 zope.interface 现在来自 8.7 的 wheel 之外，结果都与 10 月 14 日一致。

每个用例都使用新的虚拟环境，并且不使用包缓存。安装的版本，以及每个包来自二进制 wheel、纯 Python wheel 还是本地构建，都是从 wheel 标签中读取的。冒烟测试访问的是本地 `http.server`，它只使用客户端声明支持的编码进行压缩。Playwright 已安装，但没有安装浏览器。全程没有使用代理。

本实验不涉及 macOS、Windows、ARM、基于 musl 的镜像或自由线程构建。PyPI 每天都在变化，所以在根据表中任何一行采取行动前，请重新运行 wheel 检查。脚本、冒烟测试和原始输出都在[下载目录](/downloads/python-3-15-web-scraping/README.md)中：[lab315.py](/downloads/python-3-15-web-scraping/lab315.py)、[results.csv](/downloads/python-3-15-web-scraping/results.csv)、[results.json](/downloads/python-3-15-web-scraping/results.json)，以及 10 月 16 日的重新运行结果 [results-2026-10-16.json](/downloads/python-3-15-web-scraping/results-2026-10-16.json)。

## 相关内容

- [Scrapy 2.19 代理设置](/zh/guides/scrapy-proxy-setup)：认证、轮换，以及 Scrapy 记录的代理错误。
- [aiohttp 代理](/zh/guides/aiohttp-proxy-setup)和 [curl_cffi 代理](/zh/guides/curl-cffi-proxy-setup)：两者在 3.15 上都能从 wheel 安装。
- [修复 "Missing dependencies for SOCKS support"](/zh/guides/fix-missing-dependencies-for-socks-support)：这个技术栈中的另一个安装错误。
- [我需要多少 GB 代理流量？](/zh/blog/how-many-gb-of-proxies-do-i-need)：技术栈跑起来之后估算流量。压缩在那里很重要：声明的编码越少，客户端下载的字节可能越多。

ipvolt 正在为开发者和智能体构建代理基础设施。[加入候补名单](https://ipvolt.com/#waitlist-hero)，开放访问时只发一封邮件。

## 参考来源

- [Python.org：Python 3.15.0 发布页面](https://www.python.org/downloads/release/python-3150/)
- [What's New in Python 3.15：UTF-8 成为默认编码](https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-utf8-default)
- [PEP 686: Make UTF-8 mode default](https://peps.python.org/pep-0686/)
- [PyPI：brotli 1.2.0 文件列表](https://pypi.org/project/brotli/1.2.0/#files)
- [PyPI：zstandard 0.25.0 文件列表](https://pypi.org/project/zstandard/0.25.0/#files)
- [PyPI：zope.interface 8.6 文件列表](https://pypi.org/project/zope.interface/8.6/#files)
- [PyPI：zope.interface 8.7 文件列表](https://pypi.org/project/zope.interface/8.7/#files)
- [PyPI：Scrapy 2.19.0](https://pypi.org/project/Scrapy/2.19.0/)
- [google/brotli-wheels issue #25：Python 3.15 wheels missing](https://github.com/google/brotli-wheels/issues/25)
- [google/brotli pull request #1583：Build and test CPython 3.15 wheels in CI](https://github.com/google/brotli/pull/1583)
- [Microsoft C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/)

## 第一时间了解 ipvolt 开放体验。

顺便一提

ipvolt 仍在开发中。留下邮箱，开放体验时我们只会通知你一次。

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

[申请抢先体验](https://ipvolt.com/zh/blog/python-3-15-web-scraping#waitlist-blog-end)

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


## 相关文章

- [用 ToDetect 检查代理 IP、DNS 和 WebRTC 泄露](https://ipvolt.com/zh/blog/proxy-leak-check-todetect.md) (分析, 2026年10月23日, 阅读约 4 分钟): 用 ToDetect 的免费工具检查浏览器代理是否泄露 IP、DNS、WebRTC 和时区，并以一条 4G 移动线路的实测结果为例。
- [日本价格解析：日元、全角字符与税込/税抜标签](https://ipvolt.com/zh/blog/japanese-price-parsing.md) (分析, 2026年10月6日, 阅读约 2 分钟): 解析日本价格字段，同时不丢失币种和计税基础。运行一套经过测试的 Python 夹具，覆盖全角日元、混合价格以及刻意设计的待复核用例。
- [SOCKS5 与 SOCKS5h 的区别：6 个客户端实测](https://ipvolt.com/zh/blog/socks5-vs-socks5h.md) (对比, 2026年10月4日, 阅读约 4 分钟): socks5:// 并不是在每个客户端里都表示本地 DNS。我们记录了 curl、Requests、HTTPX、aiohttp、Playwright 和 Node 在每种协议写法下发给 SOCKS5 代理的地址。

## 相关指南

- [Scrapy 2.19 代理配置：认证、轮换与常见错误](https://ipvolt.com/zh/guides/scrapy-proxy-setup.md): Scrapy 2.19 代理配置：把凭据写进 meta["proxy"]、按请求轮换、HTTP_PROXY 环境变量，以及 TunnelError 407、被忽略的 407 响应和 socks5 报错的处理方法。
- [Scrapy 抓取 0 页：start_requests() 已移除](https://ipvolt.com/zh/guides/scrapy-start-requests-not-called.md): Scrapy 2.16 移除了 start_requests()：爬虫输出 Crawled 0 pages 后以 finished 关闭且无警告。改用 async def start()，已在 2.19 实测。
- [aiohttp 3.14 代理配置：认证、trust_env 与报错](https://ipvolt.com/zh/guides/aiohttp-proxy-setup.md): 在 aiohttp 3.14 中配置代理：替换已弃用的 BasicAuth 和 proxy_auth，启用 trust_env，并看懂 407 与 Bad status line 报错。基于 3.14.4 实测。

## 关于 ipvolt

来自 ipvolt 团队的技术分析。

ipvolt 尚未开放使用。

[阅读英文原文](https://ipvolt.com/blog/python-3-15-web-scraping.md)
