Tutoriales

Inyección de tokens GeeTest en marcos de automatización de navegadores

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 gt y challenge de 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

Próximos pasos

Inyecta soluciones de GeeTest v3 en cualquier framework: consigue tu API key de CaptchaAI y empieza a resolver.

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