Разработка скрипта для обхода Geetest CAPTCHA на Python: от идеи до реализации

Введение в предмет - или почему распознавание капчи 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, ниже расскажу куда его нужно будет подставить.

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

#!/usr/bin/env python3 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, как это произошло у меня.

Начать дискуссию