Análisis9 min de lectura

Python 3.15 para web scraping: qué se instala y qué se rompe

pip install scrapy falla en Python 3.15 sin compilador de C porque brotli no tiene wheel cp315. Instalamos 19 paquetes de scraping con pip y uv para ver el resto.

En esta página

En Python 3.15.0, la mayor parte de un stack de scraping se instala desde wheels ya compilados y pasó una prueba de humo. Todo lo que necesita brotli o zstandard falla en una máquina sin compilador de C, porque ninguno de los dos tiene wheel para 3.15, y eso incluye un simple pip install scrapy. La solución rápida es instalar gcc antes de pip install (solución 1, más abajo) o quedarse en Python 3.14 hasta que brotli publique wheels cp315 (solución 2).

Última comprobación: 16 de octubre de 2026, 10:09 UTC. En ese momento PyPI seguía sin wheel cp315 para brotli 1.2.0 y zstandard 0.25.0. zope.interface 8.7, publicado el 15 de octubre, ya trae wheels cp315.

PaqueteVersiónWheel cp315Con compiladorSin compiladorNotas
scrapy2.19.0Python puro; no (brotli 1.2.0)se instalafalla al compilar brotliexige brotli en CPython desde la 2.18.0
requests[socks]2.34.2 + PySocks 1.7.1Python purose instalase instala
httpx[socks]0.28.1 + socksio 1.0.0Python purose instalase instala
httpx[brotli]0.28.1no (brotli 1.2.0)se instalafalla al compilar brotlihttpx brotlicffi se instala sin compilador
httpx[zstd]0.28.1no (zstandard 0.25.0)se instalafalla al compilar zstandardla compilación más lenta
aiohttp3.14.5síse instalase instala
aiohttp[speedups]3.14.5 + aiodns 4.0.4no (Brotli 1.2.0)se instalafalla al compilar Brotliaiohttp aiodns brotlicffi se instala
aiohttp-socks0.12.0Python purose instalase instala
urllib3[brotli]2.8.0no (brotli 1.2.0)se instalafalla al compilar brotliurllib3 brotlicffi se instala
curl_cffi0.16.3abi3se instalase instala
playwright1.64.0no depende de la versiónse instalase instalanavegadores sin probar
lxml6.1.3síse instalase instala
beautifulsoup44.15.0Python purose instalase instala
parsel1.12.1Python purose instalase instala
selectolax1.0.0síse instalase instala1.0: selectolax.parser lanzó ImportError, usa selectolax.lexbor
pandas3.0.6 + numpy 2.5.4síse instalase instala
pydantic2.14.1 + pydantic-core 2.50.1pydantic-core: síse instalase instala
orjson3.13.0síse instalase instala
tenacity9.2.1Python purose instalase instala

"Con compilador" y "sin compilador" son la imagen oficial python:3.15-slim con y sin gcc, y cada paquete se instaló tanto con pip como con uv, que coincidieron en todos los casos. Un paquete de Python puro no tiene código compilado, así que no necesita wheel cp315. Cada instalación correcta pasó su prueba de humo.

pip install scrapy falla en Python 3.15: Failed building wheel for brotli

En la imagen slim, pip 26.2.1 se detiene en 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 falla en el mismo paquete y muestra la cadena:

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`

La cadena es corta. Scrapy 2.19.0 es Python puro, pero requiere brotli>=1.2.0; implementation_name != "pypy", así que toda instalación en CPython arrastra brotli. Ese requisito llegó con Scrapy 2.18.0 (20 de agosto de 2026); la 2.17.0 no lo tenía. brotli 1.2.0, publicado el 5 de noviembre de 2025, tiene wheels para CPython 2.7 y de 3.6 a 3.14, y nada más reciente. Sin un wheel compatible, pip y uv descargan el archivo de código fuente y compilan la extensión _brotli, que necesita gcc. Lo mismo ocurre con httpx[brotli], aiohttp[speedups] y urllib3[brotli], que en CPython también piden el paquete brotli, y con httpx[zstd], que pide zstandard 0.25.0.

Solución 1: añadir un compilador a la imagen

Esta única línea, ejecutada en la imagen slim, bastó para todos los paquetes que fallaban:

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

Instaló gcc 14.2.0. Después, pip install scrapy tardó 28 s en lugar de fallar, uv tardó 19 s, y el spider rastreó sus páginas de prueba, incluida una comprimida con brotli. La compilación lenta es la de zstandard: 63 s para httpx[zstd] con pip. En un Dockerfile, pon la línea antes del pip install.

Solo se probó esta línea de Debian. En Windows, el compilador que necesitan estas compilaciones viene con las C++ Build Tools de Microsoft; no probamos Windows.

Solución 2: quedarse en Python 3.14 hasta que existan los wheels

Python 3.14 tiene wheels de brotli y zstandard, así que allí el problema no existe. Para saber si brotli ya se ha puesto al día, ejecuta esto con Python 3.15:

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

El 16 de octubre seguía terminando con ERROR: No matching distribution found for brotli. Cuando descargue un archivo .whl, los wheels cp315 estarán publicados y pip install scrapy funcionará sin compilador.

En el proyecto original, el issue #25 de google/brotli-wheels, "Python 3.15 wheels missing", se abrió el 9 de octubre y el 16 de octubre no tenía respuestas. El pull request #1583 de google/brotli añade un trabajo de CI que compila wheels cp315. El 16 de octubre seguía abierto sin revisión, y el único comentario era el del bot del CLA. Su descripción indica que un mantenedor aún tendría que publicar los wheels en PyPI.

¿No usas Scrapy? brotlicffi tiene wheel

brotlicffi 1.2.0.2 publica un wheel abi3 que pip instaló en 3.15. HTTPX, urllib3 y aiohttp lo aceptan en lugar de brotli, así que sin compilador estas combinaciones funcionaron y descomprimieron una respuesta en brotli:

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

La última es aiohttp[speedups] sin el requisito Brotli. Scrapy no puede usar esta vía: en CPython exige el propio paquete brotli.

Con zstd es más fácil de lo que parece. En Python 3.14 y posteriores, urllib3 2.8.0 y aiohttp 3.14.5 descomprimen zstd con el módulo compression.zstd de la biblioteca estándar, así que ambos ofrecieron zstd y descomprimieron una respuesta en zstd sin ningún paquete adicional. Solo httpx[zstd] necesita zstandard.

No uses --only-binary a ciegas

--only-binary :all: le dice a pip que nunca compile desde el código fuente. Parece la solución obvia, y no falla:

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

pip imprimió INFO: This is taking longer than usual mientras buscaba y después instaló httpx 0.18.2 (junio de 2021) y aiohttp 0.13.1 (diciembre de 2014). La única otra pista fue una línea:

code
WARNING: aiohttp 0.13.1 does not provide the extra 'speedups'

El resolvedor retrocedió por versiones antiguas hasta encontrar unas cuyos requisitos podían cumplirse solo con wheels. uv pip install --no-build eligió las mismas dos versiones e imprimió warning: The package `aiohttp==0.13.1` does not have an extra named `speedups` .

Limitar el flag a un paquete no lo hace seguro. pip install --only-binary brotli scrapy también termina con código 0 e instala Scrapy 2.17.0, la última versión sin el requisito de brotli. uv hizo lo mismo. Ese Scrapy rastreó las páginas de prueba, pero solo ofrecía a los servidores gzip, deflate, mientras que 2.19.0 con brotli ofrecía gzip, deflate, br, zstd. Si tus spiders usan async def start(), 2.17.0 los sigue ejecutando.

Para detectar una bajada de versión silenciosa, revisa las versiones instaladas después de cada instalación:

sh
pip list
uv pip list

Para que falle de forma visible, fija la versión que quieres junto al flag:

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 con la misma versión fijada se detuvo con error: No solution found when resolving dependencies. Un requirements.txt con versiones exactas para tus paquetes principales te da la misma protección en cada build.

Resuelto el 15 de octubre: zope.interface sin aceleraciones en C

Twisted, y con él Scrapy, depende de zope.interface. zope.interface 8.6 solo tiene wheels hasta CPython 3.14. El 14 de octubre, sin compilador, pip y uv lo instalaron igualmente en 3.15: su compilación pasa a Python puro cuando la extensión en C no se puede compilar. La salida de la instalación no mostró ningún error ni advertencia.

El coste se veía en la comprobación básica de interfaces. En el laboratorio, IThing.providedBy(obj) tardó 271 ns por llamada sin la extensión en C y 101 ns con ella. El laboratorio no midió qué supone eso para un rastreo completo.

zope.interface 8.7, publicado el 15 de octubre, incluye wheels cp315. El 16 de octubre, una instalación limpia sin compilador obtuvo el wheel binario 8.7 con pip y con uv, la extensión en C se importó y la misma comprobación tardó 102 ns. Un entorno virtual creado antes del 15 de octubre conserva la 8.6 en Python puro hasta que actualices zope.interface.

Para ver cuál tienes:

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

Un ModuleNotFoundError significa la versión de respaldo en Python puro. Con el wheel 8.7, o con gcc instalado, la importación funcionó.

Otros cambios de 3.15 que importan al hacer scraping

El que más se va a notar es el modo UTF-8, ahora activado por defecto (PEP 686). What's New in Python 3.15 dice que las operaciones de E/S sin una codificación explícita, como open('flying-circus.txt'), usan ahora UTF-8 sea cual sea el entorno del sistema. En el laboratorio, con LANG=C, open() sin encoding escribió UTF-8 en 3.15; con -X utf8=0 volvió a ANSI_X3.4-1968, es decir, ASCII.

En scraping esto aparece sobre todo en las exportaciones. Un CSV o JSON escrito con open(path, "w") es ahora UTF-8 en cualquier máquina, incluidos los sistemas Windows donde 3.14 usaba la página de códigos de la configuración regional. Normalmente es lo que quieres. Si una herramienta posterior espera la codificación antigua, pasa encoding="locale" o define PYTHONUTF8=0; mejor aún, indica la codificación de forma explícita al escribir y al leer.

Si actualizas Scrapy al mismo tiempo

Pasar a Python 3.15 suele venir con un Scrapy nuevo. Desde la 2.16, Scrapy ya no llama a start_requests(): un spider que solo define ese método se abre, no envía nada y se cierra como terminado, sin ninguna advertencia. Scrapy rastrea 0 páginas después de actualizar tiene la corrección de una línea, async def start(), que es la que usó el spider de este laboratorio.

Método y límites

Registrado el 14 de octubre de 2026 en un host Ubuntu x86_64, dentro del sistema de archivos de la imagen oficial python:3.15-slim (Debian 13 trixie, digest de índice sha256:ea4f1f29…, construida el 12 de octubre de 2026), ejecutado en un espacio de nombres privado de Linux en lugar de Docker. El intérprete informó Python 3.15.0 (main, Oct 12 2026, 22:33:31) [GCC 14.2.0], con el pip 26.2.1 incluido y uv 0.13.1. La copia con compilador añadió gcc 14.2.0 con la línea de apt de arriba. La tabla sale de esas ejecuciones. El 16 de octubre se repitieron sin compilador los casos de Scrapy, --only-binary brotli y zope.interface: todo coincidió con el 14 de octubre salvo zope.interface, que ahora vino del wheel 8.7.

Cada caso tuvo un entorno virtual nuevo y ninguna caché de paquetes. Las versiones instaladas y si cada una venía de un wheel binario, de un wheel de Python puro o de una compilación local se leyeron de las etiquetas de los wheels. Las pruebas de humo usaron un http.server local que solo comprime con las codificaciones que ofrece el cliente. Playwright se instaló, pero no sus navegadores. No se usó ningún proxy.

El laboratorio no dice nada sobre macOS, Windows, ARM, imágenes basadas en musl ni builds sin GIL. PyPI cambia a diario, así que repite la comprobación de wheels antes de actuar según cualquier fila. El script, las pruebas de humo y la salida en bruto están en la carpeta de descarga: lab315.py, results.csv, results.json y la repetición del 16 de octubre, results-2026-10-16.json.

Relacionado

ipvolt está construyendo infraestructura de proxies para desarrolladores y agentes. Únete a la lista de espera para recibir un correo cuando se abra el acceso.

Fuentes

  1. Python.org: página de la versión Python 3.15.0
  2. What's New in Python 3.15: UTF-8 como codificación por defecto
  3. PEP 686: Make UTF-8 mode default
  4. PyPI: archivos de brotli 1.2.0
  5. PyPI: archivos de zstandard 0.25.0
  6. PyPI: archivos de zope.interface 8.6
  7. PyPI: archivos de 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

Etiquetas:ProxiesTroubleshooting