Middleware de proxy en Scrapy: guía de configuración completa (2026)

Publicado el 4 de junio de 2026 · ≈11 min de lectura

Scrapy sigue siendo el caballo de batalla para los crawls en producción en 2026 — y sigue siendo el framework donde la configuración de proxy más confunde a la gente, porque hay cuatro lugares distintos donde conectar un proxy y tres de ellos están mal para la mayoría de los proyectos. Esta guía te da el correcto: un pequeño middleware personalizado con enrutamiento por petición, sesiones sticky, detección de baneos y un comportamiento de reintento sensato.

Si estás con requests/httpx/aiohttp a secas, consulta Cómo rotar proxies en Python. Este artículo es específico de Scrapy.

La versión de 30 segundos

Para una puerta de enlace residencial rotativa, la configuración mínima viable es una línea por petición — sin middleware:

def start_requests(self):
    for url in self.urls:
        yield scrapy.Request(
            url,
            meta={"proxy": "http://USERNAME:[email protected]:913"},
        )

El HttpProxyMiddleware integrado de Scrapy lee request.meta["proxy"] y gestiona la autenticación a partir de la URL. La puerta de enlace rota la IP de salida por ti. Si eso es todo lo que necesitas, detente aquí. El resto de esta guía es para cuando necesitas control: sesiones sticky, enrutamiento por país, rotación consciente de baneos y ajuste de concurrencia.

Un middleware de proxy para producción

Coloca esto en middlewares.py. Asigna sesiones sticky por dominio, rota ante baneos y etiqueta cada petición para que puedas depurar qué sesión obtuvo qué:

import random
import string

GATEWAY = "us.jibaoproxy.com:913"
USERNAME = "USERNAME"          # move to settings.py / env in real projects
PASSWORD = "PASSWORD"

def _new_session(n=8):
    return "".join(random.choices(string.ascii_lowercase + string.digits, k=n))

class JibaoProxyMiddleware:
    """Sticky session per domain; rotate session on ban."""

    def __init__(self):
        self.sessions = {}          # domain -> session id

    def _proxy_url(self, session_id):
        user = f"{USERNAME}-session-{session_id}"
        return f"http://{user}:{PASSWORD}@{GATEWAY}"

    def process_request(self, request, spider):
        domain = request.url.split("/")[2]
        session = self.sessions.setdefault(domain, _new_session())
        request.meta["proxy"] = self._proxy_url(session)
        request.meta["proxy_session"] = session

    def rotate(self, domain):
        """Call when a session is burned."""
        self.sessions[domain] = _new_session()

Y un middleware de descarga complementario que detecta baneos y reintenta en una sesión nueva:

from scrapy.downloadermiddlewares.retry import RetryMiddleware
from scrapy.utils.response import response_status_message

BAN_CODES = {403, 429}
BAN_MARKERS = (b"captcha", b"access denied", b"unusual traffic")

class BanAwareRetryMiddleware(RetryMiddleware):

    def process_response(self, request, response, spider):
        banned = (
            response.status in BAN_CODES
            or any(m in response.body[:2048].lower() for m in BAN_MARKERS)
        )
        if banned:
            domain = request.url.split("/")[2]
            proxy_mw = spider.crawler.engine.downloader.middleware.middlewares
            for mw in proxy_mw:
                if hasattr(mw, "rotate"):
                    mw.rotate(domain)        # burn the session
            reason = response_status_message(response.status)
            return self._retry(request, reason, spider) or response
        return super().process_response(request, response, spider)

Conecta ambos en settings.py:

DOWNLOADER_MIDDLEWARES = {
    "myproject.middlewares.JibaoProxyMiddleware": 350,
    "scrapy.downloadermiddlewares.retry.RetryMiddleware": None,   # replace stock retry
    "myproject.middlewares.BanAwareRetryMiddleware": 550,
}
RETRY_TIMES = 2

La prioridad importa: el middleware de proxy debe ejecutarse antes que el HttpProxyMiddleware de Scrapy (750), así que cualquier valor por debajo de 750 funciona; 350 lo mantiene temprano y predecible.

Sticky vs rotativo: qué modo para qué spider

Tipo de crawlModoImplementación
Recolección de páginas sin estadoRotativoUsuario simple, la puerta de enlace rota por petición
Login + crawl tras autenticaciónSticky por cuenta-session-{account_id}, nunca rotar a mitad del login
Listados con mucha paginaciónSticky por dominio, rotar ante baneoEl middleware de arriba
Precios específicos por geografíaRotativo + fijación de paísParámetros estilo USERNAME-country-de

Tratamiento más profundo de este compromiso: Sesiones de proxy sticky vs rotativas.

Ajustes de concurrencia que no te hacen banear

Los valores por defecto de Scrapy están ajustados para un crawling cortés de una sola IP. Detrás de un pool rotativo puedes presionar mucho más — pero los límites por dominio aún importan porque el objetivo ve el comportamiento agregado:

# settings.py - sane starting point behind a residential pool
CONCURRENT_REQUESTS = 64
CONCURRENT_REQUESTS_PER_DOMAIN = 8     # what the target experiences
DOWNLOAD_DELAY = 0.25                  # jitter applied per slot
RANDOMIZE_DOWNLOAD_DELAY = True        # 0.5x-1.5x the delay
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 6.0
DOWNLOAD_TIMEOUT = 30
ROBOTSTXT_OBEY = True

Sube CONCURRENT_REQUESTS_PER_DOMAIN solo después de observar tu tasa de 403 al nivel actual durante unos pocos miles de peticiones. Pasar de 8 → 32 porque "de todos modos los proxies rotan" es como la gente quema GB en reintentos.

Disciplina de ancho de banda (los GB residenciales son dinero)

Fallos comunes y qué significan realmente

407 Proxy Authentication Required

Las credenciales no llegaron al proxy. Ponlas en la URL (http://user:pass@host:port) dentro de meta["proxy"] — Scrapy las parsea y establece Proxy-Authorization por ti. Configurar la cabecera manualmente y usar credenciales en la URL provoca rarezas de doble autenticación; elige una.

TunnelError: Could not open CONNECT tunnel

Casi siempre un host/puerto mal escrito, o un objetivo HTTPS a través de un endpoint que no permite CONNECT en ese puerto. Verifica primero con curl -x fuera de Scrapy.

El spider funciona 10 minutos y luego todo es 403

Tu sesión sticky sobrevivió a su bienvenida, o tu tasa por dominio está demasiado caliente. El middleware consciente de baneos de arriba gestiona el primer caso; baja CONCURRENT_REQUESTS_PER_DOMAIN para el segundo. Si es un objetivo que verifica JA4, el propio stack TLS de Scrapy puede ser la señal delatora — consulta JA3/JA4 explicados para saber por qué ningún proxy arregla eso.

Herramienta gratuita · sin registro

Valida tu lista de proxies antes del crawl

Pega los endpoints en nuestro Proxy Checker: prueba en lote la conectividad, la latencia, el nivel de anonimato y el tipo de IP de salida — detecta proxies muertos o mal etiquetados antes de que Scrapy desperdicie reintentos en ellos.

Verificar mis proxies →

¿Cansado de cuidar listas gratuitas? Una sola puerta de enlace residencial las reemplaza todas — obtén 500MB de tráfico gratis →

Resumen

Apunta tus spiders a un pool de verdad

Sesiones residenciales rotativas y sticky en una sola puerta de enlace. 500MB de tráfico gratis para crawlear.

Comenzar prueba gratuita

Para todos los productos IP · un enorme pool de nodos disponibles en cualquier momento

Regístrate ahora y obtén hasta un 100% de reembolso en tu recarga

Los nuevos usuarios reciben 500MB al registrarse, más un bono en la primera recarga. Oferta por tiempo limitado.