# Proxies en Scrapy 2.19: autenticación, rotación, errores

Source: https://ipvolt.com/es/guides/scrapy-proxy-setup
Markdown: https://ipvolt.com/es/guides/scrapy-proxy-setup.md
Language: es

[Inicio de ipvolt](https://ipvolt.com/es.md) / [Guías](https://ipvolt.com/es/guides.md) / Proxies en Scrapy 2.19: autenticación, rotación, errores

Integración
Publicado: 2026-10-07
8 min de lectura
Por ipvolt

Configura proxies en Scrapy 2.19: credenciales en meta["proxy"], rotación por petición, HTTP_PROXY y soluciones para TunnelError 407, el 407 ignorado y socks5.

Pon las credenciales en la URL del proxy y pásala como `meta["proxy"]`:

```python
import scrapy

PROXY = "http://user:pass@proxy.example.com:8080"


class ExampleSpider(scrapy.Spider):
    name = "example"

    async def start(self):
        yield scrapy.Request("https://example.com/", meta={"proxy": PROXY})

    def parse(self, response):
        self.logger.info("%s via %s", response.status, response.meta["proxy"])
```

`HttpProxyMiddleware` está activado por defecto (`HTTPPROXY_ENABLED = True`). Traslada el usuario y la contraseña de la URL a una cabecera `Proxy-Authorization` ([documentación](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#module-scrapy.downloadermiddlewares.httpproxy)), así que `response.meta["proxy"]` contiene la URL sin credenciales.

En nuestro laboratorio esto autenticó peticiones `https://` y `http://` contra un proxy local que exige autenticación Basic. Todo lo que sigue se registró el 7 de octubre de 2026 con Scrapy 2.19.0, Twisted 26.4.0 y Python 3.14.4.

Tres detalles antes de los errores:

- **Codifica la contraseña con porcentajes.** `p@ss:w/rd` funcionó como `p%40ss%3Aw%2Frd`. Sin codificar, la petición falló con `ValueError: Port could not be cast to integer value as 'w'`.
- **Las peticiones creadas en un callback no heredan el proxy.** Una petición creada con `response.follow()` salió directa. Una redirección conservó el proxy. Define `meta["proxy"]` en cada petición, o en un downloader middleware como se muestra más abajo.
- **Usa `async def start()`.** Un spider que solo definía `start_requests()` se abrió, no envió nada y se cerró con `finish_reason: finished` y sin ningún aviso. Scrapy 2.16.0 eliminó el método: «is removed and no longer called» ([notas de la versión](https://docs.scrapy.org/en/latest/news.html#release-2-16-0)). Lo sustituye [`Spider.start()`](https://docs.scrapy.org/en/latest/topics/spiders.html#scrapy.Spider.start). [Scrapy no llama a start_requests()](/es/guides/scrapy-start-requests-not-called) explica la migración, incluidos los spider middlewares.

## Rotación de proxies: define meta["proxy"] en cada petición

La rotación es un downloader middleware que se ejecuta antes que `HttpProxyMiddleware` (orden 750) y fija una URL de proxy con credenciales:

```python
from itertools import cycle

PROXIES = cycle([
    "http://user:pass@proxy-a.example.com:8080",
    "http://user:pass@proxy-b.example.com:8080",
])


class RotateProxyMiddleware:
    def process_request(self, request):
        request.meta["proxy"] = next(PROXIES)
```

```python
DOWNLOADER_MIDDLEWARES = {"myproject.middlewares.RotateProxyMiddleware": 740}
```

En el laboratorio, una petición redirigida pasó dos veces por este middleware, fue a dos proxies y se autenticó en ambos.

La versión que falla fija una URL sin credenciales y escribe la cabecera a mano:

```python
request.meta["proxy"] = next(PROXIES)  # "http://proxy-a.example.com:8080"
request.headers["Proxy-Authorization"] = "Basic dXNlcjpwYXNz"
```

La primera petición llegó al proxy A con la cabecera. El destino respondió 301 y la petición redirigida volvió a pasar por los middlewares. La rotación eligió el proxy B y puso la cabecera otra vez, pero el proxy B recibió la petición sin `Proxy-Authorization` y respondió 407. Una petición `https://` reintentada perdió la cabecera de la misma forma.

`HttpProxyMiddleware` registra con qué URL de proxy se envió una cabecera puesta a mano. Cuando una pasada posterior sobre la misma petición tiene otra URL de proxy sin credenciales, borra la cabecera. Las [notas de la versión 2.6.2](https://docs.scrapy.org/en/latest/news.html#release-2-6-2) explican el motivo: los middlewares de rotación cambiaban el proxy y dejaban la cabecera antigua, de modo que las credenciales de un proxy iban a otro. Desde la [2.7.1](https://docs.scrapy.org/en/latest/news.html#release-2-7-1), una cabecera puesta a mano se conserva solo «for as long as that proxy URL remains the same». Las credenciales de la URL siempre la sustituyen.

Dos resultados más del mismo código:

- En la primera pasada la cabecera aún no está ligada a un proxy. Un middleware que cambió `meta["proxy"]` de A a B antes de que se ejecutara `HttpProxyMiddleware` envió la cabecera puesta a mano al proxy B.
- Sin ningún proxy, la cabecera se eliminó antes de que la petición llegara al destino.

## Variables de entorno: HTTP_PROXY se usa por defecto

Scrapy lee las variables de proxy cuando arranca el rastreo, y no hace falta ningún ajuste para activarlo. Con `HTTP_PROXY=http://user:pass@127.0.0.1:8888` y sin `meta["proxy"]`, la petición del laboratorio pasó por el proxy y se autenticó.

- `HTTP_PROXY` y `http_proxy` en minúsculas se aplicaron a las URL `http://`, y `HTTPS_PROXY` a las URL `https://`. Con solo `HTTP_PROXY` definida, una petición `https://` salió directa. `ALL_PROXY` se ignoró.
- `NO_PROXY=127.0.0.1` envió la petición directa. `meta["proxy"]` tuvo prioridad sobre las variables e ignoró `NO_PROXY`, como dice la documentación.
- `meta={"proxy": None}` envió una petición directa con la variable definida.
- `HTTPPROXY_ENABLED = False` hizo que Scrapy dejara de leer las variables. También desactivó el tratamiento de las credenciales: `meta["proxy"] = "http://user:pass@127.0.0.1:8888"` lanzó entonces `ValueError: invalid hostname: user:pass@127.0.0.1` para una URL `http://` y el `TunnelError` 407 para `https://`.

Para ignorar el entorno y que `meta["proxy"]` siga funcionando, sustituye el middleware por una subclase que descarta lo que leyó:

```python
from scrapy.downloadermiddlewares.httpproxy import HttpProxyMiddleware


class ExplicitProxyMiddleware(HttpProxyMiddleware):
    def __init__(self, auth_encoding="latin-1"):
        super().__init__(auth_encoding)
        self.proxies = {}
```

```python
DOWNLOADER_MIDDLEWARES = {
    "scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware": None,
    "myproject.middlewares.ExplicitProxyMiddleware": 750,
}
```

`proxies` no es un atributo documentado, así que vuelve a probarlo después de actualizar. aiohttp hace lo contrario: ignora las variables salvo que pases `trust_env=True` ([Proxies en aiohttp 3.14](/es/guides/aiohttp-proxy-setup)). [Variables de entorno de proxy](/es/guides/proxy-environment-variables) compara otros clientes.

## TunnelError: Could not open CONNECT tunnel with proxy

```text
scrapy.core.downloader.handlers.http11.TunnelError: Could not open CONNECT tunnel with proxy 127.0.0.1:8888 [{'status': 407, 'reason': b'Proxy Authentication Required'}]
```

El proxy rechazó la petición `CONNECT` de una URL `https://`. El mensaje fue idéntico sin credenciales y con una contraseña incorrecta, y no contiene las credenciales, así que no te dice cuál de los dos casos tienes. Revisa la URL de `meta["proxy"]` en la petición que falla y después sigue el orden de comprobaciones de [Corrige el error de proxy 407](/es/guides/fix-proxy-error-407).

- `TunnelError` está en `RETRY_EXCEPTIONS`. Con la configuración por defecto, el proxy del laboratorio recibió el `CONNECT` rechazado tres veces. El registro mostró `Retrying <GET https://127.0.0.1:8443/> (failed 1 times)`, después `(failed 2 times)` y después `Gave up retrying`. Con `"dont_retry": True` en `meta`, el proxy recibió uno.
- `handle_httpstatus_list` no cambia nada aquí. No hay respuesta, solo la excepción, así que trátala en un `errback`.

## Ignoring response 407: HTTP status code is not handled or not allowed

```text
Ignoring response <407 http://127.0.0.1:8080/>: HTTP status code is not handled or not allowed
```

Para una URL `http://`, el 407 del proxy es una respuesta normal y `HttpErrorMiddleware` la descarta. El callback no se llamó nunca, esta línea INFO fue la única señal y el rastreo se cerró con `finish_reason: finished`. Con un `errback`, la línea no se registra y el errback recibe `scrapy.spidermiddlewares.httperror.HttpError: Ignoring non-200 response`. Scrapy no reintentó la petición.

Para recibir la respuesta en el callback, permite ese estado en la petición:

```python
yield scrapy.Request(url, meta={"proxy": PROXY, "handle_httpstatus_list": [407]})
```

El atributo del spider `handle_httpstatus_list = [407]` y el ajuste `HTTPERROR_ALLOWED_CODES = [407]` hicieron lo mismo ([HttpErrorMiddleware](https://docs.scrapy.org/en/latest/topics/spider-middleware.html#module-scrapy.spidermiddlewares.httperror)). El callback recibió entonces el estado 407 con `Proxy-Authenticate: Basic realm="lab"`. Sin ninguno de ellos, las estadísticas del rastreo cuentan las respuestas descartadas en `httperror/response_ignored_status_count/407`.

## UnsupportedURLSchemeError: Unsupported scheme: b'socks5'

```text
scrapy.exceptions.UnsupportedURLSchemeError: Unsupported scheme: b'socks5'
```

El download handler por defecto de Scrapy no admite SOCKS. Con `meta["proxy"] = "socks5://127.0.0.1:1080"`, una petición `http://` lanzó este error una vez, sin reintentos. `socks5h://` lanzó `Unsupported scheme: b'socks5h'`, y una URL SOCKS en `HTTP_PROXY` lanzó el mismo error.

Una petición `https://` no lo lanzó. Scrapy envió `CONNECT 127.0.0.1:8443` como HTTP en claro al puerto SOCKS, el servidor SOCKS del laboratorio cerró la conexión y Scrapy esperó el timeout de descarga tres veces antes de informar `DownloadTimeoutError: Getting https://127.0.0.1:8443/ took longer than 15.0 seconds.` El laboratorio fijó `DOWNLOAD_TIMEOUT` en 15. El valor por defecto es 180.

Hay dos salidas:

- Usa un endpoint de proxy HTTP si tu proveedor tiene uno. Entonces se aplica todo lo anterior.
- Cambia a `HttpxDownloadHandler`. Viene con Scrapy, y la [documentación](https://docs.scrapy.org/en/latest/topics/download-handlers.html#httpx-handler) indica que admite SOCKS5 y lo marca como experimental y todavía no recomendado para producción.

```sh
python -m pip install "scrapy[httpx]"
```

```python
DOWNLOAD_HANDLERS = {
    "http": "scrapy.core.downloader.handlers._httpx.HttpxDownloadHandler",
    "https": "scrapy.core.downloader.handlers._httpx.HttpxDownloadHandler",
}
```

Con este handler, `socks5://` devolvió 200 para ambos esquemas. Para `http://localhost:8080/` el servidor SOCKS registró un nombre de dominio, así que el handler envía el nombre de host al proxy. `socks5h://` también funcionó. [socks5 vs socks5h](/es/blog/socks5-vs-socks5h) muestra qué hacen otros clientes con los dos esquemas. Sus errores se leen distinto: un `CONNECT` rechazado apareció como `scrapy.exceptions.DownloadConnectionRefusedError: 407 Proxy Authentication Required`.

## Reintentos: RetryMiddleware repite las peticiones POST

`RetryMiddleware` reintentó un `POST` igual que reintenta un `GET`. En el laboratorio, un `POST` pasó por el proxy hasta un destino que leyó la petición y cerró la conexión sin responder. Scrapy registró `Retrying <POST http://127.0.0.1:8081/jobs> (failed 1 times)` y lo envió de nuevo. El destino recibió el mismo cuerpo dos veces y el callback vio un solo 201.

Para una petición que no es seguro repetir, pon `"dont_retry": True` en `meta` ([RetryMiddleware](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#module-scrapy.downloadermiddlewares.retry)). El destino recibió entonces un solo `POST` y el errback recibió `scrapy.exceptions.DownloadFailedError`. [Reintentos con proxy: una respuesta perdida, dos trabajos creados](/es/blog/proxy-retries-duplicate-jobs) muestra qué cambia una clave de idempotencia.

Cada reintento es otra petición a través del proxy. [¿Cuántos GB de proxies necesitas?](/es/blog/how-many-gb-of-proxies-do-i-need) mide cuánto consume una descarga de solo HTML. Si quieres ayuda de diagnóstico dentro de un agente de programación, [Proxy Toolkit MCP](/mcp) acepta descripciones de errores saneadas. Mantén las credenciales fuera de sus argumentos.

## Cómo se probó

En Ubuntu 26.04.1 con Python 3.14.4, Scrapy 2.19.0, Twisted 26.4.0 y httpx2 2.13.1, todo en `127.0.0.1`: `python -m http.server` como destinos HTTP y HTTPS, tres proxies que exigen autenticación Basic y registran cada petición con los nombres de sus cabeceras, un servidor SOCKS5 que registra cada destino y un destino que pierde la respuesta al primer `POST`. Cada uno de los 62 casos ejecutó `scrapy runspider` en un intérprete nuevo con un entorno limpio. No se usó ninguna cuenta de proxy. En Python 3.15.0, `pip install scrapy` falla sin un compilador de C porque brotli no tenía wheel cp315 el 14 de octubre de 2026; las soluciones probadas están en [Python 3.15 para web scraping](/es/blog/python-3-15-web-scraping).

El [archivo del laboratorio](https://ipvolt.com/downloads/scrapy-proxy-setup/scrapy-proxy-lab.zip) contiene el [ejecutor](https://ipvolt.com/downloads/scrapy-proxy-setup/run_lab.py), los [casos](https://ipvolt.com/downloads/scrapy-proxy-setup/cases.py), el [proxy](https://ipvolt.com/downloads/scrapy-proxy-setup/auth_proxy.py), el [servidor SOCKS5](https://ipvolt.com/downloads/scrapy-proxy-setup/socks5_server.py), el [README](https://ipvolt.com/downloads/scrapy-proxy-setup/README.md) y los [resultados registrados](https://ipvolt.com/downloads/scrapy-proxy-setup/results.json).

No se probó: macOS y Windows, autenticación de proxy Digest y NTLM, proxies SOCKS con autenticación, proxies a los que se llega por TLS, HTTP/2 y el download handler de Scrapy basado en aiohttp.

ipvolt es un servicio de proxies para desarrolladores que todavía no está abierto; [únete a la lista de acceso anticipado](https://ipvolt.com/#waitlist-closing) y recibirás un solo correo cuando abra.

## Fuentes y lecturas adicionales

- [Scrapy documentation: HttpProxyMiddleware and HTTPPROXY_ENABLED](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#module-scrapy.downloadermiddlewares.httpproxy)
- [Scrapy documentation: Request.meta special keys (proxy, dont_retry, handle_httpstatus_list)](https://docs.scrapy.org/en/latest/topics/request-response.html#topics-request-meta)
- [Scrapy documentation: HttpErrorMiddleware and HTTPERROR_ALLOWED_CODES](https://docs.scrapy.org/en/latest/topics/spider-middleware.html#module-scrapy.spidermiddlewares.httperror)
- [Scrapy documentation: RetryMiddleware](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#module-scrapy.downloadermiddlewares.retry)
- [Scrapy documentation: Spider.start()](https://docs.scrapy.org/en/latest/topics/spiders.html#scrapy.Spider.start)
- [Scrapy documentation: HttpxDownloadHandler](https://docs.scrapy.org/en/latest/topics/download-handlers.html#httpx-handler)
- [Scrapy release notes: 2.16.0, start_requests() removed](https://docs.scrapy.org/en/latest/news.html#release-2-16-0)
- [Scrapy release notes: 2.6.2, Proxy-Authorization security fix](https://docs.scrapy.org/en/latest/news.html#release-2-6-2)
- [Scrapy release notes: 2.7.1, explicit Proxy-Authorization header](https://docs.scrapy.org/en/latest/news.html#release-2-7-1)

## Guías relacionadas

- [Corrige el error de proxy 407 sin adivinar](https://ipvolt.com/es/guides/fix-proxy-error-407.md)
- [Scrapy no llama a start_requests(): Crawled 0 pages](https://ipvolt.com/es/guides/scrapy-start-requests-not-called.md)
- [Variables de entorno de proxy: HTTP_PROXY y NO_PROXY](https://ipvolt.com/es/guides/proxy-environment-variables.md)

## Sobre ipvolt

Los ejemplos usan configuraciones de proxy genéricas, con enlaces a la documentación técnica original. El comportamiento específico de cada producto debe comprobarse con tu proveedor. ipvolt sigue en desarrollo.

[Leer el original en inglés](https://ipvolt.com/guides/scrapy-proxy-setup.md)

## Entérate cuando se abra el acceso.

ipvolt · En desarrollo

Estamos construyendo infraestructura de proxies para desarrolladores y equipos de datos. Únete a la lista de interés para recibir un aviso cuando ipvolt esté listo.

Un correo cuando se abra el acceso. Nada más.

[Solicitar acceso anticipado](https://ipvolt.com/es/guides/scrapy-proxy-setup#waitlist-closing)

[Privacidad](https://ipvolt.com/privacy)

