Quando a CaptchaAI resolve um desafio GeeTest v3, ela devolve três valores — não um único token, como acontece no reCAPTCHA. É aí que a maioria das automações trava: o script preenche só um campo, ou dispara o callback errado, e o formulário rejeita a resposta mesmo com a resolução correta em mãos. Este tutorial mostra onde cada framework espera esses três valores — campos ocultos, callback JavaScript do próprio widget ou payload de XHR — em Playwright, Puppeteer e Selenium.
Você sai deste tutorial sabendo:
- onde gravar os três valores em cada framework
- como acionar o callback do GeeTest quando o site não usa campos de formulário
- como tratar sites que enviam a resposta via XHR em vez de POST
- os erros mais comuns quando a validação falha mesmo com a resolução certa
Os três valores que a CaptchaAI devolve no GeeTest v3
Todo desafio GeeTest v3 resolvido pela CaptchaAI retorna um resultado de três partes:
{
"geetest_challenge": "a1b2c3d4e5...modified_challenge",
"geetest_validate": "abc123def456_validate",
"geetest_seccode": "abc123def456_validate|jordan"
}
Os três valores viajam juntos. Se faltar um só — mesmo com os outros dois corretos — o servidor rejeita a verificação; não existe validação parcial no GeeTest.
Etapa 1: capture gt e challenge e resolva com a CaptchaAI
O primeiro passo é igual em qualquer framework: capture os parâmetros gt e challenge que o widget GeeTest expõe na página e envie-os para a API da CaptchaAI. A função abaixo cobre envio e polling — o GeeTest v3 costuma retornar em menos de 12 segundos, mas o loop aguenta até 3 minutos para absorver picos de fila:
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")
O retorno é um dicionário com os três valores, prontos para as próximas etapas.
Rodando os testes fora do Brasil? Workers hospedados na região
sa-east-1da AWS (São Paulo) reduzem o RTT até a CaptchaAI — ajuda quando omaxPollTimeestá apertado.
Etapa 2: envie a solução ao Playwright (Python)
Capture os parâmetros do widget
Escute o evento response do Playwright e intercepte a chamada de registro do GeeTest — é nela que o site expõe gt e challenge, antes mesmo de o widget terminar de renderizar:
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"]
Grave os valores nos campos ocultos do formulário
Com a solução em mãos, crie (ou atualize) os três campos ocultos que o formulário espera e só então clique em enviar:
# Solve with 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")
Alternativa: acione o callback do GeeTest
Alguns sites não leem campos de formulário — esperam que o próprio widget chame getValidate() e dispare o evento de sucesso. Nesse caso, sobrescreva o método e dispare o evento manualmente:
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);
}}
""")
Etapa 3: envie a solução ao Puppeteer (JavaScript)
A lógica é a mesma do Playwright, adaptada para a API do Puppeteer: capture gt e challenge no evento response, resolva com a CaptchaAI e grave os três valores nos campos ocultos antes de enviar o formulário:
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 with 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');
}
Etapa 4: envie a solução ao Selenium (Python)
No Selenium, a extração dos parâmetros costuma vir de atributos data-* no próprio HTML do widget, e a gravação dos valores acontece via execute_script — o mesmo princípio dos dois frameworks anteriores:
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 with 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()
Quando o site envia os valores por XHR em vez de POST tradicional
Nem todo formulário faz um POST tradicional. Alguns enviam os valores do GeeTest dentro de uma requisição XHR ou fetch para um endpoint de API. Nesse caso, em vez de preencher campos, intercepte a requisição de envio e grave os três valores diretamente no corpo antes de deixá-la seguir:
# 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)
Erros comuns e como corrigir
Antes de suspeitar da resolução da CaptchaAI, confira estes cinco pontos — eles cobrem a maioria dos casos em que o formulário rejeita uma resposta correta:
| Problema | Causa | Correção |
|---|---|---|
| "Falha na validação do GeeTest" | Faltou um dos três valores | Confirme que geetest_challenge, geetest_validate e geetest_seccode foram todos gravados |
| O desafio expira antes da gravação | Muito tempo entre a extração e a resolução | Extraia e resolva em sequência rápida, sem filas intermediárias |
| O formulário é enviado, mas os valores não chegam | Nome de campo ou seletor de formulário errado | Inspecione o formulário real da página para confirmar os nomes |
| O callback não dispara | O site usa um nome de callback próprio | Procure o nome do callback nas opções de initGeetest |
| Valores gravados, mas o widget continua visível | Estado interno do widget não foi atualizado | Dispare o callback de sucesso programaticamente, como na Etapa 2 |
Resumo
O padrão se repete em qualquer framework:
- capture
gtechallengefrescos direto do widget - resolva com a CaptchaAI e confira se os três valores voltaram
- grave
geetest_challenge,geetest_validateegeetest_seccodeno lugar que o site espera — campo oculto, callback ou corpo do XHR - dispare o envio só depois de confirmar que nenhum dos três ficou de fora
Muda apenas a API usada para capturar a resposta de rede e escrever no DOM — em Playwright e Puppeteer via eventos de response, no Selenium via atributos data-* do próprio HTML do widget.
Perguntas frequentes
Os valores do GeeTest expiram? Preciso resolver bem perto do envio?
Sim. A janela de validade é curta — normalmente alguns minutos, dependendo da configuração do site. Resolva com a CaptchaAI o mais perto possível do envio do formulário; se a extração e o envio ficam separados por uma fila assíncrona, é mais seguro capturar gt e challenge de novo do que reaproveitar valores antigos.
Dá para enviar os valores do GeeTest sem abrir um navegador?
Sim, se o site aceitar um POST direto com os três valores. Nesse caso, você intercepta o endpoint de envio do formulário e manda os valores por requests ou qualquer cliente HTTP — sem precisar de Playwright, Puppeteer ou Selenium.
A CaptchaAI resolve a versão mais recente do GeeTest, a v4?
Não. A CaptchaAI resolve GeeTest v3, o solver usado neste tutorial. O suporte ao GeeTest v4 está listado como "em breve" e ainda não está disponível. Se o site que você testa já migrou para a v4, os três valores deste guia não se aplicam.
Vale registrar geetest_validate e geetest_seccode em log de depuração?
Evite em produção. Esses dois valores identificam a sessão resolvida e, se vinculados a um usuário real, podem ser tratados como dado pessoal sob a LGPD. Em staging com dados fictícios não há problema; em produção, mascare o valor no log ou descarte-o depois de confirmar a depuração.
Por que o GeeTest usa três valores separados em vez de um token único?
Cada um cobre uma função diferente: geetest_challenge identifica a sessão do desafio, geetest_validate prova que ele foi resolvido, e geetest_seccode carrega um hash usado para detectar violação dos dados enviados.
Artigos relacionados
- Como resolver o GeeTest v3 em Node.js
- Resolvendo GeeTest v3 em Node.js com a CaptchaAI
- Automação de navegador mobile: como resolver CAPTCHA
Próximas etapas
Envie a solução do GeeTest para qualquer framework de automação — gere sua chave de API da CaptchaAI e comece a resolver hoje.