Ya tienes la solución de CaptchaAI; el reto real es colocar sus valores donde el sitio los espera. GeeTest v3 no entrega un único token: devuelve geetest_challenge, geetest_validate y geetest_seccode, y cada página los consume de una forma distinta —campos ocultos del formulario, un callback de JavaScript o el cuerpo de una petición XHR—. Aquí inyectamos los tres valores en Playwright, Puppeteer y Selenium, con código que puedes reutilizar tal cual.
Sea cual sea el framework, el guion es siempre el mismo:
- Captura
gtychallengede la petición de registro del widget. - Resuelve el desafío con CaptchaAI y recibe los tres valores.
- Inyéctalos donde el sitio los lee: campos del formulario, callback o XHR.
Qué devuelve GeeTest v3 (los tres valores)
CaptchaAI te entrega un resultado en tres partes:
{
"geetest_challenge": "a1b2c3d4e5...modified_challenge",
"geetest_validate": "abc123def456_validate",
"geetest_seccode": "abc123def456_validate|jordan"
}
Cada valor cumple una función distinta:
geetest_challenge: identifica la sesión del desafío.geetest_validate: demuestra que el desafío se resolvió.geetest_seccode: aporta el hash de control contra manipulaciones.
Los tres son obligatorios: si falta uno, la verificación del servidor falla por mucho que el desafío esté bien resuelto.
Paso 1: extrae los parámetros y resuelve con CaptchaAI
El punto de partida es idéntico en cualquier framework: captura gt y challenge de la página y envíalos a CaptchaAI para resolver GeeTest v3.
import requests
import time
def solve_geetest(gt, challenge, page_url):
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": "YOUR_API_KEY",
"method": "geetest",
"gt": gt,
"challenge": challenge,
"pageurl": page_url,
"json": 1
})
task_id = resp.json()["request"]
for _ in range(60):
time.sleep(3)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY",
"action": "get",
"id": task_id,
"json": 1
})
data = result.json()
if data["status"] == 1:
return data["request"] # Returns dict with three values
raise TimeoutError("GeeTest solve timed out")
Sondea el resultado cada tres segundos y devuelve el diccionario con los tres valores cuando el estado pasa a 1.
Paso 2: inyecta la solución en Playwright (Python)
Captura de gt y challenge
GeeTest registra el widget con una llamada de red que expone gt y challenge. Escucha esa respuesta de registro y guarda ambos:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
# Capture GeeTest registration response
geetest_data = {}
def capture_geetest(response):
if "register" in response.url and response.status == 200:
try:
data = response.json()
if "gt" in data and "challenge" in data:
geetest_data.update(data)
except Exception:
pass
page.on("response", capture_geetest)
page.goto("https://staging.example.com/qa-login")
page.wait_for_selector(".geetest_holder")
gt = geetest_data["gt"]
challenge = geetest_data["challenge"]
Inyecta los valores en el formulario
El método más directo es escribir los tres valores en campos ocultos del formulario, creándolos al vuelo si no existen:
# Solve con CaptchaAI
solution = solve_geetest(gt, challenge, page.url)
# Method 1: Set hidden form fields
page.evaluate(f"""
const fields = {{
'geetest_challenge': '{solution["geetest_challenge"]}',
'geetest_validate': '{solution["geetest_validate"]}',
'geetest_seccode': '{solution["geetest_seccode"]}'
}};
for (const [name, value] of Object.entries(fields)) {{
let input = document.querySelector(`input[name="${{name}}"]`);
if (!input) {{
input = document.createElement('input');
input.type = 'hidden';
input.name = name;
document.querySelector('form').appendChild(input);
}}
input.value = value;
}}
""")
# Submit the form
page.click("#submit-button")
Método 2: dispara el callback de GeeTest
Muchos sitios no leen campos ocultos: esperan que el objeto de GeeTest devuelva la validación a través de su callback. En ese caso, sobrescribe getValidate y lanza el evento de éxito:
page.evaluate(f"""
// Find the GeeTest captcha object
if (window.captchaObj) {{
// Simulate a successful solve
const result = {{
geetest_challenge: '{solution["geetest_challenge"]}',
geetest_validate: '{solution["geetest_validate"]}',
geetest_seccode: '{solution["geetest_seccode"]}'
}};
// Override getValidate to return our solution
window.captchaObj.getValidate = function() {{ return result; }};
// Trigger the success callback
const successEvent = new Event('geetest_success');
document.dispatchEvent(successEvent);
}}
""")
Paso 3: inyecta la solución en Puppeteer (JavaScript)
En Puppeteer el patrón es idéntico: intercepta la respuesta de registro, resuelve con CaptchaAI e inyecta los tres valores en el formulario.
const puppeteer = require('puppeteer');
async function solveAndInject() {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
// Capture GeeTest params
let gt, challenge;
page.on('response', async (response) => {
if (response.url().includes('register') && response.status() === 200) {
try {
const data = await response.json();
if (data.gt && data.challenge) {
gt = data.gt;
challenge = data.challenge;
}
} catch (e) {}
}
});
await page.goto('https://staging.example.com/qa-login');
await page.waitForSelector('.geetest_holder');
// Solve con CaptchaAI (implementation from earlier)
const solution = await solveCaptcha(gt, challenge, page.url());
// Inject the three values
await page.evaluate((sol) => {
// Set hidden inputs
const form = document.querySelector('form');
['geetest_challenge', 'geetest_validate', 'geetest_seccode'].forEach(name => {
let input = document.querySelector(`input[name="${name}"]`);
if (!input) {
input = document.createElement('input');
input.type = 'hidden';
input.name = name;
form.appendChild(input);
}
input.value = sol[name];
});
}, solution);
await page.click('#submit-button');
}
El resultado es el mismo formulario relleno que en Playwright, pero resuelto con la API asíncrona de Puppeteer.
Paso 4: inyecta la solución en Selenium (Python)
Selenium no trae un listener de respuestas tan directo: aquí leemos gt y challenge de los atributos data- del DOM y volcamos la solución con execute_script:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://staging.example.com/qa-login")
# Wait for GeeTest widget
WebDriverWait(driver, 15).until(
EC.presence_of_element_located((By.CLASS_NAME, "geetest_holder"))
)
# Extract gt and challenge from the page
gt = driver.execute_script(
"return document.querySelector('[data-gt]')?.dataset.gt"
)
challenge = driver.execute_script(
"return document.querySelector('[data-challenge]')?.dataset.challenge"
)
# Solve con CaptchaAI
solution = solve_geetest(gt, challenge, driver.current_url)
# Inject via JavaScript
driver.execute_script(f"""
var fields = {{
'geetest_challenge': '{solution["geetest_challenge"]}',
'geetest_validate': '{solution["geetest_validate"]}',
'geetest_seccode': '{solution["geetest_seccode"]}'
}};
var form = document.querySelector('form');
for (var name in fields) {{
var input = document.querySelector('input[name="' + name + '"]');
if (!input) {{
input = document.createElement('input');
input.type = 'hidden';
input.name = name;
form.appendChild(input);
}}
input.value = fields[name];
}}
""")
driver.find_element(By.ID, "submit-button").click()
Si el formulario se manda por AJAX en lugar de recargar la página, combina este volcado con la intercepción de XHR de la siguiente sección.
Cuando el sitio envía la solución por XHR
No todos los formularios se mandan con un POST clásico. Cuando el sitio manda el resultado de GeeTest dentro de una XHR, intercepta la ruta y añade los valores al cuerpo antes de dejarla continuar:
# Playwright: Intercept the XHR and inject values
def handle_route(route):
if "login" in route.request.url and route.request.method == "POST":
# Modify the POST data to include our solution
post_data = route.request.post_data
# Add GeeTest values to the request
route.continue_(post_data=modified_data)
else:
route.continue_()
page.route("**/api/login**", handle_route)
Un caso real: QA de tu propio flujo de acceso
Piensa en una agencia en Madrid o Ciudad de México que corre pruebas automatizadas cada noche sobre su propio portal de acceso —el mismo tipo de formulario que protege una cita previa— con GeeTest v3. Sin inyección de tokens, cada ejecución se detiene en el widget; con el patrón de esta guía, el pipeline resuelve el desafío con CaptchaAI, inyecta los tres valores y termina la prueba. Como CaptchaAI factura por thread con un coste mensual fijo en USD, el equipo estima por adelantado el gasto de sus suites de QA. Trabaja siempre sobre entornos propios y respeta los términos de servicio aplicables.
Resolución de problemas comunes
| Problema | Causa | Solución |
|---|---|---|
| "GeeTest validation failed" | Falta uno de los tres valores | Comprueba que se inyecten challenge, validate y seccode |
| El challenge expira antes de inyectarlo | Pasó demasiado tiempo entre la captura y la resolución | Extrae y resuelve en sucesión inmediata |
| El formulario se envía pero sin los valores | Nombre de campo o selector de formulario incorrecto | Inspecciona el formulario real y ajusta los name |
| El callback no se dispara | El sitio usa un nombre de callback personalizado | Búscalo en las opciones de initGeetest |
| Los valores se inyectan pero el widget sigue visible | El estado visual del widget no se actualizó | Dispara el callback de éxito por código |
Preguntas frecuentes
¿Qué plan de CaptchaAI conviene para resolver GeeTest v3 en producción?
Depende de tu concurrencia, no del número de resoluciones: CaptchaAI factura por thread con resoluciones ilimitadas dentro del plan. BASIC ($15/mes, 5 threads) cubre suites de QA pequeñas; ADVANCE ($90/mes, 50 threads) o PREMIUM ($170/mes, 100 threads) encajan cuando corres muchos flujos en paralelo.
¿CaptchaAI es compatible con GeeTest v4?
Todavía no. CaptchaAI resuelve GeeTest v3; la compatibilidad con GeeTest v4 figura como próximamente. Esta guía se aplica a GeeTest v3, que es el que devuelve los tres valores challenge, validate y seccode.
¿Puedo inyectar los tres valores sin abrir un navegador?
Sí, si el sitio acepta un POST HTTP directo con los tres valores. Intercepta el endpoint de envío del formulario y manda challenge, validate y seccode con requests o cualquier cliente HTTP, sin necesidad de Playwright ni Selenium.
¿Cuánto tiempo sigue siendo válida una solución de GeeTest?
Poco: el challenge caduca a los pocos segundos. Por eso conviene capturar los parámetros, resolver con CaptchaAI e inyectar el resultado en una sucesión rápida, en lugar de guardar soluciones para reutilizarlas más tarde.
Artículos relacionados
- Resolver GeeTest v3 en Node.js
- GeeTest v3 con Node.js y CaptchaAI
- Resolver CAPTCHA en automatización de navegadores móviles
Próximos pasos
Inyecta soluciones de GeeTest v3 en cualquier framework: consigue tu API key de CaptchaAI y empieza a resolver.