Sin KYC — sin ID, sin selfie, sin documentos. Paga con criptomonedas: BTC, USDT, USDC, ETH, SOL, LTC
Idioma: Español
Comienza ahora

Docs · Scrapy 2.x · middleware · AutoThrottle

Middleware de proxy Scrapy para proxies residenciales rotativos

Un middleware downloader de 30 líneas que elige país y sesión por petición, más ajustes que mantienen un crawl respetuoso: robots.txt, concurrencia por dominio, AutoThrottle y reintentos.

Última actualización: · 6 minutos de lectura

Respuesta rápida

¿Cómo uso proxies rotativos con Scrapy?

Pon request.meta["proxy"] en http://USERNAME:[email protected]:7777 en un middleware pequeño que corre antes del HttpProxyMiddleware de Scrapy; Scrapy envía las credenciales por ti. Con proxies residenciales CrawlRelay, país y sesión fija van en el usuario, así un middleware maneja ambos. El tráfico cuesta 0,50 US$/GB desde 50 GB. Se paga en crypto, sin KYC.

Configuración rápida de Scrapy

Dónde va el proxyrequest.meta["proxy"], configurado por tu middleware
Orden del middlewareTu middleware debajo de 750, prioridad del HttpProxyMiddleware integrado
CredencialesEn la URL del proxy; Scrapy lo convierte en una cabecera Proxy-Authorization
SOCKS5No con el manejador HTTP/1.1 por defecto; Scrapy 2.17+ soporta SOCKS vía HttpxDownloadHandler opcional. El puerto HTTP 7777 es lo más simple
RotaciónNueva salida por conexión nueva; un ID de sesión único por solicitud normalmente da IP diferente
CortesíaROBOTSTXT_OBEY, CONCURRENT_REQUESTS_PER_DOMAIN, AUTOTHROTTLE_ENABLED
Reintentos429 y 5xx están en el RETRY_HTTP_CODES por defecto; Retry-After no se lee por defecto

¿Cómo escribo el middleware de proxy?

Pon la clase en middlewares.py. Construye el usuario de las configuraciones y tres claves opcionales meta: proxy_country, proxy_session y proxy_ttl. Una solicitud sin proxy_session rota; con proxy_session="new" crea sesión nueva, por tanto IP salida distinta; con ID de sesión fijo comparte la salida con otras solicitudes del mismo flujo. El middleware recalcula el proxy en cada pasada, incluyendo retries, y no modifica proxies definidos por otro código. Si falta CRAWLRELAY_USER, el crawl se detiene al iniciar en vez de correr desde tu propia IP.

middlewares.py
import uuid
from urllib.parse import quote


class CrawlRelayProxyMiddleware:
    """Sets request.meta["proxy"]; Scrapy's HttpProxyMiddleware then sends the credentials."""

    def __init__(self, user, password, country):
        self.user, self.password, self.country = user, password, country

    @classmethod
    def from_crawler(cls, crawler):
        s = crawler.settings
        if not s.get("CRAWLRELAY_USER"):
            # Fail loudly: NotConfigured would only disable the middleware and crawl from your own IP
            raise ValueError("CRAWLRELAY_USER is not set: refusing to crawl without the proxy")
        return cls(s.get("CRAWLRELAY_USER"), s.get("CRAWLRELAY_PASSWORD", ""), s.get("CRAWLRELAY_COUNTRY", "us"))

    def process_request(self, request, spider=None):
        if "proxy" in request.meta and not request.meta.get("_crawlrelay"):
            return None  # a proxy set by other code: leave it alone
        parts = [self.user, "country", request.meta.get("proxy_country", self.country)]
        session = request.meta.get("proxy_session")
        if session == "new":  # a fresh session for this request (and for each of its retries)
            session = uuid.uuid4().hex[:12]
        if session:
            parts += ["session", session, "ttl", str(request.meta.get("proxy_ttl", 10))]
        username = "-".join(parts)
        request.meta["proxy"] = "http://%s:%[email protected]:7777" % (
            quote(username, safe=""), quote(self.password, safe=""))
        request.meta["_crawlrelay"] = True
        return None

¿Qué configuraciones de Scrapy mantienen un crawl proxy rápido y respetuoso?

Los proxies distribuyen tus solicitudes en muchas IPs, pero el objetivo ve tu tasa total. Estas configuraciones mantienen cada dominio a un ritmo sostenible y dejan que AutoThrottle desacelere solo si las respuestas se vuelven lentas. Mantén ROBOTSTXT_OBEY = True, que ya configuran los nuevos proyectos Scrapy.

settings.py (extracto)
import os

BOT_NAME = "myproject"
ROBOTSTXT_OBEY = True
USER_AGENT = "ExampleCrawler/1.0 (+https://example.com/crawler-info)"

CRAWLRELAY_USER = os.environ.get("CRAWLRELAY_USER")  # e.g. the username from your dashboard
CRAWLRELAY_PASSWORD = os.environ.get("CRAWLRELAY_PASSWORD")
CRAWLRELAY_COUNTRY = "us"

DOWNLOADER_MIDDLEWARES = {
    "myproject.middlewares.CrawlRelayProxyMiddleware": 350,  # runs before HttpProxyMiddleware (750)
}

CONCURRENT_REQUESTS = 32
CONCURRENT_REQUESTS_PER_DOMAIN = 4
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 1.0
AUTOTHROTTLE_MAX_DELAY = 30.0
AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0

RETRY_ENABLED = True
RETRY_TIMES = 3
RETRY_HTTP_CODES = [429, 500, 502, 503, 504, 522, 524, 408]
DOWNLOAD_TIMEOUT = 45

¿Cómo mantengo una IP para paginación y rotar para las páginas de ítems?

Pasa un proxy_session fijo en las solicitudes de un flujo que dependa de la página anterior, y ninguno en páginas independientes. El spider a continuación corre en books.toscrape.com, un sitio sandbox publicado para práctica de scraping.

spiders/catalog.py
import scrapy


class CatalogSpider(scrapy.Spider):
    name = "catalog"
    start_urls = ["https://books.toscrape.com/"]

    def parse(self, response):
        for href in response.css("article.product_pod h3 a::attr(href)").getall():
            # Product pages are independent: rotating exits
            yield response.follow(href, callback=self.parse_item)
        next_page = response.css("li.next a::attr(href)").get()
        if next_page:
            # The listing flow keeps one sticky exit for 30 minutes
            yield response.follow(next_page, callback=self.parse,
                                  meta={"proxy_session": "listing1", "proxy_ttl": 30})

    def parse_item(self, response):
        yield {
            "title": response.css("div.product_main h1::text").get(),
            "price": response.css("p.price_color::text").get(),
            "url": response.url,
        }

¿Scrapy reutiliza conexiones y eso detiene la rotación?

Sí, Scrapy mantiene conexiones activas y las reutiliza por proxy y host objetivo, así que solicitudes consecutivas en una conexión comparten una salida. Para la mayoría de los crawls esto está bien: Scrapy abre hasta CONCURRENT_REQUESTS_PER_DOMAIN conexiones por sitio, y cada conexión nueva generalmente obtiene una IP residencial diferente.

Cuando una solicitud debe salir por una IP nueva, por ejemplo un reintento tras un 502](/docs/errors/502-bad-gateway/) de una salida fallida, reenvíala con response.request.replace(meta={**response.meta, "proxy_session": "new"}, dont_filter=True). El middleware crea un nuevo nombre de usuario de sesión, un usuario distinto significa una pool de conexiones distinta, por lo que Scrapy abre una conexión nueva y la gateway normalmente asigna una salida diferente.

¿Cómo respeto Retry-After en respuestas 429 en Scrapy?

El RetryMiddleware incorporado de Scrapy reintenta el 429 pero no espera el retraso Retry-After. AutoThrottle reduce la velocidad del dominio con el tiempo, lo cual suele ser suficiente. Para APIs que envían valores Retry-After largos, agrega un middleware pequeño que lea el encabezado y programe el reintento con retraso, o reduce CONCURRENT_REQUESTS_PER_DOMAIN para ese dominio. La página 429 explica qué significa ese encabezado.

¿Cómo uso el proxy con scrapy-playwright?

Para páginas que requieren JavaScript, scrapy-playwright pasa las opciones de lanzamiento de Playwright a través de una configuración. Da las credenciales como campos separados: Chromium ignora credenciales incluidas dentro de la URL del servidor. Usa un contexto por sesión sticky si renderizas flujos sin login que abarcan varias páginas.

settings.py para scrapy-playwright
import os

DOWNLOAD_HANDLERS = {
    "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
    "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"

PLAYWRIGHT_LAUNCH_OPTIONS = {
    "proxy": {
        "server": "http://gw.crawlrelay.com:7777",
        "username": os.environ.get("CRAWLRELAY_USER", "USERNAME") + "-country-us",
        "password": os.environ.get("CRAWLRELAY_PASSWORD", "PASSWORD"),
    },
}

Scrapy y proxies: preguntas

¿Necesito scrapy-rotating-proxies o una lista de proxies?

No. Esos paquetes rotan una lista de direcciones de proxies individuales y bloquean las que fallan. Con un gateway residencial, la rotación ocurre detrás de una dirección: cada conexión nueva generalmente obtiene una IP residencial diferente. Solo necesitas un middleware que configure request.meta["proxy"].

¿Por qué Scrapy muestra TunnelError con mi proxy?

TunnelError: Could not open CONNECT tunnel significa que el proxy rechazó o falló la solicitud CONNECT para un sitio HTTPS. El estado en el mensaje te dice por qué: 407 es por credenciales, 502 o 504 es por la salida o el objetivo. Mira las páginas 407 y 502.

¿Cuántas solicitudes concurrentes por dominio debo usar?

Empieza con 2 a 4 por dominio y deja que AutoThrottle ajuste. Aumenta solo cuando la proporción de respuestas 429, 403 y 5xx se mantenga estable y observa los tiempos de respuesta del sitio. Sitios grandes toleran más que sitios pequeños; un sitio pequeño puede necesitar un retraso entre solicitudes y una sola conexión.

¿Debo desactivar ROBOTSTXT_OBEY para obtener más datos?

No. robots.txt es la forma en que un sitio indica qué rutas pueden consultar clientes automatizados, y el formato se estandarizó con RFC 9309 en 2022. Ignorarlo incrementa tu riesgo legal y ético y viola nuestra Política de Uso Aceptable cuando se usa para acceder a contenido excluido por el sitio.

¿Cuánto tráfico usa un crawl de Scrapy?

Scrapy descarga solo HTML, así que las páginas suelen costar decenas de kilobytes cada una, comprimidas. Revisa el valor downloader/response_bytes en las estadísticas que Scrapy imprime al final de la ejecución y divídelo por el número de respuestas. La guía de ancho de banda convierte eso en un costo.

Conecta IPs residenciales a tu proyecto Scrapy

5,94 US$ para 10 GB para probar, 0,50 US$/GB de 50 GB.

Desde 0,50 US$/GB · sin KYC

Comienza ahora