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 Chrome —
navigator.webdriver = true— флаг--disable-blink-features=AutomationControlled; - Puppeteer — не хватает штатных плагинов браузера — готовый плагин экосистемы Puppeteer, закрывающий большинство типовых сигналов автоматизации разом;
- Selenium — виден переключатель
enable-automation—excludeSwitches: ["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 вообще появляется:
- Подключите API-решение через
in.php/res.php— оно решает саму CAPTCHA и не зависит от того, что именно проверяет конкретный сайт. - Уберите сигнал
navigator.webdriverи задайте реалистичный viewport. - Для staging-стендов Cloudflare Turnstile сохраняйте
qa_validation_cookieмежду запросами одной сессии.
API-решение не соревнуется с детектором сайта — оно работает независимо от исхода этой гонки.