Algunos sitios usan reCAPTCHA en su página de inicio de sesión y Turnstile en su página de pago. Otros prueban A/B entre proveedores: la misma URL muestra reCAPTCHA para un visitante y Turnstile para otro. Codificar un único método de resolución significa que su automatización se interrumpe cada vez que encuentra el otro tipo.
Por qué los sitios utilizan varios proveedores de CAPTCHA
| Escenario | Cómo aparece |
|---|---|
| Diferentes páginas, diferentes proveedores | Iniciar sesión = reCAPTCHA, finalizar compra = Torniquete |
| Proveedores de pruebas A/B | La misma página muestra aleatoriamente cualquier tipo |
| Migración en progreso | Las páginas antiguas tienen reCAPTCHA, las páginas nuevas tienen Turnstile |
| Repliegue ante el fracaso | El proveedor primario falla → vuelve al secundario |
| Variación regional | reCAPTCHA para visitantes de EE. UU., Turnstile para la UE (GDPR) |
Python: detección automática y resolución
import requests
import time
import re
from dataclasses import dataclass
API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
@dataclass
class CaptchaInfo:
provider: str # "recaptcha" or "turnstile"
method: str # API method name
sitekey: str
pageurl: str
response_field: str # Form field name for the token
def detect_captcha_type(html, pageurl):
"""
Detect which CAPTCHA provider is on the page.
Returns CaptchaInfo or None.
"""
# Check for Turnstile
turnstile_match = re.search(
r'class=["\'][^"\']*cf-turnstile[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)["\']',
html,
)
if not turnstile_match:
turnstile_match = re.search(
r'data-sitekey=["\']([^"\']+)["\'][^>]*class=["\'][^"\']*cf-turnstile',
html,
)
if turnstile_match:
return CaptchaInfo(
provider="turnstile",
method="turnstile",
sitekey=turnstile_match.group(1),
pageurl=pageurl,
response_field="cf-turnstile-response",
)
# Check for reCAPTCHA
recaptcha_match = re.search(
r'class=["\'][^"\']*g-recaptcha[^"\']*["\'][^>]*data-sitekey=["\']([^"\']+)["\']',
html,
)
if not recaptcha_match:
recaptcha_match = re.search(
r'data-sitekey=["\']([^"\']+)["\'][^>]*class=["\'][^"\']*g-recaptcha',
html,
)
# Also check for script-rendered reCAPTCHA
if not recaptcha_match:
recaptcha_match = re.search(
r'grecaptcha\.render\([^,]+,\s*\{[^}]*["\']sitekey["\']\s*:\s*["\']([^"\']+)["\']',
html,
)
if recaptcha_match:
return CaptchaInfo(
provider="recaptcha",
method="userrecaptcha",
sitekey=recaptcha_match.group(1),
pageurl=pageurl,
response_field="g-recaptcha-response",
)
return None
def solve_captcha(info):
"""Solve any detected CAPTCHA type via CaptchaAI."""
params = {
"key": API_KEY,
"method": info.method,
"json": 1,
}
if info.method == "userrecaptcha":
params["googlekey"] = info.sitekey
params["pageurl"] = info.pageurl
elif info.method == "turnstile":
params["sitekey"] = info.sitekey
params["pageurl"] = info.pageurl
resp = requests.post(SUBMIT_URL, data=params, timeout=30).json()
if resp.get("status") != 1:
raise RuntimeError(f"Submit failed: {resp.get('request')}")
task_id = resp["request"]
for _ in range(60):
time.sleep(5)
poll = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get",
"id": task_id, "json": 1,
}, timeout=15).json()
if poll.get("request") == "CAPCHA_NOT_READY":
continue
if poll.get("status") == 1:
return poll["request"]
raise RuntimeError(f"Solve failed: {poll.get('request')}")
raise RuntimeError("Timeout")
def process_page(session, url):
"""Fetch page, detect CAPTCHA type, solve, and return form-ready data."""
response = session.get(url)
captcha_info = detect_captcha_type(response.text, url)
if not captcha_info:
print(f"No CAPTCHA detected on {url}")
return None
print(f"Detected {captcha_info.provider} on {url}")
print(f" Sitekey: {captcha_info.sitekey[:30]}...")
token = solve_captcha(captcha_info)
print(f" Solved: {token[:30]}...")
return {
"provider": captcha_info.provider,
"response_field": captcha_info.response_field,
"token": token,
}
# Usage: Handle multiple pages with different providers
session = requests.Session()
pages = [
"https://staging.example.com/qa-login", # Might have reCAPTCHA
"https://example.com/checkout", # Might have Turnstile
]
for url in pages:
result = process_page(session, url)
if result:
form_data = {result["response_field"]: result["token"]}
# Add other form fields...
# session.post(url, data=form_data)
JavaScript: Detección dinámica de CAPTCHA
const API_KEY = "YOUR_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
function detectCaptchaType(html, pageurl) {
// Turnstile
const turnstileMatch = html.match(/cf-turnstile[^>]*data-sitekey=["']([^"']+)["']/);
if (turnstileMatch) {
return { provider: "turnstile", method: "turnstile", sitekey: turnstileMatch[1], pageurl, field: "cf-turnstile-response" };
}
// reCAPTCHA
const recaptchaMatch = html.match(/g-recaptcha[^>]*data-sitekey=["']([^"']+)["']/);
if (recaptchaMatch) {
return { provider: "recaptcha", method: "userrecaptcha", sitekey: recaptchaMatch[1], pageurl, field: "g-recaptcha-response" };
}
// Script-rendered reCAPTCHA
const scriptMatch = html.match(/sitekey["']\s*:\s*["']([^"']+)["']/);
if (scriptMatch) {
return { provider: "recaptcha", method: "userrecaptcha", sitekey: scriptMatch[1], pageurl, field: "g-recaptcha-response" };
}
return null;
}
async function solveCaptcha(info) {
const body = new URLSearchParams({ key: API_KEY, method: info.method, json: "1" });
if (info.method === "userrecaptcha") { body.set("googlekey", info.sitekey); body.set("pageurl", info.pageurl); }
else if (info.method === "turnstile") { body.set("sitekey", info.sitekey); body.set("pageurl", info.pageurl); }
const resp = await (await fetch(SUBMIT_URL, { method: "POST", body })).json();
if (resp.status !== 1) throw new Error(`Submit: ${resp.request}`);
const taskId = resp.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const url = `${RESULT_URL}?key=${API_KEY}&action=get&id=${taskId}&json=1`;
const poll = await (await fetch(url)).json();
if (poll.request === "CAPCHA_NOT_READY") continue;
if (poll.status === 1) return poll.request;
throw new Error(`Solve: ${poll.request}`);
}
throw new Error("Timeout");
}
async function processPage(url) {
const response = await fetch(url);
const html = await response.text();
const info = detectCaptchaType(html, url);
if (!info) { console.log(`No CAPTCHA on ${url}`); return null; }
console.log(`${info.provider} detected on ${url}`);
const token = await solveCaptcha(info);
return { provider: info.provider, field: info.field, token };
}
// Usage
const pages = ["https://staging.example.com/qa-login", "https://example.com/checkout"];
for (const url of pages) {
const result = await processPage(url);
if (result) {
console.log(`Solved ${result.provider}: ${result.token.substring(0, 30)}...`);
}
}
Referencia de detección de proveedores
| Proveedor | Marcador HTML | URL del guión | Campo de respuesta |
|---|---|---|---|
| reCAPTCHA v2 | class="g-recaptcha" |
google.com/recaptcha/api.js |
g-recaptcha-response |
| Cloudflare Turnstile | class="cf-turnstile" |
challenges.cloudflare.com/turnstile |
cf-turnstile-response |
| hCaptcha | class="h-captcha" |
js.hcaptcha.com/1/api.js |
h-captcha-response |
Solución de problemas
| Problema | causa | Solución |
|---|---|---|
| Se detectó un tipo de CAPTCHA incorrecto | Regex coincide con el elemento incorrecto | Primero verifique los nombres de clases específicos del proveedor, no solo data-sitekey |
| Token rechazado después de una resolución correcta | Se utilizó un nombre de campo de respuesta incorrecto | Haga coincidir el nombre del campo con el proveedor: g-recaptcha-response vs cf-turnstile-response |
| El tipo de CAPTCHA cambia entre visitas | Prueba A/B o selección basada en geografía | Detectar siempre dinámicamente; nunca codifique el proveedor |
| Ambos proveedores detectados en una página. | Uno puede estar oculto/inactive | Verifique la visibilidad del elemento: resuelva solo el CAPTCHA visible |
| La detección falla para CAPTCHA renderizados por script | No hay marcador HTML en la fuente | Verifique las llamadas grecaptcha.render() o turnstile.render() en los scripts |
Preguntas frecuentes
¿Puede una página usar reCAPTCHA y Turnstile simultáneamente?
Rara vez en el mismo formulario, pero ocurre en diferentes partes de un sitio. Si ambos aparecen en una página, normalmente solo uno está activo: verifique qué widget está visible y tiene un data-sitekey que no esté vacío.
¿CaptchaAI utiliza la misma API para ambos proveedores?
Los mismos puntos finales (in.php / res.php) pero diferentes valores de method. reCAPTCHA usa method=userrecaptcha con googlekey, mientras que Turnstile usa method=turnstile con sitekey. El patrón de detección automática maneja esta asignación.
¿Cómo manejo la conmutación por error del proveedor?
Si un sitio cambia de reCAPTCHA a Turnstile a mitad de sesión (por ejemplo, después de que reCAPTCHA no se carga), vuelva a detectar el tipo de CAPTCHA en cada solicitud de página en lugar de almacenar en caché el proveedor desde la primera visita.
Artículos relacionados
- Cómo resolver la devolución de llamada de Recaptcha V2 usando Api
- Cloudflare Challenge Vs Detección de torniquete
- Comparación de Geetest y Cloudflare Turnstile
Próximos pasos
Manejar sitios con múltiples proveedores CAPTCHA:obtenga su clave API CaptchaAIe implementar la detección automática.
Guías relacionadas:
- Manejo de múltiples CAPTCHA en una sola página
- Cadenas CAPTCHA: formularios secuenciales de varios pasos
- CAPTCHA en modales emergentes: detección e inyección