Solución de Problemas

Cloudflare Challenge vs Turnstile: cómo detectar cuál tienes

La respuesta rápida, en dos casos:

  • Te tapa la página entera antes de ver el contenido → es Cloudflare Challenge, y tu método es cloudflare_challenge.
  • Aparece como una casilla dentro de un formulario ya cargado → es Turnstile, y tu método es turnstile.

No hay término medio y no hay autodetección: el método viaja en tu solicitud. Por eso un ERROR_CAPTCHA_UNSOLVABLE repetido casi nunca significa que el desafío sea imposible; significa que estás describiendo una protección que el sitio no tiene.


Señales de un Cloudflare Challenge

El Challenge es un intersticial: lo ves antes de que exista la página real. Suele responder con 403 o 503, y el HTML que recibes no es el del sitio, sino el de Cloudflare.

En el navegador:

  • Mensaje del tipo "Comprueba tu navegador antes de acceder…"
  • Rueda giratoria de carga de Cloudflare
  • El título de la pestaña contiene "Just a moment…"
  • La URL no cambia, pero el contenido sí

En el HTML:

<!-- Cloudflare Challenge page indicators -->
<title>Just a moment...</title>
<div id="challenge-running">...</div>
<div class="cf-browser-verification">...</div>

Detección programática:

import requests

resp = requests.get("https://example.com")

is_cf_challenge = (
    resp.status_code == 403 and
    "Just a moment" in resp.text or
    "cf-browser-verification" in resp.text or
    "challenge-platform" in resp.text
)

if is_cf_challenge:
    print("Cloudflare Challenge detectado — use method: cloudflare_challenge")

Ojo: el marcador del título sigue en inglés aunque el portal esté en español. Cloudflare sirve su intersticial sin localizarlo, así que buscar "Un momento…" no encuentra nada.


Señales de Cloudflare Turnstile

Turnstile vive dentro de una página que ya se cargó por completo, igual que un reCAPTCHA. Si puedes leer el contenido del sitio y solo el botón de enviar está condicionado, es Turnstile.

En el navegador:

  • Widget pequeño de Cloudflare dentro de un formulario
  • El resto de la página se ve con normalidad
  • El widget dice "Verifica que eres humano"

En el HTML:

<!-- Turnstile widget indicators -->
<div class="cf-turnstile" data-sitekey="0x4AAAAAAADnPIDROz1234"></div>

<!-- Or loaded via JavaScript -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js"></script>

Detección programática:

import requests

resp = requests.get("https://staging.example.com/qa-login")

is_turnstile = (
    "cf-turnstile" in resp.text or
    "challenges.cloudflare.com/turnstile" in resp.text
)

if is_turnstile:
    # Extract sitekey
    if 'data-sitekey="' in resp.text:
        start = resp.text.index('data-sitekey="') + 14
        end = resp.text.index('"', start)
        sitekey = resp.text[start:end]
        print(f"Turnstile detected — sitekey: {sitekey}")
        print("Use method: turnstile")

Cuidado con los widgets renderizados por JavaScript: si el sitekey no aparece en el HTML que devuelve requests, carga la página con un navegador headless y lee el DOM ya renderizado.


Qué cambia en tu código según cuál sea

Ya sabes cuál tienes delante. Esta es la consecuencia práctica, campo por campo:

Característica Cloudflare Challenge Cloudflare Turnstile
Tipo Intersticial de página completa Widget integrado
Apariencia Bloquea toda la página, "Revisando tu navegador" Casilla pequeña o widget invisible dentro de un formulario
Método CaptchaAI cloudflare_challenge turnstile
Devuelve Cookie qa_validation_cookie Cadena de token
Requiere proxy Sí (obligatorio) Recomendado
Requiere User-Agent Sí (obligatorio) No

La diferencia de fondo es la última columna de arriba: qué devuelve cada uno. El Challenge entrega una cookie de sesión atada a la IP que la generó, y de ahí salen las dos casillas obligatorias. Turnstile entrega un token de un solo uso, independiente de tu IP, que inyectas en el formulario y caduca al usarlo.


Una función que decide el método por ti

En producción no quieres inspeccionar cada dominio a mano. Esta función devuelve directamente el método que debes enviar:

def detect_cloudflare_type(url):
    """Detect which Cloudflare protection a URL uses."""
    resp = requests.get(url, allow_redirects=True)

    # Check for full-page Challenge
    if resp.status_code in [403, 503]:
        if any(marker in resp.text for marker in [
            "Just a moment",
            "cf-browser-verification",
            "challenge-platform",
            "challenge-running"
        ]):
            return {"type": "cloudflare_challenge", "method": "cloudflare_challenge"}

    # Check for embedded Turnstile
    if "cf-turnstile" in resp.text or "challenges.cloudflare.com/turnstile" in resp.text:
        sitekey = None
        if 'data-sitekey="' in resp.text:
            start = resp.text.index('data-sitekey="') + 14
            end = resp.text.index('"', start)
            sitekey = resp.text[start:end]
        return {"type": "turnstile", "method": "turnstile", "sitekey": sitekey}

    return {"type": "none", "method": None}


# Usage
result = detect_cloudflare_type("https://example.com")
print(f"Protection: {result['type']}")
print(f"CaptchaAI method: {result['method']}")

Guarda el resultado en caché por dominio con un TTL corto: las configuraciones de Cloudflare cambian, pero no cada minuto.


Los cuatro errores que explican casi todos los fallos

Antes del código correcto conviene mirar el equivocado, que es probablemente el que ya tienes en producción:

Error Resultado Solución
Usar turnstile en una página de Challenge ERROR_CAPTCHA_UNSOLVABLE Cambia a cloudflare_challenge
Usar cloudflare_challenge con Turnstile Formato de respuesta incorrecto Cambia a turnstile
Enviar el Challenge sin proxy ERROR_PROXY_NOT_AUTHORIZED Añade el proxy: es obligatorio en el Challenge
Enviar Turnstile sin sitekey ERROR_BAD_PARAMETERS Extrae el sitekey del HTML de la página

Si tu código no aparece aquí, la referencia de códigos de error trae el listado completo.


Cómo se resuelve cada uno

Los tres campos marcados como obligatorios no lo son por formalidad: sin ellos, la cookie que recibes no sirve desde tu salida de red.

data = {
    "key": "YOUR_API_KEY",
    "method": "cloudflare_challenge",
    "pageurl": "https://example.com",
    "proxy": "host:port:user:pass",   # MANDATORY
    "proxytype": "HTTP",               # MANDATORY
    "userAgent": "Mozilla/5.0 ...",    # MANDATORY
    "json": 1
}
# Returns qa_validation_cookie cookie — set it in your session

Turnstile: sitekey y token

Mucho más simple: sitekey y URL de la página. El token vuelve y lo colocas en el campo cf-turnstile-response antes de enviar el formulario.

data = {
    "key": "YOUR_API_KEY",
    "method": "turnstile",
    "sitekey": "0x4AAAAAAADnPIDROz1234",
    "pageurl": "https://staging.example.com/qa-login",
    "json": 1
}
# Returns a token — inject into cf-turnstile-response field

Ambos métodos consumen threads del mismo plan: no hay recargo por tipo de CAPTCHA. Con BASIC ($15/mes, 5 threads) mantienes cinco resoluciones simultáneas; si recorres varios portales en paralelo, ADVANCE ($90/mes, 50 threads) es el salto habitual.


Un caso concreto: un portal público y un marketplace en el mismo scraper

Un equipo en Ciudad de México vigila dos destinos con el mismo proceso: las citas de un portal de trámites públicos y los precios de su propio catálogo en un marketplace regional. Ambos están detrás de Cloudflare, así que el equipo asume que se resuelven igual. No es así:

  • El portal de trámites levanta el intersticial ante cualquier cliente que no parezca un navegador → cloudflare_challenge, con proxy y User-Agent declarados y la cookie guardada en la sesión.
  • El marketplace deja ver el catálogo sin fricción y solo pone Turnstile en el acceso al panel de vendedor → turnstile, sin proxy obligatorio y un token por envío.

Una llamada a detect_cloudflare_type() al inicio de cada ejecución enruta ambos destinos. Y una nota que nunca sobra: revisa los términos de servicio del sitio y la normativa de protección de datos aplicable —GDPR y LOPDGDD en España, LFPDPPP en México— antes de automatizar cualquier recolección.


Preguntas frecuentes

La detección no encuentra el sitekey, pero en el navegador veo el widget. ¿Por qué?

Porque el widget se monta por JavaScript y requests solo ve el HTML inicial. Carga esa URL con un navegador headless y lee el data-sitekey del DOM ya renderizado.

¿Puedo reutilizar el token de Turnstile en varias solicitudes?

No. Es de un solo uso y caduca en pocos minutos: pide uno nuevo por cada envío del formulario.

¿Puede un mismo sitio tener las dos protecciones a la vez?

Sí, y es más frecuente de lo que parece: Challenge en la entrada al dominio y Turnstile en un formulario interno. Resuelve primero el Challenge, conserva la cookie en la sesión y solo entonces pide el token de Turnstile.

¿Sirve este mismo enfoque para reCAPTCHA o GeeTest?

El razonamiento sí; los parámetros no. Cada familia —reCAPTCHA v2, GeeTest v3— tiene sus propios marcadores en el HTML y su propio método, así que el detect_ de arriba se amplía con un marcador por familia. GeeTest v4 aún no está disponible en CaptchaAI; figura como próximamente.

¿Por qué el Challenge necesita proxy y Turnstile no?

Porque la cookie qa_validation_cookie queda vinculada a la IP que la generó. Si se resuelve desde una salida de red distinta a la tuya, el sitio la rechaza. El token de Turnstile, en cambio, es independiente de la IP.


Empieza a resolver Cloudflare hoy

Crea tu cuenta en captchaai.com y ejecuta tu primera detección con el método correcto en la misma sesión.


Guías relacionadas

Los comentarios están deshabilitados para este artículo.