Para resolver un CAPTCHA deslizante de GeeTest v3 con la API de CaptchaAI solo necesitas tres datos del sitio: gt, challenge y pageurl. Saber cuáles son es lo fácil; lo que hace fallar a casi todos es que el challenge caduca en segundos. Esta guía recorre el flujo completo con código en Python. Es un patrón habitual en el login de portales de trámites de la región.
El flujo de resolución en tres pasos
Antes de entrar en el código, este es el recorrido de principio a fin:
- Extraes
gty unchallengefresco de la página objetivo. - Envías ambos valores a la API de CaptchaAI y sondeas hasta recibir la solución.
- Devuelves la terna
challenge+validate+seccodeal endpoint de validación del sitio.
Cada paso depende del anterior y el reloj empieza a correr en cuanto pides el challenge, así que conviene tener los tres tramos encadenados antes de lanzar el primero. Verás este mismo patrón en portales de cita previa, trámites de la administración pública y paneles de proveedores que protegen el acceso con GeeTest v3.
Los cuatro parámetros de GeeTest v3
| Parámetro | ¿Obligatorio? | Qué es |
|---|---|---|
gt |
Sí | ID de cuenta de GeeTest (hexadecimal de 32 caracteres). Aparece en el HTML o en la respuesta de la API. |
challenge |
Sí | Cadena de desafío propia de cada sesión. Tiene que ir fresca en cada resolución. |
pageurl |
Sí | URL completa de la página donde se muestra el CAPTCHA. |
api_server |
No | Subdominio del servidor de GeeTest cuando el sitio usa uno personalizado. |
Paso 1: extraer gt y challenge de la página
El gt suele estar en el HTML; el challenge llega de una llamada register-slide. Este extractor prueba ambas vías:
# extract_geetest_params.py
import requests
import re
import json
def extract_geetest_v3(page_url, session=None):
"""Extract GeeTest v3 gt and challenge from a page."""
if session is None:
session = requests.Session()
session.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36"
)
resp = session.get(page_url, timeout=15)
html = resp.text
# Method 1: Extract gt from HTML
gt_match = re.search(r'gt["\']?\s*[:=]\s*["\']([a-f0-9]{32})', html)
gt = gt_match.group(1) if gt_match else None
# Method 2: Find API endpoint that returns challenge
api_match = re.search(r'(https?://[^"\']+register-slide[^"\']*)', html)
challenge = None
if api_match:
api_url = api_match.group(1)
api_resp = session.get(api_url, timeout=10)
try:
data = api_resp.json()
challenge = data.get("challenge")
gt = gt or data.get("gt")
except json.JSONDecodeError:
pass
if not challenge:
# Try embedded challenge
ch_match = re.search(r'challenge["\']?\s*[:=]\s*["\']([a-f0-9]+)', html)
challenge = ch_match.group(1) if ch_match else None
return {"gt": gt, "challenge": challenge, "pageurl": page_url}
# Usage
params = extract_geetest_v3("https://staging.example.com/qa-login")
print(f"gt: {params['gt']}")
print(f"challenge: {params['challenge']}")
Paso 2: enviar el desafío a CaptchaAI
Envías la tarea a in.php y consultas el resultado en res.php:
# solve_geetest.py
import requests
import time
import os
def solve_geetest(gt, challenge, pageurl, api_server=None):
"""Solve GeeTest v3 slide CAPTCHA via CaptchaAI."""
api_key = os.environ["CAPTCHAAI_API_KEY"]
payload = {
"key": api_key,
"method": "geetest",
"gt": gt,
"challenge": challenge,
"pageurl": pageurl,
"json": 1,
}
if api_server:
payload["api_server"] = api_server
# Submit
resp = requests.post(
"https://ocr.captchaai.com/in.php",
data=payload,
timeout=30,
)
result = resp.json()
if result.get("status") != 1:
raise RuntimeError(f"Submit failed: {result.get('request')}")
task_id = result["request"]
# Poll — GeeTest typically solves in 10-20 seconds
time.sleep(10)
for _ in range(30):
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key,
"action": "get",
"id": task_id,
"json": 1,
}, timeout=15)
data = resp.json()
if data.get("status") == 1:
return data["request"] # Returns challenge, validate, seccode
if data["request"] != "CAPCHA_NOT_READY":
raise RuntimeError(data["request"])
time.sleep(5)
raise TimeoutError("GeeTest solve timeout")
Un GeeTest v3 se resuelve en 10-20 segundos: por eso se espera 10 s antes del primer sondeo.
Paso 3: devolver la solución al sitio
CaptchaAI devuelve tres valores —challenge, validate y seccode— que reenvías al endpoint de validación del sitio. Si falta cualquiera, se rechaza:
# submit_solution.py
import json
def submit_geetest_solution(session, validation_url, solution, original_challenge):
"""Submit GeeTest solution to the target site."""
# Parse solution if string
if isinstance(solution, str):
solution = json.loads(solution)
payload = {
"geetest_challenge": solution.get("challenge", original_challenge),
"geetest_validate": solution.get("validate", ""),
"geetest_seccode": solution.get("seccode", ""),
}
resp = session.post(validation_url, data=payload, timeout=30)
return resp
# Complete flow
def full_geetest_flow(page_url, validation_url):
import requests
from extract_geetest_params import extract_geetest_v3
session = requests.Session()
session.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36"
)
# Step 1: Extract parameters
params = extract_geetest_v3(page_url, session)
print(f"gt: {params['gt']}, challenge: {params['challenge'][:16]}...")
# Step 2: Solve
solution = solve_geetest(
params["gt"], params["challenge"], params["pageurl"],
)
print("Solved!")
# Step 3: Submit
resp = submit_geetest_solution(
session, validation_url, solution, params["challenge"],
)
print(f"Validation response: {resp.status_code}")
return resp
Mantén la misma sesión de principio a fin
El sitio ata el challenge a las cookies con las que lo pediste. Si lo extraes con una session y envías la solución con otra, el servidor ve un cliente distinto y rechaza la validación aunque los tres campos vayan correctos. Por eso el mismo objeto requests.Session() recorre las tres fases —extracción, resolución y envío— sin recrearse por el camino. Es el segundo motivo de fallos más habitual, justo detrás del challenge caducado.
Por qué el challenge caduca en segundos
El challenge expira muy rápido: si lo envías un minuto después, ya no sirve. Pídelo justo antes de resolver:
# fresh_challenge.py
import time
def get_fresh_challenge(session, register_url):
"""Always fetch a fresh challenge before solving."""
resp = session.get(register_url, timeout=10)
data = resp.json()
challenge = data.get("challenge")
if not challenge:
raise ValueError("No challenge returned")
return challenge
def solve_with_fresh_challenge(session, gt, register_url, pageurl):
"""Ensure challenge is fresh before submitting to CaptchaAI."""
challenge = get_fresh_challenge(session, register_url)
# Submit immediately — don't let it expire
solution = solve_geetest(gt, challenge, pageurl)
return solution
Regla clave: un desafío caducado siempre falla, por correcto que sea el resto del código.
Sitios con servidor GeeTest personalizado
Algunos sitios enrutan GeeTest por un subdominio propio. Indica entonces el parámetro api_server; si lo omites, la resolución falla:
# The api_server parameter specifies a custom GeeTest backend
# Default: api.geetest.com
# Custom examples: api-na.geetest.com, api.geetest.com/ajax-custom
solution = solve_geetest(
gt="abc123...",
challenge="def456...",
pageurl="https://staging.example.com/qa-login",
api_server="api-na.geetest.com", # North America endpoint
)
Lista de comprobación antes de enviar
- El
gttiene 32 caracteres hexadecimales. - El
challengeprocede de una llamadaregister-slidereciente, no del HTML cacheado. - Usas la misma
sessionen las tres fases. - Incluyes
api_serversolo si el sitio enruta GeeTest por un subdominio propio. - Reenvías los tres campos que devuelve CaptchaAI:
challenge,validateyseccode.
Errores frecuentes y cómo resolverlos
| Problema | Causa | Solución |
|---|---|---|
ERROR_CAPTCHA_UNSOLVABLE |
El challenge ya caducó |
Pide uno nuevo justo antes de enviarlo |
validate llega vacío |
El sitio no es GeeTest v3 | Puede ser GeeTest v4, un protocolo distinto (próximamente en CaptchaAI) |
| El sitio rechaza la solución | Falta seccode |
Envía los tres campos: challenge, validate y seccode |
No aparece el parámetro gt |
Se carga por JavaScript | Usa Selenium o revisa las respuestas XHR |
Preguntas frecuentes
¿GeeTest v3 y GeeTest v4 se resuelven igual?
No, son protocolos distintos. CaptchaAI resuelve GeeTest v3 con el flujo gt + challenge de esta guía; GeeTest v4 aún no está disponible (próximamente).
¿Necesito un navegador headless para extraer los parámetros?
Depende. Si el gt y register-slide están en el HTML o en respuestas XHR, basta con requests; si el widget se monta por JavaScript, usa Selenium en modo headless.
¿Cuántos threads necesito para resolver GeeTest en volumen?
Se factura por thread concurrente, con resoluciones ilimitadas por thread. BASIC ($15/mes, 5 threads) cubre proyectos pequeños; para miles de GeeTest por hora, sube a ADVANCE ($90/mes, 50 threads).
¿Por qué el sitio rechaza una solución correcta?
Casi siempre porque falta uno de los tres campos (challenge/validate/seccode), porque el challenge ya había caducado o porque cambiaste de session entre la extracción y el envío.
¿Puedo reutilizar un challenge para varias resoluciones?
No. Cada challenge vale para una sola resolución y expira en segundos. Pide uno nuevo con register-slide justo antes de cada intento; reciclarlo provoca un ERROR_CAPTCHA_UNSOLVABLE.
Guías relacionadas
Domina los parámetros de GeeTest: empieza con CaptchaAI.