API Tutorials

Параметры GeeTest Slide CAPTCHA и отправка через API CaptchaAI

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

CaptchaAI решает GeeTest v3 (метод geetest) уже сейчас; поддержка GeeTest v4 пока в разработке — если сайт отдаёт version=4, эта связка параметров не подойдёт (см. изменения в GeeTest v4).


Обязательные параметры GeeTest v3

Параметр Обязателен Описание
gt Да Идентификатор аккаунта GeeTest на сайте (32-значный hex). Есть в исходном коде страницы или в ответе API
challenge Да Строка, привязанная к сессии. Должна быть свежей на каждое решение
pageurl Да Полный URL страницы, где показывается CAPTCHA
api_server Нет Кастомный поддомен API-сервера GeeTest

Перепутанный порядок этих полей или устаревший challenge — две самые частые причины, по которым решение GeeTest отклоняется сайтом ещё до того, как вы успеете разобраться, в чём дело.


Извлекаем gt и challenge со страницы

Оба значения обычно приходят двумя путями: часть встроена прямо в HTML, часть — в ответ конечной точки регистрации (register-slide). Функция ниже пробует оба варианта по очереди и возвращает то, что нашла первым:

# extract_geetest_params.py
import requests
import re
import json


def extract_geetest_v3(page_url, session=None):
    """Extract GeeTest v3 gt and challenge from a page."""
    if session is None:
        session = requests.Session()
        session.headers["User-Agent"] = (
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36"
        )

    resp = session.get(page_url, timeout=15)
    html = resp.text

    # Method 1: Extract gt from HTML
    gt_match = re.search(r'gt["\']?\s*[:=]\s*["\']([a-f0-9]{32})', html)
    gt = gt_match.group(1) if gt_match else None

    # Method 2: Find API endpoint that returns challenge
    api_match = re.search(r'(https?://[^"\']+register-slide[^"\']*)', html)

    challenge = None
    if api_match:
        api_url = api_match.group(1)
        api_resp = session.get(api_url, timeout=10)
        try:
            data = api_resp.json()
            challenge = data.get("challenge")
            gt = gt or data.get("gt")
        except json.JSONDecodeError:
            pass

    if not challenge:
        # Try embedded challenge
        ch_match = re.search(r'challenge["\']?\s*[:=]\s*["\']([a-f0-9]+)', html)
        challenge = ch_match.group(1) if ch_match else None

    return {"gt": gt, "challenge": challenge, "pageurl": page_url}


# Usage
params = extract_geetest_v3("https://staging.example.com/qa-login")
print(f"gt: {params['gt']}")
print(f"challenge: {params['challenge']}")

Если gt не находится ни одним из способов — вероятно, виджет подгружается через JavaScript уже после первой отрисовки страницы; см. раздел «Типичные ошибки» ниже. И, как обычно при парсинге чужих страниц: собирайте только те данные, которые вы вправе обрабатывать в рамках своего QA- или интеграционного сценария — это касается и 152-ФЗ для проектов с российскими пользователями, и GDPR-подхода для трансграничных.


Отправляем задачу в CaptchaAI

Дальше — стандартный цикл in.php / res.php: отправляем gt, challenge и pageurl с методом geetest, получаем id задачи и опрашиваем результат:

# solve_geetest.py
import requests
import time
import os


def solve_geetest(gt, challenge, pageurl, api_server=None):
    """Solve GeeTest v3 slide CAPTCHA via CaptchaAI."""
    api_key = os.environ["CAPTCHAAI_API_KEY"]

    payload = {
        "key": api_key,
        "method": "geetest",
        "gt": gt,
        "challenge": challenge,
        "pageurl": pageurl,
        "json": 1,
    }

    if api_server:
        payload["api_server"] = api_server

    # Submit
    resp = requests.post(
        "https://ocr.captchaai.com/in.php",
        data=payload,
        timeout=30,
    )
    result = resp.json()

    if result.get("status") != 1:
        raise RuntimeError(f"Submit failed: {result.get('request')}")

    task_id = result["request"]

    # Poll — GeeTest typically solves in 10-20 seconds
    time.sleep(10)
    for _ in range(30):
        resp = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key,
            "action": "get",
            "id": task_id,
            "json": 1,
        }, timeout=15)
        data = resp.json()

        if data.get("status") == 1:
            return data["request"]  # Returns challenge, validate, seccode
        if data["request"] != "CAPCHA_NOT_READY":
            raise RuntimeError(data["request"])
        time.sleep(5)

    raise TimeoutError("GeeTest solve timeout")

Первая пауза в 10 секунд не случайна: GeeTest v3 обычно решается быстрее чем за 12 секунд, и опрашивать res.php раньше — только лишние запросы и риск упереться в ограничение частоты. На тарифе BASIC ($15/мес, 5 потоков) этого хватает для единичных проверок в staging; для параллельного прогона десятков сценариев логичнее смотреть в сторону STANDARD ($30/мес, 15 потоков) или ADVANCE ($90/мес, 50 потоков) — по одному потоку на каждую одновременную задачу.


Передаём решение на сайт

Ответ CaptchaAI содержит три поля — challenge, validate, seccode — их нужно отправить обратно в конечную точку проверки на целевом сайте, иначе решение засчитано не будет:

# submit_solution.py
import json


def submit_geetest_solution(session, validation_url, solution, original_challenge):
    """Submit GeeTest solution to the target site."""
    # Parse solution if string
    if isinstance(solution, str):
        solution = json.loads(solution)

    payload = {
        "geetest_challenge": solution.get("challenge", original_challenge),
        "geetest_validate": solution.get("validate", ""),
        "geetest_seccode": solution.get("seccode", ""),
    }

    resp = session.post(validation_url, data=payload, timeout=30)
    return resp


# Complete flow
def full_geetest_flow(page_url, validation_url):
    import requests
    from extract_geetest_params import extract_geetest_v3

    session = requests.Session()
    session.headers["User-Agent"] = (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36"
    )

    # Step 1: Extract parameters
    params = extract_geetest_v3(page_url, session)
    print(f"gt: {params['gt']}, challenge: {params['challenge'][:16]}...")

    # Step 2: Solve
    solution = solve_geetest(
        params["gt"], params["challenge"], params["pageurl"],
    )
    print("Solved!")

    # Step 3: Submit
    resp = submit_geetest_solution(
        session, validation_url, solution, params["challenge"],
    )
    print(f"Validation response: {resp.status_code}")
    return resp

full_geetest_flow собирает три шага в один прогон — удобно как основа для собственного воркера или для интеграционного теста, который дергается перед каждым релизом.


Свежесть challenge: главное правило

challenge привязан к конкретной сессии и живёт недолго:

# fresh_challenge.py
import time


def get_fresh_challenge(session, register_url):
    """Always fetch a fresh challenge before solving."""
    resp = session.get(register_url, timeout=10)
    data = resp.json()

    challenge = data.get("challenge")
    if not challenge:
        raise ValueError("No challenge returned")

    return challenge


def solve_with_fresh_challenge(session, gt, register_url, pageurl):
    """Ensure challenge is fresh before submitting to CaptchaAI."""
    challenge = get_fresh_challenge(session, register_url)

    # Submit immediately — don't let it expire
    solution = solve_geetest(gt, challenge, pageurl)
    return solution

Главное правило: получили challenge — сразу же отправляйте его в CaptchaAI, без пауз на логирование или retry-очереди. Устаревший challenge провалится всегда, сколько бы раз вы ни повторяли запрос. Это особенно заметно на мобильных сетях или при нестабильном канале до тестового стенда: если между извлечением параметров и отправкой задачи проходит лишняя секунда на пересборку соединения, лучше сразу закладывать повторное извлечение challenge, а не повтор с тем же значением.


Нестандартный API-сервер GeeTest

Часть сайтов — особенно с инфраструктурой в Европе или Казахстане — обслуживает GeeTest через собственный поддомен вместо api.geetest.com. Такой адрес нужно передать в api_server:

# The api_server parameter specifies a custom GeeTest backend
# Default: api.geetest.com
# Custom examples: api-na.geetest.com, api.geetest.com/ajax-custom

solution = solve_geetest(
    gt="abc123...",
    challenge="def456...",
    pageurl="https://staging.example.com/qa-login",
    api_server="api-na.geetest.com",  # North America endpoint
)

Если не передать api_server там, где он нужен, решение обычно отклоняется сайтом молча — без внятной ошибки от CaptchaAI, потому что сам токен корректен, просто выдан не под тот бэкенд GeeTest.


Типичные ошибки и их причины

Проблема Причина Что делать
ERROR_CAPTCHA_UNSOLVABLE Устаревший challenge Получите новый challenge прямо перед отправкой, не заранее
validate приходит пустым Не та версия API Используйте version=4 только для сайтов на GeeTest v4
Сайт отклоняет решение Не отправлен seccode Проверьте, что уходят все три поля — challenge, validate, seccode
gt не находится на странице Виджет подгружается через JavaScript Разбирайте XHR-ответы конечной точки регистрации или используйте Selenium

Вопросы, которые часто задают про параметры GeeTest

Чем gt отличается от challenge?

gt — это идентификатор аккаунта GeeTest на сайте, он не меняется от запроса к запросу. challenge генерируется под каждую сессию заново и извлекается непосредственно перед решением.

Сколько живёт challenge и когда его лучше извлекать?

Обычно 60–120 секунд. Извлекайте его прямо перед вызовом CaptchaAI, а не заранее по расписанию — старое значение приведёт к ERROR_CAPTCHA_UNSOLVABLE независимо от того, насколько корректны остальные параметры.

gt не находится ни в HTML, ни в ответе API — что делать?

Значит, виджет GeeTest рендерится динамически через JavaScript уже после загрузки страницы. Откройте вкладку сети в браузере, найдите запрос к register-slide, и заберите gt/challenge из его ответа — либо используйте headless-браузер, который дожидается полной отрисовки перед парсингом.

Сколько потоков нужно для стабильного прогона GeeTest?

Один поток закрывает одну задачу от отправки до получения решения. Для редких ручных проверок хватает BASIC (5 потоков); для параллельных сценариев в CI — считайте по числу одновременных сессий и берите тариф с запасом, а не впритык.

Решение отклонено сайтом, хотя challenge был свежим — в чём дело?

Скорее всего, до конечной точки проверки дошли не все три поля. Проверьте, что вы отправляете challenge, validate и seccode одновременно, а не только одно-два из них — если validate пустой, взгляните на раздел «Типичные ошибки» выше: чаще всего причина в несовпадении версии GeeTest на сайте и в запросе.


Похожие материалы


Разобрались с параметрами GeeTest v3 — переходите к CaptchaAI и отправляйте первое решение через API.

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