Tutorials

GeeTest v3 Извлечение и решение параметров слайдера

Чтобы решить слайдер GeeTest v3 через API, нужны два значения со страницы: gt (постоянный идентификатор сайта) и challenge (одноразовый токен сеанса). Без обоих параметров CaptchaAI вернёт ERROR_BAD_PARAMETERS. Ниже — три способа получить их с любой страницы: перехват сетевого запроса, разбор HTML и перехват инициализации initGeetest, плюс готовый код отправки в CaptchaAI на Python и JavaScript.

Выбор способа зависит от того, как конкретный сайт подгружает GeeTest: одни отдают параметры отдельным запросом к эндпоинту регистрации, другие встраивают их прямо в разметку, третьи передают только через вызов initGeetest() в браузере. Дальше — по порядку, от самого надёжного к самому универсальному.


Параметры gt и challenge: что это и откуда брать

Параметр Описание Тип Пример
gt Идентификатор сайта (32-символьный hex) Статический, привязан к сайту 019924a82c70bb123aae90d483b6a0ed
challenge Токен сеанса (32-символьный hex) Динамический, меняется при каждой загрузке a3f5c8d2e1b04a6789012345678abcdf
api_server Поддомен GeeTest API (необязательный) Статический, привязан к сайту api-na.geetest.com

gt можно закешировать — он не меняется между запросами к одному и тому же сайту. challenge кешировать нельзя: он одноразовый и привязан к конкретной загрузке страницы, поэтому для каждой новой попытки решения нужно извлекать свежее значение заново.


Способ 1: перехватить запрос к эндпоинту регистрации

Самый надёжный вариант — большинство внедрений GeeTest v3 сами запрашивают gt/challenge у эндпоинта регистрации при загрузке страницы, и оба значения приходят в одном JSON-ответе:

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

Достаточно найти URL этого запроса в HTML и повторить его отдельным HTTP-вызовом — открывать браузер не обязательно.

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 (перехват сети в Puppeteer)

Если запрос сложно найти статическим разбором HTML (например, URL собирается на клиенте), проще подписаться на все сетевые ответы страницы и выловить нужный по содержимому:

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);

Способ 2: разобрать исходный код страницы

Часть сайтов вообще не делает отдельного запроса и вшивает gt/challenge прямо в HTML — в data-атрибуты виджета или в переменные инлайн-скрипта. В этом случае регулярное выражение по уже загруженному HTML быстрее, чем ловить сетевые запросы:

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}")

Если оба паттерна возвращают None, вероятнее всего сайт использует способ 3 ниже — параметры собираются динамически прямо в момент вызова initGeetest().


Способ 3: перехватить вызов initGeetest

GeeTest v3 запускается через initGeetest(), и в объект config попадают оба нужных параметра ещё до того, как виджет отрисуется на странице. Перехватывать нужно до выполнения скриптов страницы — иначе браузер успеет инициализировать капчу без вашего hook:

// 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", ... }

Этот способ работает почти на любом сайте с виджетом GeeTest v3, поэтому держите его как запасной вариант, если способы 1 и 2 не дали результата.


Отправка gt и challenge в CaptchaAI

Когда gt и challenge извлечены, задача уходит в CaptchaAI тем же способом, что и остальные типы CAPTCHA: POST на in.php с методом geetest, затем опрос res.php до готовности решения. GeeTest v3 входит в число типов с базовой поддержкой CaptchaAI, поэтому дополнительной настройки аккаунта не требуется — достаточно действующего API-ключа.

Для команды, которая гоняет QA-сценарии на нескольких стендах параллельно, тарифицируется не число решённых капч, а число одновременных потоков: план BASIC ($15/мес, 5 потоков) закрывает единичные прогоны и локальную отладку, а при регулярном прогоне десятков сценариев в CI имеет смысл смотреть на STANDARD ($30/мес, 15 потоков) или выше — при этом решений на поток по-прежнему не ограничено.

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);

Как подставить решение обратно в форму

Ответ CaptchaAI для GeeTest v3 содержит три поля — challenge, validate и seccode. Все три нужно записать в скрытые поля формы до отправки, иначе сайт отклонит решение как неполное:

# 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"])

Типичные ошибки и как их избежать

challenge живёт недолго, и большинство проблем на практике сводятся к тому, что решение приходит на сайт позже, чем истекает срок действия токена:

Проблема Причина Решение
ERROR_BAD_PARAMETERS Не передан gt или challenge Оба параметра обязательны — извлеките заново, если один из них пуст
Устаревший challenge Токен истёк до отправки на сайт Извлекайте challenge непосредственно перед вызовом in.php, без задержек между шагами
Неверный gt Значение скопировано с другого домена gt уникален для каждого сайта — извлекайте его именно с целевой страницы, не переиспользуйте из другого проекта
Сайт отклоняет решение Challenge истёк, пока CaptchaAI решал слайдер Сократите интервал между извлечением параметров и отправкой задачи в API

Если ошибка повторяется стабильно, а не время от времени, почти всегда дело не в CaptchaAI, а в том, что скрипт извлекает устаревшую пару gt/challenge — например, кеширует их между запусками вместо повторного извлечения.


Часто задаваемые вопросы

Почему extraction возвращает пустые gt или challenge?

Чаще всего — код ищет их до того, как виджет успел загрузиться. Дождитесь события загрузки эндпоинта регистрации (способ 1) или сначала проверьте способ 3 через initGeetest, если сайт не делает отдельного сетевого запроса.

Нужен ли headless-браузер для извлечения параметров?

Не всегда. Если сайт отдаёт gt/challenge отдельным HTTP-запросом (способ 1) или вшивает их в HTML (способ 2), достаточно обычного HTTP-клиента вроде requests. Headless-браузер нужен только для способа 3, когда параметры собираются исключительно в момент выполнения JavaScript на странице.

Сколько живёт challenge и сколько времени есть на решение?

Обычно 60–120 секунд с момента извлечения. Извлекайте challenge и сразу отправляйте его в CaptchaAI без промежуточных шагов — каждая лишняя секунда между извлечением и отправкой повышает риск, что сайт отклонит уже готовое решение.

В чём разница между GeeTest v3 и GeeTest v4?

GeeTest v3 работает с парой gt/challenge и слайдером как основным типом задания. GeeTest v4 использует другой идентификатор — captcha_id — и несколько типов заданий (клик, сопоставление, слайд). CaptchaAI поддерживает GeeTest v3; поддержка GeeTest v4 пока в разработке и недоступна.


Решайте GeeTest v3 в своём пайплайне

Получите API-ключ на captchaai.com и подключите извлечённые gt/challenge к in.php по коду выше — интеграция занимает один эндпоинт для отправки и один для опроса результата.


Связанные руководства

Комментарии для этой статьи отключены.