Troubleshooting

Автоматизация браузера CAPTCHA не работает, но API работает: руководство по отладке

Токен, полученный от CaptchaAI, валиден — API вернул status:1 и строку токена. Но форма в браузере всё равно не отправляется, будто CAPTCHA не решена. Дело почти всегда не в решении, а в том, как токен доехал до страницы: неверный элемент, не сработавший callback, устаревший токен или отдельная антибот-проверка сайта. Ниже — шесть конкретных причин и рабочий код для reCAPTCHA v2 и Cloudflare Turnstile.


Где чаще всего теряется токен

  • Токен попал не в тот элемент. Симптом: форма отправляется без токена. Причина — неверный CSS/ID-селектор текстового поля.
  • Токен просрочен к моменту отправки. Симптом: сайт снова показывает CAPTCHA. Причина — слишком большая пауза между решением и сабмитом.
  • Callback не сработал. Симптом: кнопка отправки остаётся неактивной. Причина — не найден или не вызван callback grecaptcha.
  • В API передан не тот pageurl. Симптом: токен не подходит для этого домена. Причина — URL, который видит виджет, отличается от переданного.
  • Блокировка уже после приёма токена. Симптом: 403 или повторная CAPTCHA после успешного решения. Причина — антибот-система сайта смотрит не только на CAPTCHA.

Совет: начните проверку с callback и тайминга между решением и отправкой — на практике эти два сценария объясняют бóльшую часть обращений «токен есть, а форма не принимает».


Исправление 1: как правильно передать токен reCAPTCHA v2 в форму

Токен должен попасть в textarea g-recaptcha-response, и должен сработать callback — форма не примет просто заполненное поле без вызова обратной функции. Функция ниже делает оба шага: сначала подставляет значение во все найденные textarea на странице (их может быть несколько), затем ищет callback через внутреннюю конфигурацию ___grecaptcha_cfg и вызывает его.

from selenium import webdriver
from selenium.webdriver.common.by import By
import time


def inject_recaptcha_token(driver, token):
    """Properly inject reCAPTCHA v2 token in browser."""

    # Step 1: Make the response textarea visible and set the value
    driver.execute_script("""
        // Find all response textareas (may be multiple on page)
        var textareas = document.querySelectorAll('[name="g-recaptcha-response"]');
        textareas.forEach(function(ta) {
            ta.style.display = 'block';
            ta.value = arguments[0];
        });

        // Also set via ID if present
        var byId = document.getElementById('g-recaptcha-response');
        if (byId) {
            byId.style.display = 'block';
            byId.value = arguments[0];
        }
    """, token)

    # Step 2: Trigger the callback
    driver.execute_script("""
        // Try standard callback
        if (typeof ___grecaptcha_cfg !== 'undefined') {
            var clients = ___grecaptcha_cfg.clients;
            for (var key in clients) {
                var client = clients[key];
                // Navigate nested structure to find callback
                for (var prop in client) {
                    var val = client[prop];
                    if (val && typeof val === 'object') {
                        for (var subprop in val) {
                            var subval = val[subprop];
                            if (subval && typeof subval === 'object' && subval.callback) {
                                subval.callback(arguments[0]);
                                return;
                            }
                        }
                    }
                }
            }
        }
        // Fallback: try common callback names
        if (typeof onCaptchaSuccess === 'function') onCaptchaSuccess(arguments[0]);
        else if (typeof captchaCallback === 'function') captchaCallback(arguments[0]);
    """, token)


# Usage
driver = webdriver.Chrome()
driver.get("https://example.com/form")
# ... solve CAPTCHA via CaptchaAI ...
inject_recaptcha_token(driver, token)
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()

Функция inject_recaptcha_token пригодится и дальше — Исправление 2 разбирает, что делать, если callback не находится тем же способом.


Исправление 2: кнопка отправки остаётся неактивной

Часть форм держит атрибут disabled на кнопке, пока не выполнится callback CAPTCHA — простая подстановка значения в поле это не снимет. Функция ниже сначала пытается достать имя callback из атрибута data-callback на виджете, а если его нет — обращается напрямую к внутренней структуре ___grecaptcha_cfg.

def find_and_trigger_callback(driver, token):
    """Find the reCAPTCHA callback and trigger it."""

    # Method 1: Extract callback from data attribute
    callback_name = driver.execute_script("""
        var widget = document.querySelector('.g-recaptcha');
        if (widget) {
            return widget.getAttribute('data-callback');
        }
        return null;
    """)

    if callback_name:
        driver.execute_script(f"window['{callback_name}'](arguments[0]);", token)
        return True

    # Method 2: Extract from grecaptcha config
    triggered = driver.execute_script("""
        try {
            var widgetId = 0;
            var callback = ___grecaptcha_cfg.clients[widgetId].aa.l.callback;
            if (typeof callback === 'function') {
                callback(arguments[0]);
                return true;
            }
        } catch(e) {}
        return false;
    """, token)

    return triggered

Исправление 3: критичное окно между решением и отправкой

У токена reCAPTCHA v2 короткий срок жизни. Если между получением ответа от res.php и сабмитом формы проходит слишком много времени, сайт покажет CAPTCHA заново — токен формально ещё валиден, но сайт уже не готов его принять. Проблема усиливается, если воркер, который опрашивает CaptchaAI, и браузер разнесены по инфраструктуре: например, скрипт решения крутится на одном сервере в Европе, а Selenium — на отдельной машине или в CI-раннере в другом регионе, и каждый лишний HTTP-переход добавляет сотни миллисекунд. Держите вызов res.php и инъекцию токена в одном процессе, без синхронных сетевых операций между ними, и отправляйте форму сразу после успешного статуса.

import requests
import time


def solve_and_inject_fast(driver, api_key, sitekey, page_url):
    """Solve CAPTCHA and inject token with minimal delay."""
    # Submit solve request
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "json": 1,
    }, timeout=30)
    task_id = resp.json()["request"]

    # Poll for result
    time.sleep(15)
    for _ in range(24):
        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:
            token = data["request"]

            # IMMEDIATELY inject — don't wait
            inject_recaptcha_token(driver, token)

            # Submit form within 5 seconds
            time.sleep(0.5)
            return True

        if data["request"] != "CAPCHA_NOT_READY":
            raise RuntimeError(data["request"])
        time.sleep(5)

    raise TimeoutError("Solve timeout")

Исправление 4: какой pageurl передавать для SPA и iframe

reCAPTCHA привязывает токен к конкретному домену и пути. Если в API уйдёт не тот pageurl, который в момент решения видит виджет, — токен придёт синтаксически корректным, но сайт его отклонит. В одностраничных приложениях и при CAPTCHA внутри iframe адрес, на который вы изначально перешли, часто уже не совпадает с текущим: были редиректы, сменился hash-роут, iframe подгрузился с другого поддомена. Берите driver.current_url непосредственно перед вызовом API, а не адрес из начала сценария.

def get_correct_pageurl(driver):
    """Get the URL that reCAPTCHA actually sees."""

    # Check if CAPTCHA is in an iframe
    iframes = driver.find_elements(By.TAG_NAME, "iframe")

    for iframe in iframes:
        src = iframe.get_attribute("src") or ""
        if "recaptcha" in src or "anchor" in src:
            # CAPTCHA is in iframe — use parent page URL
            return driver.current_url

    # For SPAs, use current URL (may differ from initial load)
    return driver.current_url


# WRONG — using the URL you navigated to
pageurl = "https://example.com/form"  # May have redirected

# CORRECT — using the URL the browser is actually on
pageurl = get_correct_pageurl(driver)

Исправление 5: блокировка происходит уже после CAPTCHA

Если токен принят, а сайт всё равно возвращает 403 или снова показывает проверку — дело не в CaptchaAI: сработала отдельная антибот-система, которая смотрит на сам браузер. Типичные сигналы — флаг navigator.webdriver, нестандартный размер окна, отсутствующие расширения и заголовки, характерные для настоящего пользовательского Chrome.

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

Набор настроек ниже убирает самые заметные признаки автоматизированного запуска и стоит проверить первым, если 403 приходит уже после успешной отправки формы с валидным токеном.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service


def create_stealth_driver():
    """Create a browser that avoids basic bot detection."""
    options = Options()

    # Remove automation indicators
    options.add_argument("--disable-blink-features=AutomationControlled")
    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    options.add_experimental_option("useAutomationExtension", False)

    # Set realistic window size
    options.add_argument("--window-size=1920,1080")

    driver = webdriver.Chrome(options=options)

    # Override navigator.webdriver
    driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
    })

    return driver


driver = create_stealth_driver()

Исправление 6: как внедрить токен Cloudflare Turnstile

У Cloudflare Turnstile другая разметка страницы: значение нужно поставить в input[name="cf-turnstile-response"], инициировать события change и input, а затем — если на странице есть атрибут data-callback — вызвать соответствующую функцию из window. Часть сайтов держит запасной вариант через g-recaptcha-response, поэтому функция ниже проверяет оба имени поля.

def inject_turnstile_token(driver, token):
    """Inject Turnstile token into the page."""
    driver.execute_script("""
        // Find Turnstile response input
        var inputs = document.querySelectorAll(
            'input[name="cf-turnstile-response"], ' +
            'input[name="g-recaptcha-response"]'
        );

        inputs.forEach(function(input) {
            input.value = arguments[0];
        });

        // Trigger change event
        inputs.forEach(function(input) {
            input.dispatchEvent(new Event('change', { bubbles: true }));
            input.dispatchEvent(new Event('input', { bubbles: true }));
        });

        // Try Turnstile callback
        if (window.turnstile) {
            var widgets = document.querySelectorAll('[data-callback]');
            widgets.forEach(function(w) {
                var cb = w.getAttribute('data-callback');
                if (typeof window[cb] === 'function') {
                    window[cb](arguments[0]);
                }
            });
        }
    """, token)

Скрипт для быстрой диагностики

Перед тем как менять код, соберите факты, а не гадайте: правда ли значение попало в textarea, загрузился ли grecaptcha, найден ли callback у виджета. Функция ниже печатает всё это одним вызовом и экономит время, которое иначе уходит на догадки методом проб и ошибок.

def debug_captcha_injection(driver, token):
    """Print debug info to diagnose injection failures."""
    info = driver.execute_script("""
        var result = {};

        // Check textarea exists
        var ta = document.querySelector('[name="g-recaptcha-response"]');
        result.textarea_found = !!ta;
        result.textarea_value_set = ta ? ta.value.length > 0 : false;

        // Check reCAPTCHA loaded
        result.grecaptcha_loaded = typeof grecaptcha !== 'undefined';

        // Check for callback
        var widget = document.querySelector('.g-recaptcha');
        result.has_data_callback = widget ? !!widget.getAttribute('data-callback') : false;
        result.callback_name = widget ? widget.getAttribute('data-callback') : null;

        // Current URL
        result.current_url = window.location.href;

        // Check for errors in console (basic)
        result.has_submit_button = !!document.querySelector('button[type=submit], input[type=submit]');

        return result;
    """)

    for key, value in info.items():
        print(f"  {key}: {value}")

    return info

Шпаргалка: симптом → причина → решение

  1. Токен на месте, форма всё равно отклоняет — callback не сработал. Найдите и вызовите функцию callback вручную.
  2. Кнопка отправки остаётся неактивной — callback должен снять disabled. Вызовите callback сразу после инъекции токена.
  3. «Неверная CAPTCHA» после отправки — токен истёк. Сократите паузу между решением и сабмитом.
  4. 403 сразу после отправки формы — сработала антибот-проверка, а не CAPTCHA. Уберите признаки автоматизации в браузере.
  5. В requests работает, в браузере — нет — разный pageurl. Используйте driver.current_url как pageurl.

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

Сколько времени есть между решением CAPTCHA и отправкой токена?

Жёсткого лимита CaptchaAI не публикует, но на практике безопаснее уложиться в несколько секунд: чем короче пауза между ответом res.php и сабмитом формы, тем ниже риск, что сайт запросит проверку заново. Не вставляйте между решением и отправкой синхронные сетевые вызовы или ожидание пользовательского ввода.

Как проверить в консоли браузера, что callback действительно сработал?

Используйте debug_captcha_injection из чек-листа выше — она разом покажет, найдена ли textarea, загружен ли grecaptcha и есть ли у виджета data-callback. Если callback_name пустой, а форма всё равно активна, значит сайт слушает не стандартный callback, а собственное событие — ищите обработчик input/change на текстовом поле.

Работает ли этот подход для Playwright и Puppeteer, а не только для Selenium?

Да, последовательность действий одинаковая для любого движка автоматизации: подставить значение в нужный элемент, инициировать события и вызвать callback. Меняется только способ выполнить JS в браузере — page.evaluate в Playwright и Puppeteer вместо execute_script в Selenium, — сама логика из примеров переносится почти без изменений.

Почему сайт возвращает 403 уже после того, как форма с токеном отправлена?

Это значит, что CAPTCHA решена корректно, но сработала отдельная антибот-проверка браузера — она не связана с CaptchaAI и срабатывает уже после приёма токена. Разбирайте её отдельно от логики CAPTCHA: признаки автоматизации в navigator.webdriver, нетипичный размер окна, отсутствующие заголовки или плагины.

Стоит ли сразу переходить на headless-режим?

Нет, сначала проверьте связку в обычном (headful) режиме — так проще отличить сбой инъекции токена от отдельной блокировки headless-браузера. Часть сайтов детектирует headless-режим отдельно и отклоняет даже валидный токен; переключайтесь на headless только после того, как убедитесь, что headful-запуск проходит стабильно.


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



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

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