Картинка с искажённым текстом решается двумя HTTP-запросами: вы отправляете изображение на in.php и забираете распознанную строку с res.php. Никакого распознавания на своей стороне, никакой обученной модели и никакого браузерного плагина — только axios и API-ключ. Ниже разобраны оба способа отправки, опрос результата, параметры, которые заметно поднимают точность, и таблица ошибок, на которых обычно застревает первая интеграция.
Такой формат CAPTCHA до сих пор массово живёт там, где интерфейсы не переписывали годами: государственные порталы, старые CRM и биллинги, формы регистрации на региональных площадках, визовые сервисы вроде BLS. Для команд из России, Беларуси и Казахстана это самый частый сценарий — не reCAPTCHA v2 на современном SPA, а старая картинка в форме, которую нужно проходить в автотестах или при легальном сборе открытых данных.
Что понадобится перед стартом
| Элемент | Значение |
|---|---|
| CaptchaAI API-ключ | Из личного кабинета на captchaai.com |
| Node.js | 14+ |
| Библиотеки | axios, fs |
| Формат изображения | JPG, PNG или GIF (100 байт – 100 КБ) |
Ключ берётся в панели управления сразу после регистрации, отдельной заявки на доступ к OCR-методу не требуется. Тарификация здесь потоковая, а не за решение: BASIC ($15/мес, 5 потоков) держит пять одновременных задач, ADVANCE ($90/мес, 50 потоков) — пятьдесят. Для парсера, который распознаёт картинки последовательно в один воркер, стартового тарифа хватает с запасом; масштабируется не число решений, а число параллельных запросов. Для агентства, которое выставляет счета в волатильной локальной валюте, фиксированный месячный платёж в USD предсказуемее поштучной оплаты.
Способ А: отправка в base64
Основной вариант, когда картинка уже в памяти — например, вы только что сняли скриншот элемента через Puppeteer и не хотите класть файл на диск.
const axios = require('axios');
const fs = require('fs');
const API_KEY = 'YOUR_API_KEY';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// Read and encode the image
const imageB64 = fs.readFileSync('captcha.png').toString('base64');
// Submit to CaptchaAI
const { data: submitData } = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: API_KEY,
method: 'base64',
body: imageB64,
json: 1,
},
});
if (submitData.status !== 1) throw new Error(submitData.request);
const taskId = submitData.request;
console.log(`Task submitted: ${taskId}`);
Ответ с status: 1 возвращает ID задачи в поле request — сохраните его, дальше опрос идёт по нему.
Способ Б: загрузка файлом через multipart
Если изображение лежит на диске или пришло потоком, form-data избавляет от лишнего кодирования и заметно уменьшает объём запроса.
const FormData = require('form-data');
const form = new FormData();
form.append('key', API_KEY);
form.append('method', 'post');
form.append('json', '1');
form.append('file', fs.createReadStream('captcha.png'));
const { data: submitData } = await axios.post('https://ocr.captchaai.com/in.php', form, {
headers: form.getHeaders(),
});
const taskId = submitData.request;
Опрос res.php: как забрать распознанный текст
Результат не приходит синхронно. Дайте первую паузу примерно в 5 с, затем опрашивайте res.php с тем же интервалом. Ответ CAPCHA_NOT_READY — это норма, а не ошибка: задача ещё в работе. Любая другая строка в request означает отказ, и цикл нужно прерывать, иначе воркер будет молча крутиться до конца лимита итераций.
await sleep(5000);
let captchaText;
for (let i = 0; i < 30; i++) {
const { data: pollData } = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: 1 },
});
if (pollData.status === 1) {
captchaText = pollData.request;
console.log(`CAPTCHA text: ${captchaText}`);
break;
}
if (pollData.request !== 'CAPCHA_NOT_READY') {
throw new Error(pollData.request);
}
await sleep(5000);
}
На нестабильном мобильном канале имеет смысл увеличить общий тайм-аут цикла, а не частоту опроса: короткий интервал не ускоряет решение, зато повышает шанс упереться в ограничение частоты запросов.
Параметры, которые повышают точность
Самая дешёвая оптимизация — заранее сообщить API, что именно изображено на картинке. Если вы знаете, что в форме всегда четыре–шесть цифр, ограничьте алфавит и длину: это снижает долю неверных прочтений на шумных изображениях.
| Параметр | Значение | Назначение |
|---|---|---|
numeric |
1 = цифры, 2 = буквы |
Ограничивает алфавит |
min_len / max_len |
Целое число | Ограничения длины |
calc |
1 |
Вычисляет математическое выражение |
regsense |
1 |
С учётом регистра |
Перед тем как выставлять эти параметры, зафиксируйте три вещи по десятку реальных картинок с целевой формы:
- какой алфавит встречается — только цифры, только буквы или смесь;
- сохраняется ли длина строки от запроса к запросу;
- важен ли регистр при проверке на стороне сайта.
Если длина плавает, min_len/max_len лучше не задавать вовсе: слишком узкие границы отбраковывают верное прочтение и задача уходит в повтор.
// Digits only, 4-6 characters
const { data } = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: API_KEY,
method: 'base64',
body: imageB64,
numeric: 1, // digits only
min_len: 4, // minimum length
max_len: 6, // maximum length
json: 1,
},
});
Комментарии в примере оставлены на английском намеренно — так проще сверяться с официальной документацией по параметрам.
Полный рабочий пример: скриншот, решение, отправка формы
Сценарий целиком: Puppeteer открывает страницу регистрации в staging-окружении, снимает элемент с картинкой, отдаёт его в CaptchaAI, дожидается текста и подставляет его в поле формы.
const axios = require('axios');
const puppeteer = require('puppeteer');
const fs = require('fs');
const API_KEY = 'YOUR_API_KEY';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function solveImageCaptcha() {
// 1. Load page and screenshot CAPTCHA
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/register');
const captchaEl = await page.$('#captcha-image');
await captchaEl.screenshot({ path: 'captcha.png' });
// 2. Encode and submit
const imageB64 = fs.readFileSync('captcha.png').toString('base64');
const { data: submit } = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: { key: API_KEY, method: 'base64', body: imageB64, json: 1 },
});
const taskId = submit.request;
// 3. Poll for text
await sleep(5000);
let text;
for (let i = 0; i < 30; i++) {
const { data: poll } = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: 1 },
});
if (poll.status === 1) { text = poll.request; break; }
if (poll.request !== 'CAPCHA_NOT_READY') throw new Error(poll.request);
await sleep(5000);
}
// 4. Type and submit
await page.type('#captcha-input', text);
await page.click('form [type="submit"]');
console.log(`Solved: ${text}`);
await browser.close();
}
solveImageCaptcha().catch(console.error);
Ожидаемый вывод в консоли:
Solved: ABC123
Если в том же пайплайне попадаются современные проверки — reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest v3, — логика меняется: там возвращается не строка, а токен, который подставляется в скрытое поле формы перед отправкой. Для картинки же результат распознавания уходит прямо в видимый input, как в примере выше. Собирая данные вместе с формами, помните про 152-ФЗ «О персональных данных»: обрабатывайте только то, что вы вправе обрабатывать, и не сохраняйте лишнего в логах воркера.
Ошибки, на которых спотыкается первая интеграция
| Ошибка | Причина | Что делать |
|---|---|---|
ERROR_WRONG_FILE_EXTENSION |
Неподдерживаемый формат | Используйте JPG, PNG или GIF |
ERROR_TOO_BIG_CAPTCHA_FILESIZE |
Изображение > 100 КБ | Сожмите перед отправкой |
ERROR_ZERO_CAPTCHA_FILESIZE |
Изображение < 100 байт | Проверьте, что скриншот не пустой |
CAPCHA_NOT_READY |
Задача ещё решается | Продолжайте опрос каждые 5 с |
Отдельно проверьте селектор элемента: пустой скриншот на 0 байт даёт ERROR_ZERO_CAPTCHA_FILESIZE чаще, чем реальные проблемы с форматом файла.
Часто задаваемые вопросы
Нужно ли ставить Puppeteer, если картинка доступна по прямой ссылке?
Нет. Скачайте файл обычным axios-запросом и отправьте его способом Б. Браузер нужен только тогда, когда изображение генерируется под конкретную сессию и без cookie не открывается.
Что делать, если текст распознан неверно?
Вызовите https://ocr.captchaai.com/res.php?key=KEY&action=reportbad&id=TASK_ID — задача помечается как неудачная. Заодно проверьте numeric, min_len и regsense: чаще всего проблема в том, что алфавит не ограничен и OCR путает 0 с O.
Сколько потоков нужно для парсера с очередью?
Считайте по числу одновременных задач, а не по объёму за сутки. Один воркер — один поток, восемь параллельных — тариф от восьми потоков и выше. Сами решения внутри тарифа отдельно не тарифицируются.
Какие ещё типы проверок закрывает тот же API-ключ?
Тем же ключом решаются reCAPTCHA v2 и v3, Cloudflare Turnstile и Cloudflare Challenge, GeeTest v3, текстовые и grid-задачи, а в beta-режиме — CaptchaFox (beta), Friendly Captcha (beta) и Lemin (beta). GeeTest v4 заявлен как «скоро». Список поддерживаемых типов стоит сверить перед тем, как закладывать его в архитектуру парсера.