Integrations

undetected-chromedriver и CaptchaAI: интеграция на Python

Рано или поздно даже аккуратно настроенный Chrome получит CAPTCHA — и сценарий автоматизации останавливается на форме входа. undetected-chromedriver убирает часть сигналов, по которым сайт понимает, что браузером управляет скрипт, но саму задачу CAPTCHA он не решает. Её решает CaptchaAI: вы отправляете параметры виджета в API, получаете готовый токен и подставляете его в форму.

Ниже — рабочая связка на Python целиком: сессия undetected-chromedriver для навигации, API CaptchaAI для reCAPTCHA v2 и Cloudflare Turnstile, расчёт потоков под нагрузку и разбор ошибок, на которые обычно уходит больше всего времени.


Два уровня одной задачи

Полезно сразу развести зоны ответственности — тогда понятно, что чинить, когда сценарий падает.

  • Уровень браузера. undetected-chromedriver — библиотека Python, которая патчит ChromeDriver из Selenium: подбирает драйвер под установленную версию Chrome, убирает часть флагов автоматизации и правит поведение, на которое смотрят защитные скрипты. Результат — проверок появляется меньше.
  • Уровень проверки. Если виджет всё-таки отрисовался, настройками браузера это уже не лечится. Нужен токен, а токен выдаёт решатель: CaptchaAI принимает sitekey и pageurl и возвращает значение для g-recaptcha-response или cf-turnstile-response.

Практический вывод: не пытайтесь «донастроить» браузер до состояния, в котором CAPTCHA не появляется никогда. Дешевле собрать предсказуемый цикл «увидели виджет → отправили задачу → подставили токен».


Что подготовить

Требование Подробности
API-ключ CaptchaAI личный кабинет CaptchaAI
Python 3.8+ подойдёт и системный, и в virtualenv
Chrome установлен локально; версию стоит зафиксировать

Установите зависимости:

pip install undetected-chromedriver requests

Шаг 1. Поднимите сессию undetected-chromedriver

uc.Chrome() при первом запуске сам скачает и пропатчит подходящий драйвер. Флаг --no-sandbox нужен в контейнерах, а фиксированный размер окна убирает расхождения в вёрстке между локальным прогоном и сервером.

import undetected_chromedriver as uc
import requests
import time


def create_stealth_browser():
    """Create an undetected Chrome browser instance."""
    options = uc.ChromeOptions()
    options.add_argument("--no-sandbox")
    options.add_argument("--window-size=1920,1080")

    driver = uc.Chrome(options=options)
    return driver

Шаг 2. Заберите sitekey со страницы

Ключ сайта почти всегда лежит в атрибуте data-sitekey. Запасной вариант — вытащить параметр k= из src фрейма reCAPTCHA: так устроены страницы, где виджет вставлен сторонним скриптом.

API_KEY = "YOUR_API_KEY"


def extract_recaptcha_sitekey(driver):
    """Extract reCAPTCHA v2 sitekey from the page."""
    try:
        element = driver.find_element("css selector", "[data-sitekey]")
        return element.get_attribute("data-sitekey")
    except Exception:
        # Try finding in iframe src
        iframes = driver.find_elements("css selector", "iframe[src*='recaptcha']")
        for iframe in iframes:
            src = iframe.get_attribute("src")
            if "k=" in src:
                return src.split("k=")[1].split("&")[0]
    return None

Шаг 3. Отправьте задачу в CaptchaAI

Схема стандартная: POST на in.php возвращает ID задачи, дальше вы опрашиваете res.php, пока ответ не перестанет быть CAPCHA_NOT_READY. reCAPTCHA v2 по SLA решается менее чем за 60 с, поэтому первая пауза в 20 с и последующий опрос раз в 5 с — разумная стартовая настройка. API-ключ держите в переменных окружения, а не в коде.

def solve_recaptcha_v2(sitekey, pageurl):
    """Submit reCAPTCHA v2 to CaptchaAI and return the token."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": pageurl,
        "json": 1
    }).json()

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

    task_id = submit["request"]

    time.sleep(20)
    for _ in range(30):
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1
        }).json()

        if result.get("status") == 1:
            return result["request"]
        if result.get("request") != "CAPCHA_NOT_READY":
            raise RuntimeError(f"Solve error: {result['request']}")
        time.sleep(5)

    raise TimeoutError("Solve timed out")

Шаг 4. Подставьте токен и отправьте форму

Токен пишется в скрытое поле g-recaptcha-response. Многие формы дополнительно ждут вызова коллбэка виджета — если после подстановки кнопка остаётся неактивной, дело почти всегда в нём.

def inject_recaptcha_token(driver, token):
    """Inject the solved token into the page and submit."""
    driver.execute_script(f'''
        document.getElementById("g-recaptcha-response").innerHTML = "{token}";
        document.getElementById("g-recaptcha-response").style.display = "block";
    ''')

    # If there's a callback function, trigger it
    driver.execute_script(f'''
        if (typeof ___grecaptcha_cfg !== 'undefined') {{
            var clients = ___grecaptcha_cfg.clients;
            for (var key in clients) {{
                var client = clients[key];
                if (client && client.callback) {{
                    client.callback("{token}");
                }}
            }}
        }}
    ''')

Готовый сценарий: вход в staging-форму с reCAPTCHA v2

Ниже — цельный скрипт для QA-стенда: заполнение полей, извлечение sitekey, решение и отправка формы. driver.quit() вынесен в finally — иначе на сервере быстро накапливаются осиротевшие процессы Chrome.

import undetected_chromedriver as uc
import requests
import time

API_KEY = "YOUR_API_KEY"


def solve_recaptcha(sitekey, pageurl):
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY, "method": "userrecaptcha",
        "googlekey": sitekey, "pageurl": pageurl, "json": 1
    }).json()

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

    task_id = submit["request"]
    time.sleep(20)

    for _ in range(30):
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1
        }).json()
        if result.get("status") == 1:
            return result["request"]
        if result.get("request") != "CAPCHA_NOT_READY":
            raise RuntimeError(f"Solve error: {result['request']}")
        time.sleep(5)
    raise TimeoutError("Solve timed out")


def main():
    driver = uc.Chrome()

    try:
        # Navigate to target page
        driver.get("https://staging.example.com/qa-login")
        time.sleep(3)

        # Fill in form fields
        driver.find_element("id", "username").send_keys("user")
        driver.find_element("id", "password").send_keys("pass")

        # Extract sitekey
        element = driver.find_element("css selector", "[data-sitekey]")
        sitekey = element.get_attribute("data-sitekey")
        pageurl = driver.current_url
        print(f"Sitekey: {sitekey}")

        # Solve CAPTCHA
        token = solve_recaptcha(sitekey, pageurl)
        print(f"Token: {token[:50]}...")

        # Inject token
        driver.execute_script(
            f'document.getElementById("g-recaptcha-response").innerHTML = "{token}";'
        )

        # Submit form
        driver.find_element("id", "submit-btn").click()
        time.sleep(3)

        print(f"Current URL: {driver.current_url}")
    finally:
        driver.quit()


if __name__ == "__main__":
    main()

Cloudflare Turnstile: тот же цикл, другой метод

Для Turnstile меняются два параметра: method становится turnstile, а ключ передаётся как sitekey. Токен кладётся в поле cf-turnstile-response. Turnstile по SLA решается менее чем за 10 с, поэтому стартовая пауза здесь короче.

def solve_turnstile(sitekey, pageurl):
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY, "method": "turnstile",
        "sitekey": sitekey, "pageurl": pageurl, "json": 1
    }).json()

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

    task_id = submit["request"]
    time.sleep(10)

    for _ in range(30):
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1
        }).json()
        if result.get("status") == 1:
            return result["request"]
        if result.get("request") != "CAPCHA_NOT_READY":
            raise RuntimeError(f"Solve error: {result['request']}")
        time.sleep(5)
    raise TimeoutError("Solve timed out")


# Inject Turnstile token
def inject_turnstile_token(driver, token):
    driver.execute_script(f'''
        var input = document.querySelector('[name="cf-turnstile-response"]');
        if (input) input.value = "{token}";
    ''')

Сколько потоков закладывать

Тарификация у CaptchaAI идёт по потокам, а не по числу решений: поток — это одна задача «в полёте», после её завершения он сразу берёт следующую. Число решений внутри тарифа не ограничено.

Практический пример: студия из Алматы гоняет ночной регресс — 12 параллельных сессий undetected-chromedriver, в каждой один вход с reCAPTCHA v2. Пик одновременных задач редко превышает десяток, поэтому STANDARD ($30/мес, 15 потоков) закрывает прогон с запасом; BASIC ($15/мес, 5 потоков) начнёт выстраивать очередь и растянет ночное окно. Если к регрессу добавляется дневной парсинг на 40–50 одновременных задач, следующая осмысленная ступень — ADVANCE ($90/мес, 50 потоков).

Для команд, которые выставляют счета в тенге, рублях или гривне, важен сам характер такой тарификации: фиксированная сумма в USD в месяц вместо плавающего счёта «за каждое решение» — бюджет считается один раз на квартал.


Что учесть при регулярных прогонах undetected-chromedriver

  • Права на данные. Если сценарий собирает что-то помимо тестовых учётных записей, заранее убедитесь, что вы вправе обрабатывать эти данные: для проектов в РФ ориентир — 152-ФЗ «О персональных данных», для трансграничных команд — привычная GDPR-дисциплина. Собирайте только то, что действительно нужно задаче.
  • География запуска. Прогон из европейского региона и из Центральной Азии даёт разное время приёма-передачи. Закладывайте в тайм-ауты запас на нестабильный канал, а не подгоняйте их под идеальный локальный прогон.
  • Повторы. Ответ CAPCHA_NOT_READY — это не ошибка, а сигнал продолжать опрос; отдельной обработки он не требует. А вот на сетевые сбои ставьте экспоненциальную задержку и ограничьте число повторов — иначе один недоступный узел съест все потоки.
  • Что поддерживается. reCAPTCHA v2 и v3, Cloudflare Turnstile, GeeTest v3, image/OCR и текстовые CAPTCHA — рабочие типы. hCaptcha и FunCaptcha CaptchaAI не решает, GeeTest v4 заявлен как «скоро». CaptchaFox (beta), Friendly Captcha (beta) и Lemin (beta) доступны в бета-режиме.

Типичные сбои

Симптом Причина Что сделать
Драйвер не стартует, ругается на версию Chrome обновился, подходящего драйвера нет Обновите Chrome или задайте version_main в uc.Chrome().
Виджет появляется каждый прогон Сайт применяет дополнительные проверки браузера Это штатная ситуация — решайте задачу через CaptchaAI.
Токен подставлен, форма не отправляется Не тот ID элемента или не вызван коллбэк виджета Проверьте разметку страницы и вызовите коллбэк вручную.
WebDriverException при старте Chrome упал в контейнере Добавьте --no-sandbox и --disable-dev-shm-usage.
Задача висит и уходит в тайм-аут Свободных потоков нет, задача ждёт в очереди Сократите параллелизм или перейдите на тариф выше.

Частые вопросы

Нужно ли фиксировать версию Chrome?

На CI — да. Автообновление Chrome ломает подбор драйвера чаще всего остального. Закрепите версию в образе и передавайте version_main в uc.Chrome().

Сколько потоков нужно для 20 параллельных сессий?

Считайте по пику одновременных задач, а не по числу сессий. Если CAPTCHA одновременно требуется примерно половине из 20 сессий, 15 потоков STANDARD ($30/мес) хватает; при стабильных 20+ задачах берите ADVANCE ($90/мес, 50 потоков).

Почему форма возвращает ошибку, хотя токен получен?

Три обычные причины: токен подставлен не в то поле, не вызван коллбэк виджета либо между получением токена и отправкой формы прошло слишком много времени — токены живут недолго. Отправляйте форму сразу после подстановки.

Подойдёт ли этот код для reCAPTCHA v3 и GeeTest v3?

Логика цикла та же, меняется набор параметров: для v3 добавляются version, action и min_score, для GeeTest v3 — свой метод и свои параметры вызова. Само взаимодействие с браузером не меняется.

Можно ли запускать это на сервере без графической оболочки?

Да. Рабочие варианты — --headless=new или обычный headed-режим под Xvfb. Учтите, что в headless-режиме проверки срабатывают чаще, поэтому закладывайте больше решений на прогон.


Подключите CaptchaAI к своей автоматизации

Получите API-ключ на captchaai.com, подставьте его в API_KEY и прогоните сценарий из раздела с полным примером — первый токен вернётся в течение минуты.


Читайте дальше

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