Use Cases

Проблемы и решения капчи headless браузера

headless-браузер получает CAPTCHA заметно чаще обычного Chrome — и дело не в невезении. Сайт складывает десяток технических сигналов и выдаёт автоматизацию ещё до первого клика скрипта. Дальше два рабочих пути: снизить число таких сигналов и решать CAPTCHA через API, когда она всё же появилась. Разберём оба.

Почему headless-режим чаще ловит проверку

Системы защиты от ботов (reCAPTCHA, Cloudflare, DataDome) не «видят» headless напрямую — они складывают косвенные признаки и снижают оценку доверия к сессии.

  • navigator.webdriver — в headless-режиме по умолчанию true;
  • размер окна — без явной настройки часто остаётся 800×600;
  • WebGL-рендерер — headless-режим возвращает программный рендерер «SwiftShader» вместо GPU;
  • Chrome DevTools Protocol — открытый CDP-порт — характерный маркер автоматизации;
  • список плагинов браузера — нет PDF-просмотрщика и других штатных плагинов;
  • Permissions API — ответы на запросы разрешений отличаются от обычного браузера;
  • User-Agent — явная подстрока «HeadlessChrome».

По отдельности почти каждый признак безобиден — обычный пользователь тоже может открыть окно нестандартного размера. Проблема начинается, когда три-четыре сигнала совпадают в одной сессии: тогда антибот-система резко снижает оценку доверия и показывает CAPTCHA вместо того, чтобы пропустить запрос молча.

Как выбрать между снижением детекции и решением через API

Это не вопрос «или-или» — в рабочем пайплайне оба подхода дополняют друг друга. Снижение сигналов детекции (Решение 2) сокращает долю сессий, где CAPTCHA вообще появляется, и экономит время с потоками. Решение через API (Решение 1) закрывает оставшиеся случаи — когда сайт всё равно показал проверку, независимо от того, насколько аккуратно настроен браузер. Если полагаться только на маскировку, любое обновление детектора со стороны сайта тихо ломает сценарий; если полагаться только на API, каждая вторая сессия тратит 5–10 секунд на решение CAPTCHA, которую можно было вовсе не увидеть. На практике разумно сначала закрыть базовые сигналы (окно, флаги WebDriver), а API держать как страховку на случай, когда признаки всё же совпали.

  • API-решение подходит, если CAPTCHA уже показана — результат не зависит от того, насколько аккуратно настроен браузер.
  • Снижение сигналов детекции подходит, если CAPTCHA всплывает слишком часто и впустую занимает потоки и время опроса.

Список патчей ниже не заменяет API-решение, а лишь сокращает частоту, с которой оно вообще понадобится.

Решение 1. Решайте CAPTCHA через API, а не гоняйтесь за детектором

Скрыть вообще все сигналы — дорогое занятие: каждое обновление Chrome даёт новый способ отличить headless-окно от обычного. Практичнее решать CAPTCHA на лету. CaptchaAI подключается на уровне HTTP-запроса и не зависит от режима браузера: сценарий находит sitekey на странице, отправляет его вместе с адресом страницы в in.php, опрашивает res.php до готовности решения и подставляет полученный токен в форму — дальше сценарий продолжает как обычно, без ручного вмешательства.

В примерах ниже используется тестовый адрес staging.example.com — замените его на реальный URL страницы вашего сценария.

Selenium (Python)

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import requests
import time

API_KEY = "YOUR_API_KEY"

options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-blink-features=AutomationControlled")
driver = webdriver.Chrome(options=options)

driver.get("https://staging.example.com/qa-login")

# Check for CAPTCHA
recaptcha = driver.find_elements("class name", "g-recaptcha")
if recaptcha:
    site_key = recaptcha[0].get_attribute("data-sitekey")

    # Solve via CaptchaAI
    resp = requests.get("https://ocr.captchaai.com/in.php", params={
        "key": API_KEY, "method": "userrecaptcha",
        "googlekey": site_key, "pageurl": driver.current_url
    })
    task_id = resp.text.split("|")[1]

    while True:
        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
        token = result.text.split("|")[1]
        break

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

Puppeteer (Node.js)

const puppeteer = require("puppeteer");
const axios = require("axios");

const API_KEY = "YOUR_API_KEY";

const browser = await puppeteer.launch({ headless: "new" });
const page = await browser.newPage();
await page.goto("https://staging.example.com/qa-login");

// Check for CAPTCHA
const siteKey = await page
  .$eval(".g-recaptcha", (el) => el.getAttribute("data-sitekey"))
  .catch(() => null);

if (siteKey) {
  const submit = await axios.get("https://ocr.captchaai.com/in.php", {
    params: {
      key: API_KEY,
      method: "userrecaptcha",
      googlekey: siteKey,
      pageurl: page.url(),
    },
  });
  const taskId = submit.data.split("|")[1];

  let token;
  while (true) {
    await new Promise((r) => setTimeout(r, 5000));
    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId },
    });
    if (result.data === "CAPCHA_NOT_READY") continue;
    token = result.data.split("|")[1];
    break;
  }

  await page.evaluate((t) => {
    document.getElementById("g-recaptcha-response").innerHTML = t;
  }, token);
  await page.click('button[type="submit"]');
}

Решение 2. Снижайте частоту появления CAPTCHA

Решать каждую CAPTCHA через API можно всегда, но если она всплывает через раз — это лишние секунды и занятые потоки. Три настройки убирают грубые сигналы автоматизации.

Цель патчей ниже — снять самые грубые признаки автоматизации, а не спрятать её целиком; специфичный детектор конкретного сайта может искать что-то ещё.

Патч сигнала WebDriver

# Selenium
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
// Puppeteer
await page.evaluateOnNewDocument(() => {
  Object.defineProperty(navigator, "webdriver", { get: () => false });
});

Реалистичный размер окна

800×600 по умолчанию — сам по себе повод для подозрения: реальные пользователи так браузер не открывают.

# Selenium
driver.set_window_size(1920, 1080)
// Puppeteer
await page.setViewport({ width: 1920, height: 1080 });

Готовые скрытые плагины

Не обязательно патчить каждый сигнал вручную — под Puppeteer и Selenium есть готовые библиотеки, закрывающие большинство типовых признаков сразу.

Если сценарий уже стабильно работает без таких библиотек, добавлять их про запас не обязательно — лишний слой усложняет отладку.

# Puppeteer
npm install puppeteer-extra puppeteer-extra-plugin-stealth
const puppeteer = require("puppeteer-extra");
const StealthPlugin = require("puppeteer-extra-plugin-stealth");
puppeteer.use(StealthPlugin());
# Selenium
pip install undetected-chromedriver
import undetected_chromedriver as uc
driver = uc.Chrome(headless=True)

Решение 3. Cloudflare Turnstile в staging: одного токена мало

При тестировании формы входа за Cloudflare Turnstile в staging решённого токена часто мало для повторных запросов — нужен ещё сессионный cookie, который сервер выдаёт вместе с ним:

# CaptchaAI handles full browser validation checks in QA
resp = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": API_KEY,
    "method": "turnstile",
    "pageurl": "https://example.com",
    "proxy": "http://user:pass@proxy:port",
    "proxytype": "HTTP"
})
task_id = resp.text.split("|")[1]

# Result includes qa_validation_cookie cookie and user_agent
# Use both to make subsequent requests

После получения токена сохраните две вещи и переиспользуйте их в следующих запросах той же сессии — иначе сервер решит, что пришёл уже другой клиент:

  • qa_validation_cookie — сессионный cookie, который подтверждает пройденную проверку;
  • user_agent — строка, с которой был получен токен; сервер сверяет её при повторном запросе.

Если стенд стоит в другом регионе, а команда работает из СНГ, заложите запас по таймауту опроса: более длинный RTT увеличивает долю CAPCHA_NOT_READY, и это лечится retry-логикой, а не переделкой запроса.

При автоматизированном тестировании такой формы собирайте только те данные, которые вы вправе обрабатывать: для проектов с российской юрисдикцией это 152-ФЗ «О персональных данных», для трансграничных команд — общие принципы GDPR. CaptchaAI решает саму проверку CAPTCHA; вопросы хранения cookie, логов и токенов между запусками остаются на стороне вашего QA-пайплайна.

Частые проблемы по браузерам и инструментам

  • headless Chromenavigator.webdriver = true — флаг --disable-blink-features=AutomationControlled;
  • Puppeteer — не хватает штатных плагинов браузера — готовый плагин экосистемы Puppeteer, закрывающий большинство типовых сигналов автоматизации разом;
  • Selenium — виден переключатель enable-automationexcludeSwitches: ["enable-automation"];
  • Playwright — сигналы WebKit выдают автоматизацию — канал Chromium с патчами под типовые признаки;
  • все инструменты — одинаковый viewport на всех сессиях — 1920×1080 или рандомизация в разумных пределах.

Вопросы про headless-режим и CAPTCHA

Нужен ли отдельный API-ключ для headless и headed режимов?

Нет. Ключ CaptchaAI не привязан к режиму браузера — решение приходит с сервера независимо от того, --headless=new у вас или обычное окно с дисплеем.

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

Зависит от объёма: CaptchaAI тарифицирует по числу одновременных потоков, а не по задачам. BASIC ($15/мес, 5 потоков) хватает небольшому скрипту, для прогона на сотнях страниц обычно берут ADVANCE ($90/мес, 50 потоков).

Playwright тоже подходит, или только Selenium и Puppeteer?

Подходит. Playwright ловит те же сигналы детекции через движок WebKit, а запрос к CaptchaAI строится одинаково независимо от драйвера.

Можно ли полностью избавиться от CAPTCHA в автоматизации?

Не полностью. Даже аккуратно настроенный headless-браузер рано или поздно получит проверку при большом объёме запросов. CaptchaAI закрывает это отдельным уровнем обработки: сценарий не падает на форме входа, а ждёт токен и продолжает работу.

Что делать, если CAPTCHA не исчезает даже после всех патчей?

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

Итог: с чего начать

Если нужно закрыть CAPTCHA уже сегодня, начните с API-решения — оно не требует переписывать сценарий детекции. Дальше добавьте патчи из Решения 2, чтобы сократить долю сессий, где CAPTCHA вообще появляется:

  1. Подключите API-решение через in.php/res.php — оно решает саму CAPTCHA и не зависит от того, что именно проверяет конкретный сайт.
  2. Уберите сигнал navigator.webdriver и задайте реалистичный viewport.
  3. Для staging-стендов Cloudflare Turnstile сохраняйте qa_validation_cookie между запросами одной сессии.

API-решение не соревнуется с детектором сайта — оно работает независимо от исхода этой гонки.

Связанные руководства

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