Tutoriales

GeeTest v3 Extracción y resolución de parámetros del control deslizante

Para resolver un control deslizante GeeTest v3 por API no arrastras la pieza tú mismo: necesitas dos valores, gt y challenge, y saber de dónde leerlos en cada carga de la página. El gt identifica el sitio y casi nunca cambia; el challenge es un token de sesión que caduca en segundos, así que tienes que capturarlo fresco justo antes de cada intento. Esta guía reúne tres formas fiables de extraer ambos —interceptar la red, leer el HTML o enganchar initGeetest— y luego los envía a CaptchaAI para recibir la solución lista para inyectar en el formulario.

Es el patrón que aparece en muchos portales de reservas, marketplaces regionales y áreas de acceso donde el flujo de login está protegido con GeeTest. Si estás haciendo QA de tu propio flujo de checkout o automatizando pruebas de un formulario, el primer paso siempre es el mismo: localizar gt y challenge.

Los parámetros gt y challenge de GeeTest v3

Parámetro Descripción Estático/Dinámico Ejemplo
gt Identificador del sitio (hexadecimal de 32 caracteres) Estático por sitio 019924a82c70bb123aae90d483b6a0ed
challenge Token de sesión (hexadecimal de 32 caracteres) Carga dinámica por página a3f5c8d2e1b04a6789012345678abcdf
api_server Subdominio API de GeeTest (opcional) Estático por sitio api-na.geetest.com

El valor gt se repite en todas las solicitudes del mismo sitio. El challenge, en cambio, se regenera cada vez que se carga el CAPTCHA: no lo reutilices entre intentos, extrae uno nuevo para cada resolución.

¿Qué método de extracción conviene?

  • Método 1 (interceptar el registro): el más limpio cuando el sitio pide el desafío a un endpoint de registro.
  • Método 2 (leer el HTML): basta con una petición cuando gt y challenge viajan incrustados en la página.
  • Método 3 (enganchar initGeetest): la opción cuando todo se inicializa por JavaScript y no expone los valores ni en el HTML ni en la red.

Método 1: interceptar la llamada de registro (register)

La mayoría de las implementaciones de GeeTest v3 piden el desafío a un endpoint de registro. La respuesta tiene esta forma:

{
  "success": 1,
  "challenge": "a3f5c8d2e1b04a6789012345678abcdf",
  "gt": "019924a82c70bb123aae90d483b6a0ed",
  "new_captcha": true
}

Python (requests)

import requests
import re

session = requests.Session()
html = session.get("https://staging.example.com/qa-login").text

# Find the register endpoint
register_url = re.search(
    r'(https?://[^"\']+(?:register|captcha|geetest)[^"\']*)',
    html
)

if register_url:
    resp = session.get(register_url.group(1)).json()
    gt = resp["gt"]
    challenge = resp["challenge"]
    print(f"gt: {gt}")
    print(f"challenge: {challenge}")

JavaScript (interceptación de red con Puppeteer)

const puppeteer = require('puppeteer');

const browser = await puppeteer.launch();
const page = await browser.newPage();

let geetestParams = {};

page.on('response', async (response) => {
  const url = response.url();
  if (url.includes('register') || url.includes('captcha')) {
    try {
      const json = await response.json();
      if (json.gt && json.challenge) {
        geetestParams = {
          gt: json.gt,
          challenge: json.challenge,
        };
        console.log('Captured GeeTest params:', geetestParams);
      }
    } catch (e) {}
  }
});

await page.goto('https://staging.example.com/qa-login', { waitUntil: 'networkidle2' });
console.log('Final params:', geetestParams);

Método 2: leer los parámetros del HTML de la página

Algunos sitios dejan gt y challenge directamente en el HTML o en scripts en línea. En ese caso no hace falta interceptar red: una sola petición y un par de expresiones regulares bastan.

import re

# From data attributes
gt_match = re.search(r'data-gt=["\']([a-f0-9]{32})', html)
challenge_match = re.search(r'data-challenge=["\']([a-f0-9]{32})', html)

# From JavaScript variables
if not gt_match:
    gt_match = re.search(r'gt\s*[=:]\s*["\']([a-f0-9]{32})', html)
if not challenge_match:
    challenge_match = re.search(r'challenge\s*[=:]\s*["\']([a-f0-9]{32})', html)

gt = gt_match.group(1) if gt_match else None
challenge = challenge_match.group(1) if challenge_match else None
print(f"gt={gt}, challenge={challenge}")

Método 3: enganchar initGeetest antes de que cargue

GeeTest v3 se inicializa con initGeetest(). Si interceptas esa llamada antes de que se ejecuten los scripts de la página, capturas la configuración completa —incluidos gt y challenge— sin depender del HTML ni de la red:

// Puppeteer: inject before page scripts
await page.evaluateOnNewDocument(() => {
  window.__geetestConfig = null;
  const origInit = window.initGeetest;

  Object.defineProperty(window, 'initGeetest', {
    set(fn) {
      this._initGeetest = function(config, callback) {
        window.__geetestConfig = config;
        console.log('GeeTest config:', JSON.stringify(config));
        return fn(config, callback);
      };
    },
    get() { return this._initGeetest; }
  });
});

await page.goto('https://staging.example.com/qa-login', { waitUntil: 'networkidle2' });

const config = await page.evaluate(() => window.__geetestConfig);
// config = { gt: "019924a...", challenge: "a3f5c8d...", product: "bind", ... }

Cómo saber qué método necesita un sitio

Antes de elegir, comprueba cómo entrega el sitio sus parámetros. Un vistazo rápido con las herramientas de desarrollo del navegador te dice qué camino seguir:

  • Abre la pestaña Network y recarga la página: si aparece una llamada a un endpoint con register o gettype que devuelve gt y challenge, ve directo al Método 1.
  • Mira el código fuente (View source) o busca data-gt en el HTML: si los valores están ahí, el Método 2 basta.
  • Si no aparecen ni en la red ni en el HTML porque un script los inyecta en tiempo de ejecución, engancha initGeetest con el Método 3.

Enviar gt y challenge a CaptchaAI

Con los dos parámetros en la mano, envías la tarea con el método geetest y sondeas el resultado. Extrae el challenge y haz el envío de inmediato: cada segundo que pasa acerca el token a su caducidad.

Python

import requests
import time

API_KEY = "YOUR_API_KEY"

# Submit
resp = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "geetest",
    "gt": gt,
    "challenge": challenge,
    "pageurl": "https://staging.example.com/qa-login",
    "json": "1",
}).json()

if resp["status"] != 1:
    raise Exception(f"Submit error: {resp['request']}")

task_id = resp["request"]

# Poll
for _ in range(30):
    time.sleep(5)
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY, "action": "get", "id": task_id, "json": "1"
    }).json()

    if result["status"] == 1:
        solution = result["request"]
        print(f"Solution: {solution}")
        break
    if result["request"] != "CAPCHA_NOT_READY":
        raise Exception(f"Error: {result['request']}")

JavaScript

const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
  params: {
    key: 'YOUR_API_KEY',
    method: 'geetest',
    gt: geetestParams.gt,
    challenge: geetestParams.challenge,
    pageurl: 'https://staging.example.com/qa-login',
    json: 1,
  }
});
const taskId = submit.data.request;

let solution = null;
for (let i = 0; i < 30; i++) {
  await new Promise(r => setTimeout(r, 5000));
  const poll = await axios.get('https://ocr.captchaai.com/res.php', {
    params: { key: 'YOUR_API_KEY', action: 'get', id: taskId, json: 1 }
  });
  if (poll.data.status === 1) {
    solution = poll.data.request;
    break;
  }
}
console.log('Solution:', solution);

Inyectar la solución en el formulario

La respuesta de CaptchaAI para GeeTest v3 trae tres campos: challenge, validate y seccode. Los escribes como inputs ocultos del formulario antes de enviarlo:

# solution is a pipe-delimited string or JSON object
# Parse and inject into the form
import json

sol = json.loads(solution) if isinstance(solution, str) else solution

driver.execute_script("""
    const form = document.querySelector('form');
    function addHidden(name, value) {
        let input = form.querySelector(`input[name="${name}"]`);
        if (!input) {
            input = document.createElement('input');
            input.type = 'hidden';
            input.name = name;
            form.appendChild(input);
        }
        input.value = value;
    }
    addHidden('geetest_challenge', arguments[0]);
    addHidden('geetest_validate', arguments[1]);
    addHidden('geetest_seccode', arguments[2]);
""", sol["challenge"], sol["validate"], sol["seccode"])

Errores frecuentes y cómo resolverlos

Casi todos los fallos al enviar la tarea vienen de un parámetro mal extraído o de un token que ya caducó. Esta tabla resume los casos más habituales:

Problema Causa Solución
ERROR_BAD_PARAMETERS Falta gt o challenge Ambos son obligatorios; vuelve a extraer valores frescos
Desafío caducado El token challenge expiró Extrae uno nuevo justo antes de enviar la tarea
Valor gt incorrecto Copiado de otro sitio El gt es específico del sitio; vuelve a extraerlo del objetivo
Solución rechazada por el sitio El desafío caducó durante la resolución Acorta el tiempo entre la extracción y el envío

La mayoría de los fallos se reducen a lo mismo: un challenge viejo. Si automatizas el flujo, encadena extracción y envío en el mismo paso para que el token llegue vivo a CaptchaAI.

Preguntas frecuentes

¿CaptchaAI es compatible con GeeTest v4?

Todavía no. CaptchaAI resuelve GeeTest v3 de forma general; el soporte de GeeTest v4 está anunciado como próximamente. GeeTest v4 usa captcha_id en lugar de gt/challenge y añade varios tipos de desafío (clic, coincidencia, deslizar), por lo que su integración es distinta.

¿Necesito un proxy para extraer gt y challenge?

No para la extracción en sí: gt y challenge son valores públicos que el propio sitio entrega al cargar el CAPTCHA. Puedes obtenerlos con una petición normal desde tu salida de red autorizada.

¿Puedo reutilizar el mismo challenge en varios envíos?

No. El challenge es de un solo uso y caduca en segundos. El gt sí se reutiliza dentro del mismo sitio, pero para cada resolución debes extraer un challenge nuevo.

¿Cuándo hace falta el parámetro api_server?

En pocos casos. api_server es opcional y solo importa cuando el sitio fija un subdominio de GeeTest concreto. Conviene incluirlo si:

  • el desafío se sirve desde un subdominio regional como api-na.geetest.com y el envío falla sin especificarlo;
  • la respuesta de registro trae ese subdominio y quieres reproducir el flujo exacto del sitio.

¿Qué plan de CaptchaAI conviene para resolver GeeTest v3 a volumen?

La facturación es por thread (CAPTCHA en curso), no por resolución, con resoluciones ilimitadas dentro de cada plan. Puedes empezar con BASIC ($15/mes, 5 threads) para pruebas y subir a ADVANCE ($90/mes, 50 threads) cuando el volumen crezca. Para agencias que facturan en monedas locales volátiles, este costo mensual fijo en USD es más predecible que el pago por resolución.

Resuelve GeeTest v3 con CaptchaAI

Obtén tu clave API en captchaai.com, extrae gt y challenge con cualquiera de los tres métodos de arriba y envíalos con el método geetest. En pocos minutos tienes la primera resolución funcionando.

Guías relacionadas

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