Ответ на BLS CAPTCHA — это не токен, а список номеров ячеек, и от этого зависит вся схема интеграции. Со страницы снимаются девять картинок сетки в base64 и числовой код инструкции, всё уходит на in.php, результат забирается с res.php, а клики по возвращённым ячейкам выполняет уже ваш браузерный код. Рабочий скрипт на Node.js и CaptchaAI, который проходит этот путь от начала до конца без собственного разбора изображений, — ниже.
Отдельный метод bls нужен именно из-за формы ответа. У reCAPTCHA или Cloudflare Turnstile решение подставляется в скрытое поле формы одной строкой; здесь такого поля нет, а приходит массив индексов, который надо ещё «проиграть» кликами по сетке.
Как устроена сетка BLS
Девять изображений нумеруются слева направо и сверху вниз:
1 | 2 | 3
---------
4 | 5 | 6
---------
7 | 8 | 9
Рядом с сеткой страница показывает числовой код инструкции — например, 664. Он и определяет, какие ячейки считаются правильными. CaptchaAI принимает девять изображений вместе с этим кодом и возвращает массив индексов совпавших ячеек. Ни координаты, ни размеры картинок передавать не нужно — важен только порядок, в котором вы их отправили.
По внутренним метрикам само распознавание сетки укладывается в <1 с. Остальное время в примере ниже — это пауза перед первым опросом и интервал между попытками.
Что понадобится
| Элемент | Значение |
|---|---|
| CaptchaAI API-ключ | из личного кабинета на captchaai.com |
| Node.js | 14+ |
| Библиотека | axios (npm install axios) |
Puppeteer здесь отвечает только за браузер: открыть страницу, вытащить картинки и кликнуть. Если у вас уже настроен Playwright, меняются вызовы автоматизации, а часть с API остаётся дословно той же.
Шаг 1. Соберите изображения сетки
Со страницы снимаются код инструкции и девять src ячеек. Часть порталов отдаёт картинки сразу в data:-URL, часть — обычными ссылками, поэтому второй случай догружается через axios и переводится в base64 вручную.
const axios = require('axios');
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/bls-form');
// Get instruction code
const instruction = await page.$eval('.bls-instruction', (el) => el.textContent.trim());
// Get all 9 cell image URLs and convert to base64
const cellImages = await page.$$eval('.bls-grid img', (imgs) =>
imgs.map((img) => img.src)
);
const images = [];
for (const src of cellImages) {
if (src.startsWith('data:')) {
images.push(src);
} else {
const { data } = await axios.get(src, { responseType: 'arraybuffer' });
const b64 = Buffer.from(data).toString('base64');
images.push(`data:image/png;base64,${b64}`);
}
}
Главное на этом шаге — не перемешать порядок. Массив images должен идти строго от первой ячейки к девятой, иначе вернувшиеся номера будут указывать не на те картинки.
Шаг 2. Отправьте задачу в CaptchaAI
Изображения уходят как image_base64_1 … image_base64_9, код инструкции — в поле instructions, метод — bls. Флаг json: '1' включает разбираемый ответ вместо текстовой строки.
const API_KEY = 'YOUR_API_KEY';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const params = new URLSearchParams({
key: API_KEY,
method: 'bls',
instructions: instruction,
json: '1',
});
// Add all 9 images
images.forEach((img, i) => {
params.append(`image_base64_${i + 1}`, img);
});
const { data: submitData } = await axios.post(
'https://ocr.captchaai.com/in.php',
params.toString()
);
if (submitData.status !== 1) throw new Error(submitData.request);
const taskId = submitData.request;
console.log(`Task submitted: ${taskId}`);
Ответ с status: 1 содержит ID задачи в поле request — его и передают дальше. API-ключ держите в переменной окружения, а не в исходнике: девять base64-картинок в теле запроса делают такие POST-запросы заметными в логах прокси и CI.
Шаг 3. Опросите результат
await sleep(5000);
let selectedCells;
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) {
selectedCells = JSON.parse(pollData.request);
console.log('Selected cells:', selectedCells);
break;
}
if (pollData.request !== 'CAPCHA_NOT_READY') {
throw new Error(pollData.request);
}
await sleep(5000);
}
Логика простая: CAPCHA_NOT_READY — задача ещё в работе, ждём дальше; любой другой текст в request — это код ошибки, и цикл нужно прерывать, а не продолжать опрос. Тридцать попыток с интервалом 5 с дают верхнюю границу около двух с половиной минут — такого запаса хватает даже на нестабильном мобильном канале.
Шаг 4. Кликните по нужным ячейкам
// Click each identified cell
const gridCells = await page.$$('.bls-grid img');
for (const cellNum of selectedCells) {
await gridCells[cellNum - 1].click();
}
// Submit the form
await page.click('.bls-submit');
console.log(`Solved: clicked cells ${JSON.stringify(selectedCells)}`);
await browser.close();
Индексы приходят с единицы, а массив элементов в браузере — с нуля, отсюда и cellNum - 1. В консоли успешный прогон выглядит так:
Selected cells: [1, 4, 7, 8]
Solved: clicked cells [1,4,7,8]
Ошибки API и что с ними делать
| Ошибка | Причина | Что делать |
|---|---|---|
ERROR_BAD_PARAMETERS |
не хватает изображений или кода инструкции | отправьте все девять картинок и поле instructions |
CAPCHA_NOT_READY |
задача ещё обрабатывается | продолжайте опрос с интервалом 5 с |
ERROR_ZERO_BALANCE |
на аккаунте нет средств | пополните баланс CaptchaAI |
Отдельно стоит логировать пару «код инструкции → возвращённые ячейки»: если форма поменяет разметку сетки, это первое место, где расхождение станет видно.
Сколько потоков заложить
Тарификация у CaptchaAI идёт по потокам, а не по количеству решений: поток — это одна задача «в полёте», и как только она завершилась, он берёт следующую. Одиночному скрипту, который обрабатывает одну форму за раз, хватает BASIC ($15/мес, 5 потоков). Команде, которая параллельно прогоняет несколько окружений QA, ближе STANDARD ($30/мес, 15 потоков).
Практический ориентир: очередь из 200 форм в час при полном цикле около 10 с загружает меньше одного потока — упирается всё обычно не в решение, а в скорость самой страницы. Фиксированная месячная сумма в USD при этом удобна тем, кто планирует расходы заранее: счёт не меняется от того, сколько сеток вы разобрали за месяц.
Сценарий: ночной регрессионный прогон формы записи
Чаще всего этот скрипт оседает в CI у команды, которая сопровождает форму записи в визовый центр: после каждого релиза надо проверить вёрстку на мобильном разрешении, валидацию полей и поведение при тайм-ауте. Сетка BLS — единственный шаг такой формы, который не автоматизируется штатными средствами Playwright или Puppeteer, и именно он ломает ночной регрессионный прогон.
Рабочая схема выглядит так:
- Поднимите стенд staging с той же разметкой (
.bls-grid,.bls-instruction), что и боевая форма. - Прогоните по нему сценарий из этого руководства и сохраните пары «код инструкции → выбранные ячейки».
- Сравните долю успешных решений между релизами — падение почти всегда означает изменение разметки, а не проблему на стороне API.
Персональные данные заявителей в такие логи не пишите: храните только технические поля. Требования 152-ФЗ «О персональных данных» распространяются и на тестовые контуры, если в них попадают реальные записи, — это вопрос вашей внутренней процедуры, а не настроек сервиса решения CAPTCHA.
Часто задаваемые вопросы
Что передавать в instructions, если код многозначный?
Передавайте код целиком, одной строкой, ровно так, как он показан на странице: 664, а не три отдельные цифры. Разбивать его не нужно.
Приходит ERROR_BAD_PARAMETERS, хотя все картинки отправлены. Почему?
Чаще всего одна из ячеек ушла пустой строкой: src подгружался лениво, и на момент чтения картинка ещё не появилась. Дождитесь всех девяти элементов и проверьте длину массива перед отправкой.
Можно ли отправить ссылки на изображения вместо base64?
Нет, метод bls работает с содержимым картинок. Ячейки внутри приватной сессии портала недоступны стороннему серверу по прямой ссылке — поэтому их и переводят в base64 на той же странице.
Что делать, если на том же портале стоит hCaptcha?
Через CaptchaAI такую проверку пройти не получится: hCaptcha не входит в список поддерживаемых типов. Доступны reCAPTCHA v2 и v3, Cloudflare Turnstile и Cloudflare Challenge, GeeTest v3, а также image/OCR, grid-image и BLS.
Как ускорить полный цикл?
Сократите интервал опроса и уберите фиксированную паузу перед первой попыткой там, где она не нужна: распознавание сетки укладывается в <1 с, а большая часть времени в примере — это ожидание между запросами к res.php.
Связанные руководства
Подключите CaptchaAI и разберите сетку BLS в своём скрипте →