Если после решения GeeTest v3 форма не проходит проверку, дело обычно не в решении, а в месте подстановки — в полях формы, JS-колбэке или теле XHR-запроса.
На практике QA-инженеры в СНГ чаще всего перехватывают виджет GeeTest на формах логина или чекаута, автоматизированных через Playwright, Puppeteer или Selenium, — и место подстановки токена отличается для каждого фреймворка. Ниже — рабочие сценарии для всех трёх, плюс отдельный разбор варианта, когда сайт вообще не использует классическую форму, а отправляет результат через XHR.
Что подставляем
CaptchaAI возвращает результат из трёх частей — нужны все сразу:
{
"geetest_challenge": "a1b2c3d4e5...modified_challenge",
"geetest_validate": "abc123def456_validate",
"geetest_seccode": "abc123def456_validate|jordan"
}
Каждое поле отвечает за свою часть проверки на стороне сайта:
geetest_challenge— идентификатор сессии решения, привязанный к конкретномуchallenge, который вы получили при инициализации виджета.geetest_validate— подтверждение того, что проверка пройдена; сервер обычно проверяет именно это поле в первую очередь.geetest_seccode— контрольный хэш, который защищает паруchallenge/validateот подмены задним числом.
Если хотя бы одно значение отсутствует, проверка завершится ошибкой — типичные причины разобраны в таблице ниже.
Шаг 1. Извлеките gt и challenge, затем решите капчу
Значения gt и challenge формирует сам виджет GeeTest при инициализации — они приходят в ответе эндпоинта регистрации ещё до того, как пользователь пройдёт визуальную часть проверки. Получите их со страницы и передайте в CaptchaAI:
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")
Функция solve_geetest выше — общий код для всех трёх фреймворков ниже: меняется только то, как вы перехватываете gt/challenge и куда подставляете готовое решение.
Шаг 2. Внедрение в Playwright (Python)
Перехватите gt и challenge
Поймайте ответ эндпоинта регистрации GeeTest с gt и challenge:
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"]
Подставьте решение в форму
Добавьте три значения в скрытые поля формы и отправьте её:
# 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")
Способ 2: вызовите обратный вызов GeeTest напрямую
Некоторые сайты слушают JS-событие виджета вместо полей формы. Подмените результат в объекте капчи и вызовите событие успеха:
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);
}}
""")
Шаг 3. Внедрение в Puppeteer (JavaScript)
Логика та же, что в Playwright — отличается только синтаксис перехвата ответов и то, что решение передаётся в page.evaluate аргументом объекта, а не через форматированную строку с подстановкой полей:
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');
}
Шаг 4. Внедрение в Selenium (Python)
В Selenium нет перехвата ответов: gt/challenge берём через 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 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()
Если сайт отправляет результат через XHR, а не форму
Некоторые сайты не используют форму вообще, а отправляют три значения отдельным XHR-запросом. Перехватите его и подмените тело:
# 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)
Типичные ошибки
| Проблема | Причина | Решение |
|---|---|---|
| Проверка не проходит | Не все значения переданы | Отправьте challenge, validate, seccode вместе |
| Challenge истёк | Задержка перед решением | Решайте сразу после извлечения |
| Форма без значений | Неверный селектор | Сверьте имена input в DOM |
| Callback не срабатывает | Своё имя функции у сайта | Найдите имя в initGeetest |
| Виджет не обновился | Состояние не сброшено | Вызовите событие успеха вручную |
Если ни одна из причин не подходит, включите логирование сетевых ответов (page.on('response', ...) в Playwright или аналог в Puppeteer/Selenium) и сверьте реальные имена полей формы. На практике большинство проблем сводится либо к опечатке в имени input, либо к тому, что решение подставляется до того, как форма появилась в DOM.
Пример из практики: QA-прогон формы логина в staging
Типичный сценарий для QA-команды, тестирующей форму логина с GeeTest v3 в CI: staging-окружение поднимается на каждый pull request, виджет инициализируется на домене вида staging.example.com, и тест должен пройти форму без участия человека. Порядок действий тот же, что и выше — перехватить gt/challenge, решить через CaptchaAI, подставить три значения, — но добавляется нюанс: staging часто перевыпускает challenge при каждом деплое, поэтому кешировать значения между прогонами нельзя.
Если тест собирает данные пользователей — даже тестовые логины и email, — команды, работающие с аудиторией в РФ, обычно фиксируют это в чек-листе по 152-ФЗ «О персональных данных»: используйте только синтетические тестовые аккаунты, а не реальные данные пользователей, даже во внутреннем staging-окружении.
Частые вопросы
Почему GeeTest использует три значения, а не один токен?
geetest_challenge — сессия, geetest_validate — подтверждение, geetest_seccode — хэш от подмены. Нужны все три.
Сколько потоков CaptchaAI хватит для QA-тестирования GeeTest?
Обычно достаточно BASIC ($15/мес, 5 потоков); при росте нагрузки — STANDARD ($30/мес, 15 потоков).
Работает ли это для GeeTest v4?
Нет, CaptchaAI решает только GeeTest v3, GeeTest v4 пока в статусе «скоро».
Как проверить, что XHR-перехват сработал, если сайт не использует форму?
Залогируйте route.request.post_data до и после подмены в перехватчике Playwright. Если тело запроса после route.continue_(post_data=...) содержит все три значения — перехват сработал; если нет, проверьте, что паттерн в page.route() совпадает с реальным URL логина.
Что делать, если challenge устаревает быстрее, чем успевает пройти QA-прогон?
Сокращайте время между извлечением gt/challenge и вызовом CaptchaAI: не кешируйте эти значения между тестовыми прогонами и не откладывайте решение в фоновый воркер с задержкой.