Use Cases

Обработка Selenium CAPTCHA с помощью Python и CaptchaAI

Selenium не проходит CAPTCHA — и не должен. Проверку решает внешний сервис, а браузер остаётся тем, чем был: открывает страницу, читает DOM и отправляет форму. Скрипт достаёт sitekey из вёрстки, передаёт его вместе с URL в API CaptchaAI, получает токен и подставляет его в скрытое поле формы. По виджету при этом никто не кликает.

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

Что понадобится перед стартом

Компонент Как поставить или получить
Python 3.7+ вместе с pip
Selenium pip install selenium
Chrome + ChromeDriver совпадающие мажорные версии
requests pip install requests
API-ключ CaptchaAI в панели управления на captchaai.com

Если ключ ещё не заведён, сделайте первый запрос к API вручную, без Selenium: так вы отделите проблемы интеграции от проблем браузера.

Схема работы: четыре шага вместо клика по виджету

  1. Selenium открывает целевую страницу.
  2. Скрипт извлекает sitekey проверки CAPTCHA из DOM.
  3. CaptchaAI решает задачу по паре «sitekey + URL страницы».
  4. Скрипт вставляет полученный токен в форму и отправляет её.

CAPTCHA решается на стороне сервиса: Selenium не взаимодействует с виджетом, ему достаточно загрузить страницу и отдать два параметра.

Шаг 1: настройте драйвер Chrome

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

options = Options()
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")

driver = webdriver.Chrome(options=options)

Аргумент --disable-blink-features=AutomationControlled убирает заметный признак автоматизации, из-за которого часть форм ведёт себя иначе даже при корректном токене. Ещё два параметра снимают служебную панель Chrome:

options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)

Драйвер можно запускать и в headless-режиме: для получения токена достаточно, чтобы страница отрисовалась и sitekey появился в DOM.

Шаг 2: напишите функцию решения

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_recaptcha_v2(site_key, page_url):
    """Solve reCAPTCHA v2 using CaptchaAI API."""
    resp = requests.get("https://ocr.captchaai.com/in.php", params={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url
    })
    if not resp.text.startswith("OK|"):
        raise Exception(f"Submit failed: {resp.text}")

    task_id = resp.text.split("|")[1]

    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY,
            "action": "get",
            "id": task_id
        })
        if result.text == "CAPCHA_NOT_READY":
            continue
        if result.text.startswith("OK|"):
            return result.text.split("|")[1]
        raise Exception(f"Solve failed: {result.text}")

    raise TimeoutError("CAPTCHA solve timed out")

Что важно в этом коде:

  • in.php принимает задачу и возвращает OK|<ID задачи>; res.php опрашивается до готовности результата.
  • Ответ CAPCHA_NOT_READY — рабочее состояние очереди. Любой другой текст без префикса OK| — код ошибки, его лучше пробрасывать наружу, а не глотать.
  • Интервал 5 секунд и 60 итераций дают запас примерно на пять минут. На практике ответ приходит раньше, но при нестабильном канале запас снимает ложные тайм-ауты.

Тарификация построена на потоках, а не на количестве решений: BASIC ($15/мес, 5 потоков) — это пять одновременных задач без лимита на их число за месяц. Для прогона на 20–30 воркеров берите ADVANCE ($90/мес, 50 потоков).

Шаг 3: получите sitekey со страницы

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

# Wait for the reCAPTCHA to load
wait = WebDriverWait(driver, 10)
recaptcha = wait.until(
    EC.presence_of_element_located((By.CLASS_NAME, "g-recaptcha"))
)

# Extract the site key
site_key = recaptcha.get_attribute("data-sitekey")
page_url = driver.current_url

print(f"Site key: {site_key}")
print(f"Page URL: {page_url}")

# Solve the CAPTCHA
token = solve_recaptcha_v2(site_key, page_url)
print(f"Token received: {token[:50]}...")

Две детали, на которых чаще всего спотыкаются:

  • WebDriverWait здесь не формальность: если виджет подгружается скриптом после основного контента, немедленный поиск g-recaptcha упадёт с NoSuchElementException.
  • page_url берите из driver.current_url, а не из константы: при редиректе сервису нужен фактический адрес, на котором отрисован виджет.

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

# Inject the token into the reCAPTCHA response field
driver.execute_script(f"""
    document.getElementById('g-recaptcha-response').innerHTML = '{token}';
    document.getElementById('g-recaptcha-response').style.display = '';
""")

# If the form uses a callback function, trigger it
driver.execute_script(f"""
    if (typeof ___grecaptcha_cfg !== 'undefined') {{
        Object.keys(___grecaptcha_cfg.clients).forEach(function(key) {{
            var client = ___grecaptcha_cfg.clients[key];
            if (client.callback) client.callback('{token}');
        }});
    }}
""")

# Submit the form
driver.find_element(By.CSS_SELECTOR, "form").submit()

# Wait for navigation
wait.until(EC.url_changes(page_url))
print(f"Success! Now on: {driver.current_url}")

Здесь работают два разных сценария:

  • Обычная форма. Первый вызов execute_script заполняет скрытое поле g-recaptcha-response и снимает display: none — часть форм проверяет видимость поля.
  • Форма с обратным вызовом. Сабмита нет, страница ждёт вызова JavaScript-функции с токеном; перебор ___grecaptcha_cfg.clients запускает её вручную.

Токен живёт около двух минут — всё долгое (скриншот, запись в базу) переносите после сабмита.

Полный пример: один файл, который можно запустить

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
import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_recaptcha_v2(site_key, page_url):
    resp = requests.get("https://ocr.captchaai.com/in.php", params={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url
    })
    if not resp.text.startswith("OK|"):
        raise Exception(f"Submit failed: {resp.text}")
    task_id = resp.text.split("|")[1]

    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id
        })
        if result.text == "CAPCHA_NOT_READY":
            continue
        if result.text.startswith("OK|"):
            return result.text.split("|")[1]
        raise Exception(f"Solve failed: {result.text}")
    raise TimeoutError("Timed out")

def main():
    options = Options()
    options.add_argument("--disable-blink-features=AutomationControlled")
    driver = webdriver.Chrome(options=options)

    try:
        driver.get("https://staging.example.com/qa-login")
        wait = WebDriverWait(driver, 10)

        # Extract site key
        recaptcha = wait.until(
            EC.presence_of_element_located((By.CLASS_NAME, "g-recaptcha"))
        )
        site_key = recaptcha.get_attribute("data-sitekey")

        # Solve
        token = solve_recaptcha_v2(site_key, driver.current_url)

        # Inject and submit
        driver.execute_script(
            f"document.getElementById('g-recaptcha-response').innerHTML = '{token}';"
        )
        driver.find_element(By.CSS_SELECTOR, "form").submit()

        wait.until(EC.url_changes(driver.current_url))
        print("Login successful!")

    finally:
        driver.quit()

if __name__ == "__main__":
    main()

Блок try/finally с driver.quit() обязателен: без него упавший на середине прогон оставляет процесс chromedriver в памяти, и на CI такие «хвосты» копятся, пока раннер не начнёт падать по нехватке ресурсов.

Другие типы проверок в том же скрипте

Смена типа CAPTCHA меняет только тело запроса к in.php; опрос res.php и вставка токена остаются прежними.

reCAPTCHA v3

def solve_recaptcha_v3(site_key, page_url, action="verify"):
    resp = requests.get("https://ocr.captchaai.com/in.php", params={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url,
        "version": "v3",
        "action": action
    })
    task_id = resp.text.split("|")[1]
    # ... same polling logic

У v3 нет виджета и клика: сервер оценивает поведение и возвращает score. Параметр action должен совпадать с тем, который использует страница, иначе оценка окажется низкой. Отдельного поля для токена здесь обычно нет — его подставляют в скрытый input формы.

Cloudflare Turnstile

def solve_turnstile(site_key, page_url):
    resp = requests.get("https://ocr.captchaai.com/in.php", params={
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": site_key,
        "pageurl": page_url
    })
    task_id = resp.text.split("|")[1]
    # ... same polling logic

Обратите внимание на имя параметра: у Turnstile это sitekey, а не googlekey, и токен вставляется в поле cf-turnstile-response. Для GeeTest v3 логика опроса та же, но параметров в запросе больше.

Прежде чем строить сценарий, сверьтесь с матрицей типов:

  • Штатно: reCAPTCHA v2 и v3 (включая Enterprise-варианты), Cloudflare Turnstile и Cloudflare Challenge, GeeTest v3, картиночные и текстовые проверки, grid-image, BLS CAPTCHA.
  • В бета-статусе: CaptchaFox (beta), Friendly Captcha (beta), Lemin (beta).
  • Не решается: hCaptcha и FunCaptcha (Arkose Labs). Поддержка GeeTest v4 заявлена как «скоро» и пока недоступна.

Сценарий из практики: ночной прогон регрессии

Знакомая ситуация в командах, которые деплоятся в европейские регионы или в дата-центры Казахстана: ночной прогон из 40 сценариев, у восьми на входе форма с reCAPTCHA v2. Эти восемь помечали как skip, и регрессия не покрывала авторизацию. Что меняется, когда решение проверки вынесено в API:

  • Стенд, а не боевой сайт. Тесты идут по staging-домену с тем же sitekey, что и на проде.
  • Параллелизм упирается в потоки. Восемь сценариев в один поток встанут в очередь; на пяти потоках BASIC они пройдут почти одновременно.
  • Тайм-аут теста считайте от времени решения. При общем лимите сценария в 60 секунд и 30 секундах на ответ сервиса лимит нужно поднять — иначе тест упадёт не по логике, а по секундомеру.
  • Данные под контролем. Требования 152-ФЗ «О персональных данных» действуют и в тестовом контуре, поэтому проще держать на стенде синтетические записи.

Что ломается чаще всего

Симптом Причина Что делать
Форма отклоняет токен срок действия токена истёк вставляйте и отправляйте в пределах 120 с
sitekey не найден виджет подгружается асинхронно увеличьте тайм-аут WebDriverWait
NoSuchElementException неверный селектор сверьте актуальную вёрстку в DevTools
Ошибка версии ChromeDriver Chrome обновился поставьте драйвер под текущий мажорный релиз
ERROR_ZERO_BALANCE в ответе res.php закончился оплаченный период проверьте баланс в панели управления
Токен принят, но форма не уходит форма ждёт обратного вызова вызовите callback через execute_script
Проверка пройдена, доступ всё равно закрыт на сайте есть защита помимо CAPTCHA это отдельный слой, токен на него не влияет

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

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

Столько, сколько сценариев с CAPTCHA идёт одновременно. Пять параллельных тестов — пять потоков (BASIC, $15/мес). Общее число решений за месяц роли не играет: ограничена только одновременность.

Почему res.php долго отвечает CAPCHA_NOT_READY?

Это нормальное состояние очереди: задача принята, результат ещё не готов. Опрашивайте раз в 5 секунд и не чаще — частый опрос ничего не ускоряет. Если состояние держится дольше пары минут, проверьте, что pageurl совпадает с фактическим адресом страницы.

Можно ли обойтись без Selenium и работать только с API?

Да, если sitekey и URL известны заранее, а форма уходит обычным POST-запросом. Selenium нужен там, где параметры проверки видны только после рендера JavaScript.

Токен вставился, но страница не реагирует — что проверить?

Скорее всего, форма построена на обратном вызове: найдите callback через ___grecaptcha_cfg.clients и вызовите его с токеном вместо submit(). Второй частый случай — поле g-recaptcha-response внутри iframe: тогда нужен switch_to.frame.

Меняется ли что-то в headless-режиме?

Для решения проверки — нет: токен приходит от сервиса, браузеру достаточно отрисовать страницу. Разница проявляется в вёрстке — задавайте window-size явно.

Что почитать дальше

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