← Todos los artículos

HTTP 200 pero Bloqueado: Cómo Detectar Bloqueos Silenciosos en Web Scraping

Tu scraper termina una ejecución. Cada petición devolvió HTTP 200. Sin excepciones, sin reintentos, sin errores en el log. Luego abres el resultado y la mitad de los registros faltan, los precios están en blanco y algunas filas contienen la frase “por favor, verifica que eres humano”. El código de estado te mintió.

Este es uno de los modos de fallo más caros en web scraping, porque es silencioso. Un bloqueo duro (403, 429, una conexión reiniciada) es ruidoso: lo ves, lo gestionas. Un bloqueo silencioso devuelve 200 con un cuerpo que no es la página que pediste, así que tu parser se ejecuta, no encuentra nada y sigue adelante. El scraping “tiene éxito” mientras tu conjunto de datos se pudre en silencio.

Esta guía cubre cómo se ven los bloqueos silenciosos desde tu lado, por qué los sitios los sirven y cómo detectarlos de forma fiable con herramientas de código abierto antes de que los datos malos lleguen a tu base de datos.

Qué Es Realmente un Bloqueo Silencioso

Un bloqueo silencioso es cualquier respuesta que lleva un código de estado de éxito pero no contiene el contenido que solicitaste. El servidor decidió que pareces un bot, pero en lugar de rechazarte de plano, te devuelve otra cosa y la marca como 200 OK. Variantes comunes:

  • Una página de desafío o interstitial. El cuerpo es un desafío de JavaScript, una página de espera de “comprobando tu navegador” o un muro CAPTCHA. Estado: 200. Contenido: no son tus datos.
  • Un muro de consentimiento o de región. Aparece una pantalla de consentimiento de cookies o RGPD en lugar del artículo. El contenido real está detrás de una interacción que nunca realizaste.
  • Una página recortada o parcial. Obtienes la estructura de la página (cabecera, pie, diseño) pero la cuadrícula de productos, la tabla de anuncios o el cuerpo del artículo está vacío o truncado. A veces recibes 3 elementos cuando la página realmente tiene 60.
  • Una página genérica de “algo salió mal”. Una página de error amigable servida con 200 para que las herramientas de monitorización no la marquen.
  • Una respuesta señuelo o trampa. Datos falsos o aleatorios diseñados para parecer plausibles pero inútiles.

El hilo común: la capa HTTP dice que todo está bien, y solo el contenido dice la verdad.

Por Qué los Sitios Sirven 200 en Lugar de 403

Parece al revés. Si un sitio no quiere servirte, ¿por qué no decir simplemente que no? Tres razones prácticas:

  1. Rompe los scrapers ingenuos en silencio. Muchos scrapers solo comprueban response.status_code. Servir un 200 con basura significa que esos scrapers nunca activan su lógica de reintento o alerta. Tu pipeline se degrada en lugar de fallar, lo cual es más difícil de notar y de depurar.
  2. Protege a los usuarios reales. Los sistemas anti-bot no son perfectos y a veces marcan a visitantes genuinos. Un desafío suave (resuelve esto, haz clic aquí) es una alternativa más amable que un 403 duro que bloquearía a un cliente real.
  3. Enturbia tu señal. Si los bloqueos siempre volvieran como 403, podrías medir tu tasa de bloqueo con precisión y adaptarte. Mezclar los bloqueos en los 200 hace que tus métricas de éxito parezcan más sanas de lo que son, lo que ralentiza tu respuesta.

Entender el motivo importa porque te dice dónde mirar: nunca confíes solo en el código de estado y valida siempre el cuerpo.

Cómo Detectar Bloqueos Silenciosos

La detección se reduce a un principio: valida el contenido, no solo el transporte. Aquí tienes un enfoque por capas, empezando por las comprobaciones más baratas.

1. Comprueba el Tamaño de la Respuesta

La señal más rápida. Una página de producto real puede pesar 200-500 KB de HTML. Una página de desafío o una estructura recortada suele ser una fracción de eso. Registra una referencia para cada tipo de página y marca cualquier cosa que caiga muy por debajo.

import requests

resp = requests.get(url, headers=headers, timeout=20)
size = len(resp.content)

if size < 15_000:  # ajusta por sitio
    raise ValueError(f"Respuesta sospechosamente pequena: {size} bytes")

El tamaño por sí solo produce falsos positivos, así que trátalo como un primer filtro, no como un veredicto.

2. Busca Firmas de Bloqueo

Busca frases y marcadores que solo aparecen en páginas de desafío o error. Mantén una lista por sitio, porque la redacción varía.

BLOCK_MARKERS = [
    "verify you are human",
    "checking your browser",
    "enable javascript and cookies",
    "access denied",
    "unusual traffic",
    "captcha",
    "cf-challenge",
    "px-captcha",
]

body = resp.text.lower()
if any(marker in body for marker in BLOCK_MARKERS):
    raise ValueError("Firma de bloqueo encontrada en el cuerpo")

Esto captura la mayoría de las páginas interstitial y de desafío. Actualiza la lista cada vez que veas un muro nuevo.

3. Afirma Sobre el Contenido que Esperas

La comprobación más fiable es positiva, no negativa. No te limites a buscar señales de bloqueo: confirma que aquello que buscas está realmente presente. Si scrapeas una página de producto, el selector del precio debe resolver. Si scrapeas una página de anuncios, el número de elementos debe estar dentro de un rango razonable.

from parsel import Selector

sel = Selector(resp.text)
items = sel.css("div.product-card")

if len(items) < 5:
    raise ValueError(f"Solo {len(items)} elementos - se esperaba una pagina completa")

price = sel.css("span.price::text").get()
if not price:
    raise ValueError("Selector de precio vacio - pagina probablemente incompleta")

Esto es lo que captura la variante más desagradable: la página parcial. Una respuesta recortada no tiene marcadores de bloqueo y puede tener un tamaño normal, pero los datos simplemente no están. Solo una afirmación positiva lo detecta.

4. Detecta Páginas Parciales e Inconsistentes

Algunos sitios devuelven la página completa la mayoría de las veces y una versión truncada al azar, especialmente bajo carga o cuando sospechan de automatización. La misma URL te da 60 elementos en una petición y 3 en la siguiente, ambas con estado 200.

Defiéndete con una heurística de completitud y un reintento acotado:

def fetch_complete(url, min_items=20, attempts=3):
    best = None
    for _ in range(attempts):
        resp = requests.get(url, headers=headers, timeout=20)
        sel = Selector(resp.text)
        items = sel.css("div.product-card")
        if best is None or len(items) > len(best):
            best = items
        if len(items) >= min_items:
            return items  # suficientemente bueno, para pronto
    return best  # devuelve el render mas completo que vimos

Quedarte con el más completo de varios intentos, en lugar del primero, convierte las respuestas parciales inestables en datos utilizables.

5. Compara con una Referencia Conocida

Para páginas de alto valor, guarda una instantánea de una respuesta sana: su tamaño, su número de elementos, la presencia de selectores clave. En cada ejecución, compara con la referencia y alerta cuando la forma se desvíe. Una caída repentina del 80% en el número de elementos en muchas páginas es una señal de bloqueo mucho mejor que cualquier petición aislada.

Convierte la Detección en un Guardián Reutilizable

No disperses estas comprobaciones por tus spiders. Envuélvelas en una única función de validación por la que pase cada respuesta antes de parsear. Falla ruidosamente (lanza una excepción, registra la URL y enruta a una cola de reintentos) para que un bloqueo silencioso se convierta en un evento visible en lugar de una pérdida silenciosa de datos.

def validate(resp, min_items=5):
    if len(resp.content) < 15_000:
        raise ValueError("respuesta demasiado pequena")
    body = resp.text.lower()
    if any(m in body for m in BLOCK_MARKERS):
        raise ValueError("firma de bloqueo")
    sel = Selector(resp.text)
    if len(sel.css("div.product-card")) < min_items:
        raise ValueError("contenido ausente")
    return sel

Un único punto de control significa un solo lugar para ajustar umbrales y añadir nuevas firmas a medida que los sitios cambian.

Cuándo la Detección No Basta

La detección te dice que una petición falló. No arregla la razón subyacente por la que te marcaron. Si tu tasa de bloqueo validada sube de forma sostenida (digamos del 1% al 8% en una semana en muchos objetivos), eso suele ser el sitio endureciendo su postura anti-bot, no un fallo en tu parser. En ese punto, las palancas del lado del lector son las de siempre: rotar IPs, usar huellas de navegador realistas con herramientas como curl_cffi o un navegador indetectable, añadir retrasos similares a los humanos y renderizar JavaScript con Playwright o Selenium cuando el contenido lo requiera.

A cierta escala, mantener todo eso por tu cuenta se convierte en un proyecto en sí mismo. Un servicio de desbloqueo gestionado como ScrapeUnblocker se encarga de las huellas, la rotación y el renderizado detrás de una sola llamada a la API, de modo que recuperas la página real y puedes centrar tu lógica de validación en la calidad de los datos en lugar de en evitar bloqueos. Puedes leer más sobre cómo encaja en un stack de scraping en la documentación para desarrolladores.

Preguntas Frecuentes

¿Un código de estado 200 significa que mi scraping funcionó? No. Un 200 solo significa que el servidor respondió con algo. No dice nada sobre si ese algo es el contenido que querías. Valida siempre el cuerpo.

¿Cómo distingo un bloqueo silencioso de una página genuinamente vacía? Usa una afirmación positiva ligada al tipo de página y una referencia. Si una página de categoría normalmente tiene 50 elementos y devuelve 0, eso es un bloqueo o un fallo del parser, no una categoría vacía. Cruza la información con el tamaño de la respuesta y las firmas de bloqueo.

¿Por qué obtengo resultados distintos para la misma URL? Algunos sitios sirven respuestas parciales o aleatorias bajo carga o cuando sospechan de automatización. Haz varias peticiones y quédate con el render más completo, y registra la variación para detectar cuándo empeora.

¿Debería reintentar un bloqueo silencioso de inmediato? Un reintento acotado (2-3 intentos) ayuda con las páginas parciales inestables. Si el bloqueo persiste, reintentar de la misma manera solo desperdicia peticiones: cambia algo primero, como tu IP, tus cabeceras o tu enfoque de renderizado.

Puntos Clave

  • Los códigos de estado describen el transporte, no el contenido. Nunca confíes solo en el 200.
  • Combina tus comprobaciones: tamaño de respuesta, firmas de bloqueo y, sobre todo, afirmaciones positivas sobre los datos que esperas.
  • Las páginas parciales son el bloqueo silencioso más escurridizo. Solo una comprobación de completitud del contenido las detecta.
  • Centraliza la validación en un único guardián para que cada respuesta se verifique antes de llegar a tu parser.
  • Una tasa de bloqueo validada creciente es una señal para cambiar de enfoque, no solo para reintentar con más fuerza.

Trata cada respuesta como culpable hasta que se demuestre que está completa, y los bloqueos silenciosos dejarán de ser silenciosos.

Prueba ScrapeUnblocker gratis

Tasa de éxito del 95%+ · desde 0,55 € por cada 1000 llamadas · 500 solicitudes gratis al registrarte.

Pruébalo gratis → Ver precios