Scrapea sin que te bloqueen: guía práctica con código
Que te bloqueen mientras haces scraping suele significar una de tres cosas: enviaste demasiadas peticiones demasiado rápido, tu cliente no se parecía en nada a un navegador o a un bot conocido, o tu dirección IP está en una lista en la que el sitio confía menos. Puedes reducir las tres con unos pocos hábitos: detectar el bloqueo pronto, ir más despacio cuando el sitio te presiona, mantener una identidad coherente y honesta, y usar proxies solo cuando sea apropiado. Esta guía recorre cada uno con código que funciona. Úsala para sitios que tienes permiso para scrapear.
Reconoce un bloqueo
Un bloqueo no siempre parece una página de error. Fíjate en:
- 403 Forbidden, que a menudo significa que el sitio ha rechazado a tu cliente. Para y comprueba si tienes permiso para estar ahí antes de volver a intentarlo.
- 429 Too Many Requests, que significa que envías demasiado. Suele venir con una cabecera
Retry-After. - 503 Service Unavailable, que puede significar que el sitio está sobrecargado. Retrocede.
- Una respuesta 200 con casi ningún contenido, una redirección a una página de inicio de sesión o de desafío, o una página que no tiene los datos que esperabas.
Registra el código de estado y una muestra corta del cuerpo para cada fallo. Un bloqueo que no ves en tus registros es un bloqueo que no puedes arreglar.
Retrocede con reintentos
Cuando el sitio dice "ve más despacio", tu código debe ir más despacio que un poco. El retroceso exponencial, con un límite de intentos, es el enfoque estándar. También respeta Retry-After cuando el sitio lo envía:
import time
import requests
session = requests.Session()
session.headers.update({
"User-Agent": "ExampleBot/1.0 (+https://example.com/bot)",
"Accept-Language": "en-US,en;q=0.9",
})
def polite_get(url: str, max_attempts: int = 4) -> requests.Response:
delay = 2.0
resp = None
for _ in range(max_attempts):
resp = session.get(url, timeout=30)
if resp.status_code not in (429, 503):
return resp
retry_after = resp.headers.get("Retry-After", "")
wait = float(retry_after) if retry_after.isdigit() else delay
time.sleep(wait)
delay *= 2
return resp # sigue bloqueado: devuelve la última respuesta para que la gestione el llamador
resp = polite_get("https://example.com/products?page=1")
print(resp.status_code, len(resp.text))
Fíjate en lo que la función no hace. No reintenta un 403, y no rota identidades para saltarse un bloqueo. Un 403 es una decisión de parar, no un rompecabezas que resolver.
Diagnostica un bloqueo paso a paso
Cuando una ejecución empieza a fallar, revisa estos puntos antes de cambiar nada más:
- Mira la primera URL que falla y su código de estado. Un bloqueo suele empezar en un punto concreto, no al azar.
- Compara el ritmo de peticiones en los minutos previos al primer fallo. Una ráfaga es la causa más común.
- Comprueba si el fallo está ligado a una ruta, a una sección o a una hora del día.
- Abre la misma URL a mano una vez en un navegador normal. Si el navegador la carga, la diferencia está en el patrón de tus peticiones. Si el navegador también está bloqueado, el sitio ha dejado de servirte la página por ahora.
- Solo entonces cambia el código. Primero ve más despacio, después añade retroceso, y por último revisa tu identidad.
Cambiar varias cosas a la vez hace imposible saber cuál ayudó.
Mantén una identidad coherente y honesta
Los scrapers se bloquean cuando su identidad cambia entre peticiones o cuando no coincide con el resto del tráfico. Una sola sesión con un User-Agent claro es la identidad estable más sencilla. Pon nombre a tu bot, añade un enlace de contacto y envía solo las cabeceras que enviaría un cliente normal. Cambiar los User-Agents para parecer muchos visitantes distintos es una forma de evasión, y tiende a hacer más probable un bloqueo, no menos.
Si un sitio sirve contenido distinto a los navegadores que a los clientes HTTP simples, usa un navegador real para esas páginas y mantén el mismo límite de ritmo. Es una ruta legítima, y mantiene el patrón de peticiones sensato.
Usa proxies con cuidado
Un proxy cambia la dirección desde la que sale una petición. Es una herramienta para necesidades legítimas, como probar desde una región concreta en un sitio que tienes permiso para probar, o repartir la carga entre tu propia infraestructura. No es una forma de escapar de un límite que el sitio te haya impuesto. El formato de un proxy es el que usan los tipos de tarea de proxy:
proxies = {
"http": "http://user:pass@proxy.example.net:8000",
"https": "http://user:pass@proxy.example.net:8000",
}
resp = session.get("https://example.com/products", proxies=proxies, timeout=30)
Elegir un tipo de proxy es una cuestión aparte, que se trata en proxies residenciales frente a datacenter y proxies rotatorios para scraping.
Cuando el bloqueo se convierte en captcha
Algunos sitios responden a un problema de ritmo con un captcha en lugar de un bloqueo duro. El orden sensato es: parar, ir más despacio, revisar las normas de nuevo y solo entonces considerar una resolución. Para los widgets de PopularCaptcha, la API activa de Scarnox devuelve un token que envías en el campo h-captcha-response. Para otros tipos de captcha, consulta la lista activa, porque el soporte de reCAPTCHA, Cloudflare Turnstile, GeeTest, AWS WAF y Arkose llegará pronto. La guía detallada es scraping de sitios web y captchas.
Si empiezas a ver bloqueos en un sitio, la causa casi siempre es el patrón de peticiones. Evitar captchas al hacer scraping cubre los hábitos que lo previenen.
Scrapea de forma responsable
- Revisa el
robots.txty los términos del sitio antes de empezar, y sigue las rutas y los retardos que pidan. - Para y deja de insistir cuando recibas un 403 o un mensaje claro de "no scrapear".
- Mantén tu ritmo lo bastante bajo como para que un visitante normal no notara tu tráfico.
- No recopiles datos personales que no tienes motivo para conservar, y trata los que recopiles según las normas de privacidad que te apliquen.
- Si necesitas mucho más volumen del que un sitio puede servir con comodidad, pide al propietario un feed o una API.