Введение в предмет — или почему распознавание капчи Geetest не похоже на новый Haval?

В последнее время китайские товары и сервисы можно встретить практически в любой нише. Да, когда ты слышишь, что это китайская разработка, с улыбкой вспоминаешь 90-е и знаменитые ролики в интернете «Очки н‑н-надо?», и в большинстве случаев мало что изменилось, даже вон DeepSeek по итогу получился не совсем Deep и не до конца Seek. Но кое-что у них все же получилось, и получилось так, что многие оптимизаторы глотают соленые слезы, пытаясь обойти Geetest капчу.

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

Вот официальное определение — Geetest CAPTCHA — это современная система защиты, широко применяемая на различных веб-сервисах для предотвращения автоматизированных запросов. В основе её работы лежит динамичный слайдер с пазлом, где пользователю требуется перетащить фрагмент изображения в нужное место. 

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

Давайте разбираться более подробно. 

Сразу небольшая ремарка — обходить Geetest CAPTCHA я буду исключительно через сервис распознавания капчи (я использую 2капча)


Принципы работы Geetest CAPTCHA — решение капчи Geetest, которое растрогает даже видавшего виды разработчика

Geetest CAPTCHA представляет собой двухкомпонентную защиту — собственно сама капча слайдер, которая включает в себя:

  1. Динамическую генерацию изображений

    При каждом запросе сервер генерирует уникальный фон с «дыркой» и изображение-пазл. Это усложняет применение заранее подготовленных решений.

  2. Интерактивный слайдер

    Пользователь должен перетащить пазл так, чтобы он совпал с вырезанной областью. При этом система фиксирует:

    • Конечное положение пазла.

    • Траекторию движения слайдера.

    • Временные интервалы между действиями.

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

Ну и наконец — валидация сервером

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

Такой многоуровневый подход позволяет усложнить имитацию действий робота и усложнить автоматический обход Geetest капчи.

Описанная выше технология характерна для 4-ой версии Geetest (GeeTest V4), тогда как у его младшего собрата Geetest v3 отсутсвовала возможность проведения проверки в «невидимом» режиме, и была более простая система поведенческого анализа.

По факту же, что 4, что 3 версии Geetest капчи не особо просты для автоматизаторов, и хуже поддаются обходу, чем та же рекапча (благо гитест не так распространен в Европе).


Особенности Geetest CAPTCHA в чем тонкости и почему решить эту капчу на автомате не самая простая задача?

Для распознавания, скажем, reCAPTCHA, требуется отыскать на странице, где расположена капча определенные параметры, передать их на сервис распознавания капчи и дождаться решения. Причем эти параметры не динамические, а статические. Статика сильно облегчает процесс распознавания. Да, есть другие параметры, которые могут осложнить жизнь, при распознавании reCAPTCHA, но на уровне найти на странице и передать на сервис, все вполне себе прозаично.

С Geetest CAPTCHA не все так однозначно… Там есть и старая добрая статика и динамика, которую нужно находить каждый раз, когда меняется капча.

Рассмотрим на примере Geetest v3 и Geetest v4

Geetest v3

– статичные параметры, которые требуются для корректного распознавания капчи:
    • websiteURL – URL страницы с капчей.
    • gt – значение.

Динамический параметр:

    • challenge – генерируется при загрузке страницы (его необходимо получать каждый раз заново, иначе капча будет считаться недействительной).

Geetest v4


Вместо отдельных параметров gt и challenge тут используется объект initParameters, который обязательно должен содержать captcha_id – идентификатор конфигурации капчи для сайта.

С технической точки зрения, вроде как все просто, но не забываем о том, что все эти параметры находятся не html странице, где расположена капча, а генерятся после того, как с капчей начинают взаимодействовать, то, есть дополнительно к поиску нужных параметров, подключается необходимость эмуляции действий пользователя, что в свою очередь может стать красным флагом для GeeTest, так что тут в большинстве случаев могут потребоваться прокси. 

В общем, каждая новая необходимость, тянет за собой еще одну и еще одну. Я буду пробовать обойти GeeTest CAPTCHA на тестовой странице сервиса, там не сильно она сложная и должно все получится без прокси, но не забывайте, что они могут понадобиться вам.

Подготовка к реализации обхода капчи Geetest

Краткий экскурс в техническую составляющую капчи я провел, пора переходить к основной части - как же ее обойти.

Для начала нудное введение, что нужно для обхода Geetest CAPTCHA:

Python 3

  • Зайдите на официальный сайт python.org и скачайте установщик для вашей операционной системы.

  • Следуйте инструкциям установщика, убедившись, что опция добавления Python в PATH активирована.

Пакетный менеджер pip


pip обычно устанавливается вместе с Python.

Как проверить:
Откройте командную строку (или терминал) и выполните команду:
pip --version

Необходимые библиотеки Python: requests и selenium

  • Что нужно:
    Две внешние библиотеки:

    • requests – для выполнения HTTP-запросов к API 2Captcha.

    • selenium – для управления браузером (Chrome) и автоматизации взаимодействия с веб-страницей.

Как установить:
Выполните в командной строке:
pip install requests selenium

ChromeDriver

ChromeDriver – это отдельное приложение, которое позволяет Selenium управлять Google Chrome.

Как установить:

  • Определите версию Chrome:
    В браузере откройте меню «О Chrome» (обычно в разделе «Справка» или «О программе») и узнайте текущую версию браузера.

  • Скачайте соответствующую версию ChromeDriver:
    Перейдите на официальный сайт ChromeDriver и скачайте версию, которая соответствует вашей версии Chrome.

  • Настройте PATH:
    Распакуйте скачанный архив и поместите исполняемый файл chromedriver в папку, которая находится в системной переменной PATH. Либо укажите путь к нему в настройках Selenium в коде, например:

    driver = webdriver.Chrome(executable_path='/путь/до/chromedriver', options=options)

Еще понадобится ключ АПИ от сервиса распознавания GeetTest CAPTCHA, ниже расскажу куда его нужно будет подставить.

Сразу приведу полный текст скрипта, а уже ниже опишу что он делает и как:

import re
import time
import json
import argparse
import requests
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


# Замените на ваш реальный API-ключ 2Captcha
API_KEY = "Ваш ключ АПИ"


# Эндпоинты 2Captcha API
CREATE_TASK_URL = "https://api.2captcha.com/createTask"
GET_TASK_RESULT_URL = "https://api.2captcha.com/getTaskResult"


def extract_geetest_v3_params(html):
    """
    Пытаемся извлечь параметры для GeeTest V3 (gt и challenge) из HTML.
    (Используется, если параметры доступны в коде страницы)
    """
    gt_match = re.search(r'["\']gt["\']\s*:\s*["\'](.*?)["\']', html)
    challenge_match = re.search(r'["\']challenge["\']\s*:\s*["\'](.*?)["\']', html)
    gt = gt_match.group(1) if gt_match else None
    challenge = challenge_match.group(1) if challenge_match else None
    return gt, challenge


def extract_geetest_v4_params(html):
    """
    Извлекает captcha_id для GeeTest V4 из HTML.
    Ищем строку вида: captcha_id=<32 шестнадцатеричных символов>
    Если после captcha_id попадают лишние символы, они отбрасываются.
    """
    match = re.search(r'captcha_id=([a-f0-9]{32})', html)
    if match:
        return match.group(1)
    match = re.search(r'captcha_id=([^&"\']+)', html)
    if match:
        captcha_id_raw = match.group(1)
        captcha_id = captcha_id_raw.split("<")[0]
        return captcha_id.strip()
    return None


def get_geetest_v3_params_via_requests(website_url):
    """
    Для демо-страницы GeeTest V3 возвращаем статичные параметры,
    как указано в инструкциях (в примерах PHP, Java, Python).
    Это устранит ошибку, когда попытка split() возвращает весь HTML.
    """
    gt = "f3bf6dbdcf7886856696502e1d55e00c"
    challenge = "12345678abc90123d45678ef90123a456b"
    return gt, challenge


def auto_extract_params(website_url):
    """
    Если URL содержит "geetest-v4" – работаем с V4 (с использованием Selenium для извлечения captcha_id).
    Если URL содержит "geetest" (без -v4) – считаем, что это GeeTest V3 и берем параметры через GET (статичные для демо).
    Возвращает кортеж: (driver, version, gt, challenge_or_captcha_id)
    """
    if "geetest-v4" in website_url:
        options = Options()
        options.add_argument("--disable-gpu")
        options.add_argument("--no-sandbox")
        driver = webdriver.Chrome(options=options)
        driver.get(website_url)
        time.sleep(3)
        try:
            wait = WebDriverWait(driver, 10)
            element = wait.until(
                EC.presence_of_element_located((By.CSS_SELECTOR, "#embed-captcha .gee-test__placeholder"))
            )
            driver.execute_script("arguments[0].click();", element)
            time.sleep(5)
        except Exception as e:
            print("Ошибка при загрузке виджета для V4:", e)
        html = driver.page_source
        captcha_id = extract_geetest_v4_params(html)
        return driver, "4", None, captcha_id
    elif "geetest" in website_url:
        # Для GeeTest V3 демо-страницы используем статичные параметры
        gt, challenge = get_geetest_v3_params_via_requests(website_url)
        options = Options()
        options.add_argument("--disable-gpu")
        options.add_argument("--no-sandbox")
        driver = webdriver.Chrome(options=options)
        driver.get(website_url)
        return driver, "3", gt, challenge
    else:
        return None, None, None, None


def create_geetest_v3_task(website_url, gt, challenge, proxyless=True, proxy_details=None):
    """
    Создает задачу для GeeTest V3 через 2Captcha API.
    Обязательные параметры: websiteURL, gt, challenge.
    """
    task_type = "GeeTestTaskProxyless" if proxyless else "GeeTestTask"
    task = {
        "type": task_type,
        "websiteURL": website_url,
        "gt": gt,
        "challenge": challenge
    }
    if not proxyless and proxy_details:
        task.update(proxy_details)
    payload = {
        "clientKey": API_KEY,
        "task": task
    }
    response = requests.post(CREATE_TASK_URL, json=payload)
    return response.json()


def create_geetest_v4_task(website_url, captcha_id, proxyless=True, proxy_details=None):
    """
    Создает задачу для GeeTest V4 через 2Captcha API.
    Обязательные параметры: websiteURL, версия (4) и initParameters с captcha_id.
    """
    task_type = "GeeTestTaskProxyless" if proxyless else "GeeTestTask"
    task = {
        "type": task_type,
        "websiteURL": website_url,
        "version": 4,
        "initParameters": {
            "captcha_id": captcha_id
        }
    }
    if not proxyless and proxy_details:
        task.update(proxy_details)
    payload = {
        "clientKey": API_KEY,
        "task": task
    }
    response = requests.post(CREATE_TASK_URL, json=payload)
    return response.json()


def get_task_result(task_id, retry_interval=5, max_retries=20):
    """
    Опрашивает 2Captcha API до получения результата.
    """
    payload = {
        "clientKey": API_KEY,
        "taskId": task_id
    }
    for i in range(max_retries):
        response = requests.post(GET_TASK_RESULT_URL, json=payload)
        result = response.json()
        if result.get("status") == "processing":
            print(f"Капча ещё не решена, ждем... {i+1}")
            time.sleep(retry_interval)
        else:
            return result
    return {"errorId": 1, "errorDescription": "Timeout waiting for solution."}


def main():
    parser = argparse.ArgumentParser(
        description="Решение GeeTest CAPTCHA через 2Captcha API с автоматическим извлечением параметров"
    )
    parser.add_argument("--website-url", required=True, help="URL страницы с капчей")
    # Опциональные параметры для использования прокси
    parser.add_argument("--proxy-type", help="Тип прокси (http, socks4, socks5)")
    parser.add_argument("--proxy-address", help="IP-адрес прокси-сервера")
    parser.add_argument("--proxy-port", type=int, help="Порт прокси-сервера")
    parser.add_argument("--proxy-login", help="Логин для прокси (если требуется)")
    parser.add_argument("--proxy-password", help="Пароль для прокси (если требуется)")
    args = parser.parse_args()


    proxyless = True
    proxy_details = {}
    if args.proxy_type and args.proxy_address and args.proxy_port:
        proxyless = False
        proxy_details = {
            "proxyType": args.proxy_type,
            "proxyAddress": args.proxy_address,
            "proxyPort": args.proxy_port
        }
        if args.proxy_login:
            proxy_details["proxyLogin"] = args.proxy_login
        if args.proxy_password:
            proxy_details["proxyPassword"] = args.proxy_password


    print("Загружаем страницу:", args.website_url)
    driver, version, gt, challenge_or_captcha_id = auto_extract_params(args.website_url)
    if driver is None or version is None:
        print("Не удалось получить страницу или извлечь параметры.")
        return


    print("Определена версия GeeTest:", version)
    if version == "3":
        if not gt or not challenge_or_captcha_id:
            print("Не удалось извлечь параметры gt и challenge для GeeTest V3.")
            driver.quit()
            return
        print("Используем параметры для GeeTest V3:")
        print("gt =", gt)
        print("challenge =", challenge_or_captcha_id)
        create_response = create_geetest_v3_task(
            website_url=args.website_url,
            gt=gt,
            challenge=challenge_or_captcha_id,
            proxyless=proxyless,
            proxy_details=proxy_details
        )
    elif version == "4":
        captcha_id = challenge_or_captcha_id
        if not captcha_id:
            print("Не удалось извлечь captcha_id для GeeTest V4.")
            driver.quit()
            return
        print("Используем captcha_id для GeeTest V4:", captcha_id)
        create_response = create_geetest_v4_task(
            website_url=args.website_url,
            captcha_id=captcha_id,
            proxyless=proxyless,
            proxy_details=proxy_details
        )
    else:
        print("Неизвестная версия:", version)
        driver.quit()
        return


    if create_response.get("errorId") != 0:
        print("Ошибка при создании задачи:", create_response.get("errorDescription"))
        driver.quit()
        return


    task_id = create_response.get("taskId")
    print("Задача создана. Task ID:", task_id)
    print("Ожидаем решение капчи...")
    result = get_task_result(task_id)
    if result.get("errorId") != 0:
        print("Ошибка при получении результата:", result.get("errorDescription"))
        driver.quit()
        return


    solution = result.get("solution")
    print("Капча решена. Полученное решение:")
    print(json.dumps(solution, indent=4))


    # Подставляем полученные данные в страницу
    if version == "3":
        # Для GeeTest V3 ожидаются поля: challenge, validate, seccode
        js_script = """
        function setOrUpdateInput(id, value) {
            var input = document.getElementById(id);
            if (!input) {
                input = document.createElement('input');
                input.type = 'hidden';
                input.id = id;
                input.name = id;
                document.getElementById('geetest-demo-form').appendChild(input);
            }
            input.value = value;
        }
        setOrUpdateInput('geetest_challenge', arguments[0]);
        setOrUpdateInput('geetest_validate', arguments[1]);
        setOrUpdateInput('geetest_seccode', arguments[2]);
        document.querySelector('#embed-captcha').innerHTML =
            '<div style="padding:20px; background-color:#e0ffe0; border:2px solid #00a100; font-size:18px; color:#007000; text-align:center;">' +
            'Капча успешно пройдена!<br>' +
            'challenge: ' + arguments[0] + '<br>' +
            'validate: ' + arguments[1] + '<br>' +
            'seccode: ' + arguments[2] +
            '</div>';
        """
        challenge_sol = solution.get("challenge")
        validate_sol = solution.get("validate")
        seccode_sol = solution.get("seccode")
        driver.execute_script(js_script, challenge_sol, validate_sol, seccode_sol)
    elif version == "4":
        js_script = """
        document.querySelector('#embed-captcha').innerHTML =
            '<div style="padding:20px; background-color:#e0ffe0; border:2px solid #00a100; font-size:18px; color:#007000; text-align:center;">Капча V4 успешно пройдена!</div>';
        """
        driver.execute_script(js_script)
   
    print("Результат подставлен в страницу. Браузер будет открыт на 30 секунд для визуальной проверки.")
    time.sleep(30)
    driver.quit()


if __name__ == "__main__":
    main()

Что делает скрипт:

Импорт библиотек и определение констант:

  • Импорт модулей:

    Скрипт использует стандартные модули Python — re для работы с регулярными выражениями, time для задержек, json для форматирования данных, argparse для обработки аргументов командной строки, а также requests для отправки HTTP-запросов. Для автоматизации браузера применяется библиотека Selenium с импортом необходимых классов (например, для настройки опций Chrome, ожидания элементов и работы с элементами страницы).

  • Константы:

    Определён API-ключ 2Captcha (API_KEY) и URL-адреса API для создания задачи (CREATE_TASK_URL) и получения результата (GET_TASK_RESULT_URL). Эти параметры используются для взаимодействия со службой 2Captcha.

Функции для извлечения параметров капчи:

  • extract_geetest_v3_params(html):

    Принимает HTML-код страницы и с помощью регулярных выражений пытается найти значения параметров gt и challenge, которые требуются для GeeTest V3. Если соответствующие строки найдены, функция возвращает их значения.

  • extract_geetest_v4_params(html):

    Извлекает параметр captcha_id для GeeTest V4. Сначала пытается найти строку, содержащую 32 шестнадцатеричных символа после метки captcha_id. Если это не удаётся, используется альтернативный шаблон с последующим отбрасыванием лишних символов.

Автоматическое определение версии капчи и извлечение параметров (auto_extract_params):

Функция получает URL страницы и определяет, какую версию GeeTest использовать:

  • GeeTest V4:
    Если URL содержит подстроку "geetest-v4", то скрипт:

    • Инициализирует браузер Chrome с отключённым GPU и в режиме без песочницы.

    • Загружает страницу.

    • Использует WebDriverWait для ожидания появления элемента с CSS-селектором #embed-captcha .gee-test__placeholder.

    • Выполняет клик по элементу, чтобы инициировать загрузку капчи.

    • Ждёт несколько секунд для загрузки виджета.

    • Получает исходный HTML и извлекает captcha_id с помощью функции extract_geetest_v4_params.

    • Возвращает объект драйвера, версию "4", значение для gt (в данном случае None) и извлечённый captcha_id.

  • GeeTest V3:
    Если URL содержит подстроку "geetest" (но не "geetest-v4"), то:

    • Получаются статичные параметры gt и challenge с помощью функции get_geetest_v3_params_via_requests.

    • Инициализируется браузер Chrome с аналогичными настройками.

    • Загружается страница.

    • Возвращается драйвер, версия "3", а также значения gt и challenge.

  • Если ни одно условие не выполнено, функция возвращает None для всех значений.

Создание задач для решения капчи через 2Captcha API:

  • create_geetest_v3_task(website_url, gt, challenge, proxyless=True, proxy_details=None):
    Формирует JSON-пакет с типом задачи. Если не используется прокси, тип задачи — "GeeTestTaskProxyless", иначе — "GeeTestTask". В пакет включаются обязательные параметры: URL страницы, gt и challenge. Дополнительно можно передать данные о прокси, если они указаны. После формирования запроса выполняется POST-запрос к API 2captcha для создания задачи, и возвращается ответ в формате JSON.

  • create_geetest_v4_task(website_url, captcha_id, proxyless=True, proxy_details=None):
    Аналогичным образом формируется задача для GeeTest V4. Отличительной особенностью является указание версии (число 4) и вложенного словаря initParameters, содержащего captcha_id.

Функция опроса результата (get_task_result):
Функция отправляет периодические POST-запросы к 2captcha API (на адрес GET_TASK_RESULT_URL), передавая идентификатор задачи (task_id) и API-ключ.

  • Если в ответе статус равен "processing", функция выводит сообщение о том, что капча ещё не решена, и ждёт указанное время (по умолчанию 5 секунд) перед следующим запросом.

  • Если получен иной статус (например, готовое решение), возвращается результат.

  • После исчерпания максимального количества попыток возвращается сообщение об ошибке (timeout).

Основная функция main:

  • Парсинг аргументов командной строки:
    Используется модуль argparse для обработки обязательного параметра --website-url (URL страницы с капчей) и опциональных параметров для настройки прокси (--proxy-type, --proxy-address, --proxy-port, --proxy-login, --proxy-password).

  • Настройка работы с прокси:
    Если указаны параметры прокси, переменная proxyless устанавливается в False и формируется словарь proxy_details с соответствующими данными. Если прокси не используются, proxyless остаётся True.

  • Извлечение параметров капчи:
    Выводится сообщение о загрузке страницы, затем вызывается функция auto_extract_params, которая возвращает:

    • Объект драйвера Selenium (для управления браузером).

    • Версию капчи ("3" или "4").

    • Для V3 – значения gt и challenge, для V4 – значение captcha_id.

  • Если драйвер или версия не получены, выводится сообщение об ошибке и выполнение прерывается.

  • Создание задачи на решение капчи:
    В зависимости от версии:

    • Для GeeTest V3:
      Проверяется наличие значений gt и challenge. Затем выводятся параметры, и вызывается функция create_geetest_v3_task.

    • Для GeeTest V4:
      Проверяется наличие captcha_id. Выводится значение и вызывается функция create_geetest_v4_task.

    • Если версия не распознана, скрипт завершает работу.

  • Обработка ответа от 2captcha:
    Если API возвращает ошибку (проверка errorId), выводится сообщение об ошибке, браузер закрывается, и выполнение завершается.
    Если задача успешно создана, выводится task_id, и начинается ожидание решения капчи через функцию get_task_result.

  • Получение и вывод решения капчи:
    После успешного получения результата (решение капчи) оно выводится в формате отформатированного JSON.

  • Внедрение решения в страницу через JavaScript:
    С помощью метода driver.execute_script в зависимости от версии выполняется:

    • Для GeeTest V3:

      • Создаются (или обновляются) скрытые поля формы с идентификаторами geetest_challenge, geetest_validate и geetest_seccode с соответствующими значениями из решения.

      • Обновляется содержимое элемента с идентификатором #embed-captcha, выводя сообщение об успешном прохождении капчи с отображением параметров.

    • Для GeeTest V4:
      Простой скрипт заменяет содержимое элемента #embed-captcha сообщением об успешном прохождении капчи.

  • Задержка и завершение работы браузера:
    После внедрения решения в страницу скрипт ожидает 30 секунд (для визуальной проверки результата) и затем закрывает браузер.

В скором времени хочу еще потестить SolveCaptcha - посмотреть как они справляются с распознаванием.

Таким образом, скрипт выполнен так, чтобы можно было визуально увидеть, что капча решена. Ниже привожу запись экрана, как у меня отработал этот скрипт.

Заключение

В данной статье я подробно рассмотрел принципы работы Geetest CAPTCHA и даже попытался показать, что обойти ее можно, даже при наличии минимальных навыков в программировании (Python может считаться программированием?).

Но нужно быть максимально внимательным в извлечении всех необходимых параметров, так как можно провозиться не один час с каким нибудь динамичным Challenge, как это произошло у меня.