分析阅读约 4 分钟

Python 3.15 网页抓取:哪些包能装上,哪些会出问题

在没有 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有编译器无编译器备注
scrapy2.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 编译失败编译最慢
aiohttp3.14.5有能装能装
aiohttp[speedups]3.14.5 + aiodns 4.0.4无(Brotli 1.2.0)能装Brotli 编译失败aiohttp aiodns brotlicffi 可以安装
aiohttp-socks0.12.0纯 Python能装能装
urllib3[brotli]2.8.0无(brotli 1.2.0)能装brotli 编译失败urllib3 brotlicffi 可以安装
curl_cffi0.16.3abi3能装能装
playwright1.64.0不区分 Python 版本能装能装未测试浏览器
lxml6.1.3有能装能装
beautifulsoup44.15.0纯 Python能装能装
parsel1.12.1纯 Python能装能装
selectolax1.0.0有能装能装1.0:selectolax.parser 抛出 ImportError,请改用 selectolax.lexbor
pandas3.0.6 + numpy 2.5.4有能装能装
pydantic2.14.1 + pydantic-core 2.50.1pydantic-core: 有能装能装
orjson3.13.0有能装能装
tenacity9.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:

code
  × 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 在同一个包上失败,并指出了依赖链:

code
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 中;我们没有测试 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“Python 3.15 wheels missing”于 10 月 9 日创建,截至 10 月 16 日没有任何回复。google/brotli 的 pull request #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 月)。唯一的其他提示只有一行:

code
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"
code
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)。What's New in Python 3.15 写道,没有显式指定编码的 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 个页面给出了一行就能解决的办法 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 检查。脚本、冒烟测试和原始输出都在下载目录中:lab315.py、results.csv、results.json,以及 10 月 16 日的重新运行结果 results-2026-10-16.json。

相关内容

ipvolt 正在为开发者和智能体构建代理基础设施。加入候补名单,开放访问时只发一封邮件。

参考来源

  1. Python.org:Python 3.15.0 发布页面
  2. What's New in Python 3.15:UTF-8 成为默认编码
  3. PEP 686: Make UTF-8 mode default
  4. PyPI:brotli 1.2.0 文件列表
  5. PyPI:zstandard 0.25.0 文件列表
  6. PyPI:zope.interface 8.6 文件列表
  7. PyPI:zope.interface 8.7 文件列表
  8. PyPI:Scrapy 2.19.0
  9. google/brotli-wheels issue #25:Python 3.15 wheels missing
  10. google/brotli pull request #1583:Build and test CPython 3.15 wheels in CI
  11. Microsoft C++ Build Tools

标签:ProxiesTroubleshooting