Añade un Sistema de Respaldo con API de Scraping a tu Proyecto Scrapy
Tu araña de Scrapy funciona. Extrae miles de páginas al día, las parsea sin problemas y no te cuesta más que ancho de banda. Y entonces, una mañana, los registros se llenan de respuestas 403, o peor aún, de respuestas 200 que contienen una página de desafío en lugar de los datos que querías. El sitio añadió una capa anti-bot y tus peticiones directas dejaron de pasar.
El primer impulso es enrutar todo a través de una API de scraping. Eso funciona, pero es un desperdicio. La mayoría de tus peticiones nunca fueron bloqueadas, y enviarlas todas por una API de pago significa pagar por páginas que podrías haber obtenido gratis. Un patrón mejor es un respaldo: obtener la página directamente primero y escalar a la API solo cuando una petición falla de verdad.
Esta guía te muestra cómo construir ese respaldo en Scrapy como un middleware de descarga, con detección adecuada, lógica de reintentos y manejo de errores. El código está completo y puedes incorporarlo a un proyecto existente.
Por Qué un Respaldo en Lugar de un Interruptor de Todo o Nada
Una API de scraping es un recurso de pago. Cada petición a través de ella cuesta un crédito. Si el 90% de tus páginas objetivo no están protegidas, enrutarlas todas por la API multiplica tu factura sin beneficio alguno.
Un respaldo invierte la lógica:
- Directo primero. Scrapy envía su petición normal. Si el sitio responde con datos limpios, ya está y no pagaste nada.
- Escalar ante el fallo. Si la respuesta está bloqueada, ausente o malformada, el middleware reintenta la misma URL a través de la API.
- Rendirse con elegancia. Tras un número fijo de intentos con la API, la petición se descarta o se registra para que no entre en bucle indefinidamente.
Así tu gasto en créditos se mantiene proporcional a lo difícil que es el sitio en realidad. También significa que puedes apuntar una araña a un conjunto mixto de dominios, algunos fáciles y otros protegidos, sin dividir tu código en dos caminos.
Dónde Encaja el Respaldo en Scrapy
Scrapy procesa cada petición a través de una cadena de middlewares de descarga antes y después de la descarga real. Este es el lugar adecuado para un respaldo, porque un middleware puede:
- Ver la respuesta que Scrapy obtuvo de la petición directa.
- Decidir si esa respuesta cuenta como un bloqueo.
- Reemplazarla con una petición nueva enrutada a través de la API.
No tocas para nada la lógica de parseo de tu araña. La araña pide una URL y recibe una respuesta funcional. Si esa respuesta vino directamente o a través de la API es invisible para ella.
Detectar un Bloqueo
La parte más difícil de un respaldo no es la llamada a la API. Es decidir cuándo activarlo. Una comprobación ingenua de response.status == 403 se pierde el fallo moderno más común: el bloqueo suave, en el que el servidor devuelve 200 OK con un desafío o una página de “verifica que eres humano” en lugar del contenido real.
Construye tu detección en torno a varias señales:
- Códigos de estado duros.
403,429y503son bloqueos explícitos o límites de tasa. - Cuerpo sospechoso en un
200. Cuerpos muy cortos, o cuerpos que contienen marcadores de desafío conocidos, son bloqueos suaves. - Contenido esperado ausente. Si la página debería contener una cuadrícula de productos y el selector no devuelve nada, trátalo como una obtención fallida.
Aquí tienes un ayudante de detección que puedes ajustar según el proyecto:
BLOCK_STATUS = {403, 429, 503}
CHALLENGE_MARKERS = (
b"captcha",
b"cf-challenge",
b"just a moment",
b"verify you are human",
)
def looks_blocked(response):
if response.status in BLOCK_STATUS:
return True
body = response.body[:20000].lower()
if len(response.body) < 500:
return True
return any(marker in body for marker in CHALLENGE_MARKERS)
Mantén la lista de marcadores corta y específica. Si haces coincidencias demasiado amplias, enviarás páginas limpias a la API y gastarás créditos. Si quieres profundizar en por qué un 200 puede seguir siendo un bloqueo, consulta nuestra guía sobre cómo detectar bloqueos suaves en web scraping.
El Middleware de Respaldo
Ahora el middleware en sí. Observa cada respuesta y, cuando looks_blocked devuelve True, reconstruye la petición para que pase por la API de scraping en lugar del origen. Lleva la cuenta de cuántas veces ha reintentado una URL dada mediante un indicador en el meta de la petición, así que nunca entra en bucle.
El endpoint de obtención de ScrapeUnblocker recibe la URL objetivo como parámetro de consulta y una clave de API en una cabecera, así que el respaldo se reduce a reescribir la URL de la petición e intercambiar las cabeceras.
import logging
from urllib.parse import quote, urlencode
from scrapy.exceptions import IgnoreRequest
from scrapy.http import Request
logger = logging.getLogger(__name__)
API_ENDPOINT = "https://api.scrapeunblocker.com/getPageSource"
class ScrapingApiFallbackMiddleware:
def __init__(self, api_key, max_api_retries):
if not api_key:
raise ValueError("SCRAPEUNBLOCKER_API_KEY is not set")
self.api_key = api_key
self.max_api_retries = max_api_retries
@classmethod
def from_crawler(cls, crawler):
return cls(
api_key=crawler.settings.get("SCRAPEUNBLOCKER_API_KEY"),
max_api_retries=crawler.settings.getint("API_FALLBACK_MAX_RETRIES", 2),
)
def _build_api_request(self, original, attempt):
params = {"url": original.meta.get("origin_url", original.url)}
# Añade opciones extra aquí si un sitio las necesita, por ejemplo:
# params["proxy_country"] = "us"
api_url = f"{API_ENDPOINT}?{urlencode(params, quote_via=quote)}"
return original.replace(
url=api_url,
method="POST",
headers={"x-scrapeunblocker-key": self.api_key},
meta={
**original.meta,
"origin_url": original.meta.get("origin_url", original.url),
"api_attempt": attempt,
"download_slot": "scrapeunblocker-api",
},
dont_filter=True,
)
def process_response(self, request, response, spider):
attempt = request.meta.get("api_attempt", 0)
if not looks_blocked(response):
return response
if attempt >= self.max_api_retries:
logger.warning(
"Giving up on %s after %d API attempts",
request.meta.get("origin_url", request.url),
attempt,
)
raise IgnoreRequest(f"Blocked after {attempt} API retries")
next_attempt = attempt + 1
logger.info(
"Blocked, routing through API (attempt %d): %s",
next_attempt,
request.meta.get("origin_url", request.url),
)
return self._build_api_request(request, next_attempt)
Algunos detalles que conviene entender:
origin_urlen meta. Una vez que una petición se reescribe para apuntar a la API,request.urles la URL de la API, no la página que querías. Guardar el objetivo original enmeta["origin_url"]permite que cada reintento reconstruya la llamada a la API a partir de la URL real.- Contador
api_attempt. Cada escalada lo incrementa. Cuando alcanzamax_api_retries, el middleware lanzaIgnoreRequesty se detiene. download_slot. Establecer una ranura compartida para las peticiones a la API te permite regularlas de forma independiente de tu tráfico directo (más sobre esto abajo).dont_filter=True. Sin esto, el filtro de duplicados de Scrapy descartaría el reintento porque apunta a una URL que la araña ya visitó.
Conectarlo en la Configuración
Activa el middleware y establece tu clave y límites en settings.py:
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.ScrapingApiFallbackMiddleware": 610,
}
SCRAPEUNBLOCKER_API_KEY = "YOUR_API_KEY"
API_FALLBACK_MAX_RETRIES = 2
# Regula el tráfico de la API por separado de las peticiones directas.
DOWNLOAD_SLOTS = {
"scrapeunblocker-api": {"concurrency": 4, "delay": 0},
}
El número de prioridad 610 coloca el middleware justo después del RetryMiddleware incorporado de Scrapy (que se sitúa en 550), de modo que los errores transitorios normales se reintentan primero de forma directa, y solo los bloqueos persistentes caen hacia la API. Lee tu clave desde una variable de entorno en lugar de escribirla directamente en el código:
import os
SCRAPEUNBLOCKER_API_KEY = os.environ["SCRAPEUNBLOCKER_API_KEY"]
Manejar Errores y Tiempos de Espera
Los bloqueos no son el único modo de fallo. La propia petición a la API puede agotar su tiempo de espera o devolver un error, y quieres manejar eso sin que se caiga el rastreo. Añade un hook process_exception al mismo middleware para que los errores de red en una petición directa también activen el respaldo:
def process_exception(self, request, exception, spider):
attempt = request.meta.get("api_attempt", 0)
if attempt >= self.max_api_retries:
return None # deja que Scrapy maneje el fallo con normalidad
logger.info(
"Download error (%s), routing through API: %s",
type(exception).__name__,
request.meta.get("origin_url", request.url),
)
return self._build_api_request(request, attempt + 1)
Combina esto con la configuración de reintentos y tiempos de espera de Scrapy para que una llamada lenta a la API no bloquee todo el rastreo:
DOWNLOAD_TIMEOUT = 60
RETRY_ENABLED = True
RETRY_TIMES = 2
Como la API renderiza las páginas protegidas con un navegador real, sus respuestas son más lentas que una obtención en crudo. Un tiempo de espera de 60 segundos le da margen para trabajar sin colgar tu araña indefinidamente.
Probar el Respaldo
Antes de ejecutarlo a gran escala, confirma que los dos caminos se comportan bien:
- Apunta la araña a una página no protegida y comprueba que los registros no muestran intentos con la API. Eso demuestra que no estás gastando créditos en páginas fáciles.
- Apúntala a una página protegida conocida y confirma que ves la línea de registro “routing through API”, seguida de un parseo exitoso. Eso demuestra que la escalada funciona.
- Fuerza un fallo estableciendo
max_api_retriesen0y confirma que la petición se descarta de forma limpia con la advertencia “giving up” en lugar de entrar en bucle.
Preguntas Frecuentes
¿El respaldo ralentiza mi rastreo? Solo en las páginas que se bloquean. Las peticiones directas corren a toda velocidad. Las peticiones escaladas son más lentas porque la API las renderiza con un navegador real, que es el precio de superar el bloqueo. La ranura de descarga separada evita que ese tráfico más lento ralentice tus peticiones directas.
¿Cómo evito pagar por páginas que en realidad no estaban bloqueadas?
Ajusta looks_blocked. Mantén la lista de marcadores de desafío específica para lo que tus objetivos realmente devuelven, y registra cada escalada durante las pruebas para detectar falsos positivos antes de que te cuesten dinero.
¿Puedo usar esto con el AutoThrottle de Scrapy? Sí. AutoThrottle ajusta los retrasos según la latencia de las respuestas. Poner las peticiones a la API en su propia ranura de descarga evita que su mayor latencia arrastre a la baja el cálculo de retraso de tu tráfico directo.
¿Y si una página necesita renderizado de JavaScript o un país específico?
Añade los parámetros correspondientes al diccionario params en _build_api_request. Por ejemplo, establece un valor proxy_country para enrutar a través de una región dada. El resto del middleware permanece igual.
Conclusión
Un respaldo con API de scraping te da lo mejor de ambos modelos: obtenciones directas gratuitas y rápidas para el grueso de tus páginas, y un camino de escalada fiable para el puñado de dominios que se resisten. Todo vive en un único middleware de descarga, así que tus arañas se mantienen limpias y tu gasto en créditos se mantiene proporcional a lo difícil que es cada sitio en realidad.
Si quieres probar el camino de escalada, ScrapeUnblocker se encarga del bypass anti-bot y del renderizado del navegador tras un único endpoint, facturado a un crédito por petición con el renderizado de JavaScript incluido. Incorpora el middleware de arriba a tu proyecto, apunta SCRAPEUNBLOCKER_API_KEY a tu clave, y tus arañas existentes siguen funcionando mientras las páginas difíciles vuelven a funcionar sin ruido.
Prueba ScrapeUnblocker gratis
Tasa de éxito del 95%+ · desde 0,55 € por cada 1000 llamadas · 500 solicitudes gratis al registrarte.