Puppeteer сам CAPTCHA не решает — он их только встречает. Скрипт достаёт sitekey из DOM, отправляет его в API CaptchaAI и получает готовый токен, который остаётся вставить в форму и отправить.
Что нужно для интеграции
- Node.js 16+ — c npm
- Puppeteer —
npm install puppeteer - Axios —
npm install axios - API-ключ CaptchaAI — выдаётся на captchaai.com, храните его в переменных окружения, а не в коде
Как распределены роли между Puppeteer и CaptchaAI
Разделение обязанностей простое: браузер отвечает за навигацию и DOM, CaptchaAI — за саму задачу, а между ними — короткий цикл отправки и опроса результата.
- Puppeteer открывает страницу с CAPTCHA.
- Скрипт достаёт sitekey CAPTCHA из DOM.
- CaptchaAI решает задачу на своей стороне — браузер в это время простаивает.
- Скрипт вставляет токен и отправляет форму.
Пока идёт решение, Puppeteer ничего не делает — это не блокирующая операция для остального пайплайна, если вы запускаете несколько вкладок параллельно и просто ждёте каждый await в своей корутине.
Командам из России, Беларуси или Казахстана тарификация по потокам удобна предсказуемостью: цена в долларах не зависит от курса и не растёт с числом решённых CAPTCHA — план BASIC стоит $15 в месяц за 5 потоков независимо от того, сколько задач через них пройдёт за месяц. Если пайплайн при этом собирает персональные данные, держите в голове 152-ФЗ или GDPR-логику для трансграничных проектов — это забота скрипта, а не CaptchaAI.
Шаг 1. Напишите модуль-солвер
Solver-модуль инкапсулирует обращение к in.php/res.php: остальному коду достаточно передать sitekey и адрес страницы, а получить в ответ готовый токен.
// solver.js
const axios = require("axios");
const API_KEY = "YOUR_API_KEY";
const POLL_INTERVAL = 5000;
const MAX_ATTEMPTS = 60;
async function solveRecaptchaV2(siteKey, pageUrl) {
// Submit task
const submitResp = await axios.get("https://ocr.captchaai.com/in.php", {
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: siteKey,
pageurl: pageUrl,
},
});
if (!submitResp.data.startsWith("OK|")) {
throw new Error(`Submit failed: ${submitResp.data}`);
}
const taskId = submitResp.data.split("|")[1];
console.log(`Task submitted: ${taskId}`);
// Poll for result
for (let i = 0; i < MAX_ATTEMPTS; i++) {
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
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;
if (result.data.startsWith("OK|")) {
return result.data.split("|")[1];
}
throw new Error(`Solve failed: ${result.data}`);
}
throw new Error("Solve timed out");
}
async function solveTurnstile(siteKey, pageUrl) {
const submitResp = await axios.get("https://ocr.captchaai.com/in.php", {
params: {
key: API_KEY,
method: "turnstile",
sitekey: siteKey,
pageurl: pageUrl,
},
});
if (!submitResp.data.startsWith("OK|")) {
throw new Error(`Submit failed: ${submitResp.data}`);
}
const taskId = submitResp.data.split("|")[1];
for (let i = 0; i < MAX_ATTEMPTS; i++) {
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
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;
if (result.data.startsWith("OK|")) return result.data.split("|")[1];
throw new Error(`Solve failed: ${result.data}`);
}
throw new Error("Solve timed out");
}
module.exports = { solveRecaptchaV2, solveTurnstile };
POLL_INTERVAL и MAX_ATTEMPTS дают запас почти в пять минут — с большим отрывом от типичных <60 с для reCAPTCHA v2 и <10 с для Turnstile, так что цикл почти всегда завершается раньше лимита.
Шаг 2. Настройте базовый профиль браузера
Дополнительная настройка ускоряет работу с сайтами, которые проверяют профиль клиента ещё до показа CAPTCHA:
const puppeteer = require("puppeteer");
async function createBrowser() {
const browser = await puppeteer.launch({
headless: "new",
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-blink-features=AutomationControlled",
],
});
const page = await browser.newPage();
await page.setUserAgent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
);
// Hide automation indicators
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, "webdriver", { get: () => false });
});
return { browser, page };
}
Для самого решения это не обязательно — CaptchaAI справится в любом случае. Но реалистичный user agent и отключённый флаг автоматизации снижают число сайтов, вообще не показывающих форму headless-клиенту.
Шаг 3. Решите reCAPTCHA прямо на странице
Здесь Puppeteer и solver.js встречаются: сначала достаём sitekey из DOM, затем передаём его в CaptchaAI и ждём токен.
const { solveRecaptchaV2 } = require("./solver");
async function scrapeWithCaptcha(url) {
const { browser, page } = await createBrowser();
try {
await page.goto(url, { waitUntil: "networkidle2" });
// Extract site key
const siteKey = await page.$eval(
".g-recaptcha",
(el) => el.getAttribute("data-sitekey")
);
console.log("Site key:", siteKey);
// Solve with CaptchaAI
const token = await solveRecaptchaV2(siteKey, url);
console.log("Token received:", token.substring(0, 50));
// Inject token
await page.evaluate((token) => {
document.getElementById("g-recaptcha-response").innerHTML = token;
document.getElementById("g-recaptcha-response").style.display = "";
}, token);
// Submit the form
await page.click('button[type="submit"]');
await page.waitForNavigation({ waitUntil: "networkidle2" });
// Scrape the content
const content = await page.content();
console.log("Page loaded successfully");
return content;
} finally {
await browser.close();
}
}
Метод page.content() в конце возвращает уже отрисованный HTML — для большинства задач парсинга этого достаточно, и скриншот не нужен.
Шаг 4. Обработайте JS-колбэк вместо submit
Часть сайтов не ждёт клика по кнопке, а слушает колбэк reCAPTCHA напрямую:
// Trigger the reCAPTCHA callback
await page.evaluate((token) => {
// Method 1: Direct callback
if (typeof ___grecaptcha_cfg !== "undefined") {
const clients = ___grecaptcha_cfg.clients;
Object.keys(clients).forEach((key) => {
const client = clients[key];
// Find the callback function
const findCallback = (obj) => {
for (const prop in obj) {
if (typeof obj[prop] === "function") {
obj[prop](token);
return true;
}
if (typeof obj[prop] === "object" && obj[prop] !== null) {
if (findCallback(obj[prop])) return true;
}
}
return false;
};
findCallback(client);
});
}
}, token);
Если страница не двигается после инъекции токена, проверьте это первым — часто форма ждёт колбэк, а не событие submit.
Полный рабочий пример
Ниже — тот же цикл submit/poll в одном самостоятельном скрипте, без разбивки на модули:
const puppeteer = require("puppeteer");
const axios = require("axios");
const API_KEY = "YOUR_API_KEY";
async function solveCaptcha(siteKey, pageUrl) {
const submit = await axios.get("https://ocr.captchaai.com/in.php", {
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: siteKey,
pageurl: pageUrl,
},
});
const taskId = submit.data.split("|")[1];
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;
if (result.data.startsWith("OK|")) return result.data.split("|")[1];
throw new Error(result.data);
}
}
(async () => {
const browser = await puppeteer.launch({
headless: "new",
args: ["--disable-blink-features=AutomationControlled"],
});
const page = await browser.newPage();
try {
await page.goto("https://staging.example.com/qa-login", {
waitUntil: "networkidle2",
});
// Get the site key
const siteKey = await page.$eval(".g-recaptcha", (el) =>
el.getAttribute("data-sitekey")
);
// Solve
const token = await solveCaptcha(siteKey, page.url());
// Inject and submit
await page.evaluate((t) => {
document.getElementById("g-recaptcha-response").innerHTML = t;
}, token);
await page.click("#submit-btn");
await page.waitForNavigation();
console.log("Done:", page.url());
} finally {
await browser.close();
}
})();
Частые сбои и как их избежать
Большинство сбоев в связке Puppeteer + CaptchaAI сводится к пяти причинам:
| Проблема | Причина | Решение |
|---|---|---|
page.$eval падает с ошибкой |
CAPTCHA подгружается уже после первого рендера | Добавьте page.waitForSelector('.g-recaptcha') перед извлечением sitekey |
| Токен не срабатывает | Истёк срок действия к моменту отправки | Вставляйте токен и отправляйте форму сразу после получения ответа |
| Сайт распознаёт headless-браузер | Профиль браузера слишком отличается от обычного | Добавьте плагины пакета puppeteer-extra для более естественного профиля |
Navigation timeout после отправки |
Страница не переходит на новый URL | Проверьте, использует ли форма AJAX-отправку вместо обычного submit |
Запрос к ocr.captchaai.com обрывается |
Сеть сервера блокирует исходящие запросы или падает по таймауту | Проверьте доступность API-хоста с той же машины, где запущен скрипт, и увеличьте таймаут axios |
Часто задаваемые вопросы
Сколько потоков CaptchaAI нужно для Puppeteer-скрапера?
Зависит от параллелизма скрипта. Пяти вкладок Puppeteer хватит на BASIC ($15/мес, 5 потоков); при десятках параллельных сессий берите STANDARD ($30/мес, 15 потоков) или выше — точные тарифы смотрите на captchaai.com/pricing.
Puppeteer совместим с Cloudflare Turnstile?
Да. Достаньте data-sitekey из блока .cf-turnstile и вызовите CaptchaAI с параметром method=turnstile — сигнатура запроса та же, что у solveTurnstile в модуле выше.
Почему токен перестаёт быть валидным сразу после получения?
Токены reCAPTCHA и Turnstile ограничены по времени на стороне целевого сайта, а не CaptchaAI. Пауза между получением ответа и вставкой в форму — логирование, лишний запрос — может стоить вам действующего токена.
Ещё пара вопросов по интеграции
- Нужно ли что-то менять в коде для нескольких CAPTCHA на одной странице? Извлеките sitekey каждого виджета отдельно и решайте их параллельно через
Promise.all()— очередь CaptchaAI обрабатывает несколько задач одновременно без дополнительной настройки. - Puppeteer и прокси в связке с CaptchaAI не конфликтуют? Нет, это независимые слои: прокси меняет сетевой маршрут запросов вашего скрипта, а CaptchaAI получает только sitekey и адрес страницы и ничего не знает о том, через какой канал Puppeteer вышел в сеть.
- Что делать, если API вернул
ERROR_ZERO_BALANCE? Пополните баланс в личном кабинете CaptchaAI — это не ошибка скрипта, а признак того, что на аккаунте закончились средства.
Похожие материалы
- Обработка CAPTCHA в Selenium на Python
- Решение CAPTCHA в Playwright
- Парсинг с CAPTCHA на Node.js