Headless-браузер в контейнере парсинга — это лишние 200–500 МБ ОЗУ и секунды на запуск Chromium на каждый экземпляр. Для решения CAPTCHA это почти всегда избыточно: reCAPTCHA, Cloudflare Turnstile и графические CAPTCHA прекрасно решаются обычными HTTP-запросами, без Puppeteer и без Playwright — браузер в этой цепочке вообще не запускается. Axios и API CaptchaAI закрывают задачу в несколько строк кода: сервис принимает параметры CAPTCHA, решает её на своей стороне и возвращает готовый токен, который остаётся подставить в форму или заголовок запроса.
В этом руководстве — рабочий клиент CaptchaAI на Axios, примеры для reCAPTCHA v2, Cloudflare Turnstile и графических CAPTCHA, сценарий парсинга целой страницы и параллельная обработка пакета URL с оглядкой на тарифы. Подход годится не только для классического парсинга: он же используется в интеграционных тестах, в CI-пайплайнах и в serverless-обработчиках, где поднимать Chromium дорого или вовсе невозможно.
Что потребуется для интеграции
Минимальный стек — Node.js, Axios и API-ключ CaptchaAI. Версия Node значения почти не имеет: подойдёт любой LTS-релиз, где есть fetch или установлен Axios:
| Требование | Подробности |
|---|---|
| Node.js | 16+ |
| Axios | 1.x |
| API-ключ CaptchaAI | Оформите на captchaai.com |
npm install axios
Клиент CaptchaAI на Axios
Ниже — компактная обёртка вокруг двух эндпоинтов CaptchaAI: in.php принимает задачу и возвращает её ID, res.php отдаёт результат по этому ID после того, как CAPTCHA решена. Метод poll() опрашивает res.php каждые 5 секунд до готовности результата или до тайм-аута, а getBalance() пригодится, чтобы держать под контролем баланс потоков перед большим пакетным запуском:
const axios = require("axios");
class CaptchaAI {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = "https://ocr.captchaai.com";
}
async submit(params) {
params.key = this.apiKey;
const resp = await axios.get(`${this.baseUrl}/in.php`, { params });
const text = resp.data;
if (!String(text).startsWith("OK|")) {
throw new Error(`Submit failed: ${text}`);
}
return String(text).split("|")[1];
}
async poll(taskId, timeoutMs = 300000) {
const deadline = Date.now() + timeoutMs;
const params = { key: this.apiKey, action: "get", id: taskId };
while (Date.now() < deadline) {
await new Promise((r) => setTimeout(r, 5000));
const resp = await axios.get(`${this.baseUrl}/res.php`, { params });
const text = String(resp.data);
if (text === "CAPCHA_NOT_READY") continue;
if (text.startsWith("OK|")) return text.split("|").slice(1).join("|");
throw new Error(`Solve failed: ${text}`);
}
throw new Error(`Timeout after ${timeoutMs}ms for task ${taskId}`);
}
async solve(params, timeoutMs = 300000) {
const taskId = await this.submit(params);
return this.poll(taskId, timeoutMs);
}
async getBalance() {
const resp = await axios.get(`${this.baseUrl}/res.php`, {
params: { key: this.apiKey, action: "getbalance" },
});
return parseFloat(resp.data);
}
}
module.exports = CaptchaAI;
Решение reCAPTCHA v2 без браузера
Классический сценарий — форма логина, защищённая reCAPTCHA v2. Клиент решает CAPTCHA, а токен g-recaptcha-response уходит вместе с остальными полями формы одним POST-запросом; браузер не участвует ни на одном шаге:
const CaptchaAI = require("./captchaai");
async function main() {
const solver = new CaptchaAI(process.env.CAPTCHAAI_API_KEY);
// Solve the CAPTCHA without opening any browser
const token = await solver.solve({
method: "userrecaptcha",
googlekey: "6Le-wvkS...",
pageurl: "https://staging.example.com/qa-login",
});
// Submit form with the token using Axios
const resp = await axios.post("https://staging.example.com/qa-login", {
username: "user",
password: "pass",
"g-recaptcha-response": token,
});
console.log(`Login response: ${resp.status}`);
}
main().catch(console.error);
Решение Cloudflare Turnstile без браузера
Turnstile решается тем же клиентом CaptchaAI — меняются только method и параметры запроса, остальная логика (submit → poll → токен) остаётся без изменений. Полученный cf-turnstile-response подставляется туда же, куда его ожидает целевой сайт — обычно в тело POST-запроса или в скрытое поле формы:
const token = await solver.solve({
method: "turnstile",
sitekey: "0x4AAAAA...",
pageurl: "https://example.com",
});
// Submit with Turnstile token
const resp = await axios.post("https://example.com/api/verify", {
"cf-turnstile-response": token,
data: "payload",
});
Решение графических CAPTCHA
Для классической текстовой/графической CAPTCHA браузер вообще не нужен ни на одном этапе, включая исходный этап получения самой картинки: изображение читается с диска (или приходит в ответе целевого сайта), кодируется в base64 и передаётся методом base64. Распознанный текст возвращается обычной строкой, без дополнительного парсинга ответа:
const fs = require("fs");
const imageBuffer = fs.readFileSync("captcha.png");
const imageB64 = imageBuffer.toString("base64");
const text = await solver.solve({
method: "base64",
body: imageB64,
});
console.log(`CAPTCHA text: ${text}`);
// Submit form with solved text
const resp = await axios.post("https://example.com/verify", {
captcha: text,
other_data: "value",
});
Парсинг страницы с CAPTCHA целиком
Более реалистичный сценарий объединяет все шаги в один скрипт: загрузить страницу, проверить, есть ли на ней reCAPTCHA, при необходимости решить её через CaptchaAI и отправить найденную форму со всеми её полями плюс токеном. Если CAPTCHA на странице нет, скрипт просто возвращает содержимое страницы — лишний вызов CaptchaAI не тратится. При сборе данных с чужих сайтов стоит помнить про 152-ФЗ «О персональных данных» (для аудитории РФ) и общие принципы GDPR-совместимой обработки для остальных регионов: собирайте только те данные, которые вы вправе обрабатывать, и не превращайте технический пример в производственный конвейер без юридической проверки:
const CaptchaAI = require("./captchaai");
const axios = require("axios");
const cheerio = require("cheerio");
async function scrapeProtectedPage(url) {
const solver = new CaptchaAI(process.env.CAPTCHAAI_API_KEY);
// Step 1: Fetch the page
const page = await axios.get(url);
const $ = cheerio.load(page.data);
// Step 2: Extract the reCAPTCHA site key
const siteKey = $(".g-recaptcha").attr("data-sitekey");
if (!siteKey) {
console.log("No CAPTCHA found, returning page content");
return page.data;
}
// Step 3: Solve the CAPTCHA
console.log(`Solving CAPTCHA for ${url}...`);
const token = await solver.solve({
method: "userrecaptcha",
googlekey: siteKey,
pageurl: url,
});
// Step 4: Submit form with token
const formAction = $("form").attr("action") || url;
const formData = {};
$("form input").each((_, el) => {
const name = $(el).attr("name");
const value = $(el).attr("value") || "";
if (name) formData[name] = value;
});
formData["g-recaptcha-response"] = token;
const result = await axios.post(formAction, new URLSearchParams(formData), {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
return result.data;
}
scrapeProtectedPage("https://example.com/data")
.then((data) => console.log("Success:", typeof data))
.catch(console.error);
Параллельная обработка нескольких задач
Promise.all() вместе с map() решает десятки CAPTCHA параллельно — узкое место здесь не код, а количество потоков, доступных по тарифу CaptchaAI. Каждая CAPTCHA, решаемая в моменте, занимает один поток; как только решение готово, поток освобождается для следующей задачи. Для быстрого теста на 10–20 URL хватает тарифа BASIC ($15/мес, 5 потоков). Агентствам и фрилансерам, которые выставляют счета в USD, обычно удобнее сразу взять STANDARD ($30/мес, 15 потоков) или ADVANCE ($90/мес, 50 потоков): команды из СНГ и Восточной Европы часто выбирают такую тарификацию именно из-за предсказуемой стоимости в валюте счёта, без привязки к числу решённых CAPTCHA и без сюрпризов в конце месяца.
async function solveBatch(urls, siteKey) {
const solver = new CaptchaAI(process.env.CAPTCHAAI_API_KEY);
const promises = urls.map(async (url) => {
try {
const token = await solver.solve({
method: "userrecaptcha",
googlekey: siteKey,
pageurl: url,
});
return { url, token, error: null };
} catch (error) {
return { url, token: null, error: error.message };
}
});
const results = await Promise.all(promises);
const solved = results.filter((r) => r.token);
console.log(`Solved ${solved.length}/${urls.length}`);
return results;
}
Типичные ошибки и их устранение
На практике почти все сбои в связке Axios + CaptchaAI сводятся к четырём причинам — сетевой проблеме, неверному ключу, нулевому балансу или истёкшему токену:
| Ошибка | Причина | Что делать |
|---|---|---|
AxiosError: getaddrinfo ENOTFOUND |
Не резолвится DNS | Проверьте сетевое подключение и доступность ocr.captchaai.com |
Submit failed: ERROR_WRONG_USER_KEY |
Неверный или устаревший API-ключ | Сверьте ключ в панели управления CaptchaAI |
Submit failed: ERROR_ZERO_BALANCE |
На счёте закончился баланс | Пополните баланс аккаунта из личного кабинета |
| Токен отклонён целевым сайтом | Токен уже истёк к моменту отправки | Отправляйте токен сразу после solve(), в течение 60 секунд |
Если ошибки повторяются сериями, а не единично, добавьте в submit() и poll() экспоненциальную задержку между повторами: сетевые сбои и кратковременные перегрузки эндпоинта чаще решаются повтором через 1–2 секунды, чем немедленным падением всего пакета.
Часто задаваемые вопросы
Сколько ОЗУ экономит отказ от headless-браузера?
Один экземпляр Chromium в headless-режиме съедает 200–500 МБ ОЗУ. Клиент CaptchaAI поверх Axios укладывается примерно в 5 МБ — при парсинге в 40 потоков разница уже критична для памяти контейнера и напрямую влияет на то, сколько параллельных воркеров поместится на одной машине.
Сколько потоков CaptchaAI нужно для параллельного парсинга?
Каждый одновременно решаемый запрос занимает один поток из вашего тарифа. Для пакета из 20–30 URL обычно достаточно STANDARD (15 потоков); если очередь регулярно превышает 50 задач одновременно, разумнее сразу перейти на ADVANCE (50 потоков) — простаивающие между задачами потоки отдельно не тарифицируются.
Что делать, если целевой сайт всё равно отклоняет токен?
Чаще всего дело в задержке между получением токена и отправкой формы: токен reCAPTCHA и Turnstile живёт около 60 секунд, а не минуты и не час. Отправляйте его сразу после solve(), не кэшируйте для повторного использования и не решайте токен заранее «про запас».
Работает ли этот подход в Docker и serverless-функциях?
Да, это и есть основной сценарий, для которого он задуман: без Chromium образ контейнера получается в разы легче, а холодный старт AWS Lambda или Google Cloud Functions не тратит секунды на запуск браузера — только один HTTP-запрос к CaptchaAI.
Можно ли использовать fetch вместо Axios?
Можно. Node.js 18+ включает встроенный fetch, а параметры API CaptchaAI при этом не меняются — меняется только синтаксис самого запроса и обработка ответа.