Как решить hCaptcha Enterprise на Node.js (SDK capskip)

hcaptcha enterprise in node.js - How to Solve hCaptcha Enterprise in Node.js (capskip SDK)

Чтобы решить hCaptcha Enterprise на Node.js, получите на сайте новый rqdata и передайте его в метод hcaptcha() пакета capskip как параметр data вместе с sitekey и URL страницы. Метод отправляет задание в CapSkip, опрашивает его, пока токен не будет готов, и возвращает токен. Затем отправьте токен в форму с User-Agent из результата. В документации CapSkip сказано, что сайт выдаёт каждый rqdata для одной проверки и что он быстро истекает, поэтому каждому заданию и каждому повтору нужен свежий. Код ниже вызывает решатель hCaptcha в CapSkip на вашем собственном ПК с Windows, без платы за каждое решение.

Что понадобится

  • CapSkip версии 4.0.0 или выше, установленный на вашей машине с Windows. В этом выпуске появилось решение hCaptcha, включая Enterprise, в формате in.php, используемом здесь, и в формате JSON createTask.
  • Node.js 18 или новее и пакет capskip версии 1.6.0 или новее: это первая версия с методом hcaptcha. Пакет объявляет Node.js 18 нижней границей и не имеет собственных зависимостей, а отправка формы использует встроенный fetch. Я запускал код только на 20.11.1, 24.22.0 и 25.2.1.
  • Сайт, которым вы владеете или который вам разрешено автоматизировать, с виджетом hCaptcha, и sitekey этого виджета.
  • Путь от скрипта до CapSkip. Режим Local оставляет CapSkip на 127.0.0.1, доступным только с этого устройства. Режим Server заставляет его слушать ещё и вашу сеть или публичный IP, поэтому скрипт на VPS с Windows, на другом ПК или на размещённой платформе тоже может им пользоваться, но для этого CapSkip нужно запускать от имени администратора. Режим выбирается в разделе Настройки подключения.

Что проверено: я запускал код с capskip 1.6.0 на двух имитациях серверов, которые написал сам. Одна изображает CapSkip, другая изображает сайт Enterprise, который принимает каждый rqdata один раз. Эти правила мои, они взяты из документации CapSkip и исходного кода пакета. Ни на одной из публичных страниц документации hCaptcha, которые я прочитал, rqdata не описан, поэтому все факты об rqdata здесь взяты из документации CapSkip. Настоящее решение не выполнялось, поэтому я не могу сказать, сколько оно длится и как ведёт себя настоящий сайт Enterprise.

Шаг 1: найдите sitekey, признак невидимого виджета и rqdata

В пустой папке выполните npm install capskip. Затем скопируйте блоки шагов с 1 по 4 в один файл hcaptcha-demo.js в этой папке в показанном порядке. Ничего не запускается до шага 4, если только вы не добавите тестовую строку в конце шага 2. Параметр recaptchaTimeout задаёт предел опроса в SDK для hCaptcha, в секундах. По умолчанию он равен 300, а более короткие 120 я выбрал сам.

// npm install capskip
const { CapSkip, ApiException } = require("capskip");

const CAPSKIP = { host: "127.0.0.1", port: 8080 }; // Local mode; Server mode: your server
const SITE = "https://www.example.com"; // a site you may automate
const PAGE_URL = `${SITE}/login`; // the page the hCaptcha widget is on
// The SDK's default key. Use your own if API Key Validation is on in CapSkip.
const API_KEY = "capskip";
const PROXY = null; // for example "203.0.113.10:8080"; null means no proxy

const solver = new CapSkip({
  ...CAPSKIP,
  apiKey: API_KEY,
  recaptchaTimeout: 120, // poll limit in seconds for hCaptcha; the default is 300
});

Значение sitekey обычно находится в контейнере виджета как data-sitekey. Если виджет строит скрипт, ищите параметр sitekey у hcaptcha.render() или параметр sitekey= в адресе iframe hCaptcha. Про невидимый виджет подробно рассказывает руководство по невидимому виджету hCaptcha и отмечает, что у такого виджета data-size равен invisible, а в документации CapSkip сказано отправлять для него invisible=1.

Необычнее всего rqdata. В документации CapSkip сказано, что некоторые сайты Enterprise передают виджету значение rqdata через hcaptcha.setData() до его запуска и что, если страница так делает, значение нужно отправить как data. Если не делает, поле data не отправляйте. Откуда берётся значение, решает сайт. Оно может лежать в HTML страницы или приходить в ответ на запрос, который делает страница, поэтому найдите в скриптах страницы вызов, который его задаёт, и проследите строку. На настоящем сайте Enterprise я это не пробовал, а функция ниже использует путь, который предоставляет моя имитация.

// Ask the site for a new challenge. The route and the field names are the
// site's choice; these are the ones in my test mock, so adapt them.
// If the sitekey is fixed in the page, return it as a constant here.
// If the site puts the values in the page HTML, fetch PAGE_URL and
// extract them here instead.
async function getChallenge() {
  const resp = await fetch(`${SITE}/api/challenge`);
  if (!resp.ok) throw new Error(`challenge failed: HTTP ${resp.status}`);
  const { sitekey, rqdata, invisible } = await resp.json();
  return { sitekey, rqdata, invisible: Boolean(invisible) };
}

Шаг 2: отправка задачи и получение токена

Один вызов отправляет задание и опрашивает результат. Метод hcaptcha() принимает sitekey, URL страницы и объект с параметрами: в data передаётся rqdata, invisible равно true для невидимого виджета, а в proxy указывается прокси для решения. SDK отправляет их в /in.php как поля формы вместе с method=hcaptcha, а invisible отправляет как 1. Параметр userAgent он не отправляет: в документации сказано, что CapSkip использует этот параметр, только если он совпадает с браузером, в котором идёт решение, а результат содержит User-Agent, с которым получен токен.

async function solve({ sitekey, rqdata, invisible }) {
  const options = {};
  // The Enterprise rqdata, fresh for this task. Skip it if the site has none.
  if (rqdata) options.data = rqdata;
  if (invisible) options.invisible = true; // data-size is invisible
  if (PROXY) options.proxy = PROXY;
  // Submits the task, polls until the token is ready, and returns the result.
  return solver.hcaptcha(sitekey, PAGE_URL, options);
}

SDK опрашивает /res.php за вас. Первый опрос уходит сразу, а паузы после него начинаются с четверти секунды, удваиваются каждый раз и перестают расти на 5 секундах. Пустое тело считается ответом «не готово»: в документации сказано, что оно означает, что результат уже прочитан или такого ID нет, а в исходном коде SDK добавлено, что CapSkip присылает такое тело сразу после отправки задания. Когда предел проходит, вызов выбрасывает TimeoutException.

Результат выдаётся один раз, и SDK сохраняет его целиком за вас. Поля code и token содержат одну и ту же строку. Поле respKey содержит второе значение, которое hCaptcha передаёт виджету вместе с токеном. По документации большинство сайтов его не читает, а SDK ставит пустую строку, если CapSkip ничего не прислал. Из userAgent берите User-Agent, с которым получен токен.

Чтобы проверить шаги 1 и 2 отдельно, добавьте последней строкой getChallenge().then(solve).then(console.log) и выполните node hcaptcha-demo.js. Вы должны увидеть объект с captchaId, code, userAgent, respKey и token. Удалите эту строку, прежде чем добавлять следующий блок.

Шаг 3: передача токена форме

Браузерный виджет сам заполняет два скрытых поля textarea: h-captcha-response и его зеркало g-recaptcha-response. У скрипта виджета нет, поэтому он отправляет их как поля формы. Согласно документации CapSkip, это означает h-captcha-response всегда, g-recaptcha-response, если в форме есть и такое поле, и User-Agent из ответа. Форма ниже условная, потому что у вашего сайта свои поля и свой маршрут.

Токены одноразовые, и в документации CapSkip сказано, что они истекают примерно через две минуты после выдачи. В собственной документации hCaptcha для ошибки expired-input-response по умолчанию указаны 120 секунд. Поэтому не решайте заранее и не держите запас токенов. Для каждой отправки формы сначала решайте, потом отправляйте.

async function postForm(solved) {
  const form = new URLSearchParams({
    username: "demo", // the form's own fields
    "h-captcha-response": solved.token,
    // delete the next line if the form has no g-recaptcha-response field
    "g-recaptcha-response": solved.token,
  });
  // Send the User-Agent the reply names, not Node's default one.
  return fetch(PAGE_URL, {
    method: "POST",
    headers: { "User-Agent": solved.userAgent },
    body: form,
  });
}

Шаг 4: полный пример со свежим rqdata на каждую попытку

Последний блок собирает всё в цикл повторов. Добавьте его в конец hcaptcha-demo.js и запустите командой node hcaptcha-demo.js. Каждая попытка сначала вызывает getChallenge, поэтому в каждой задаче rqdata, выданный только что. Цикл повторяет только ERROR_CAPTCHA_UNSOLVABLE, код для задачи, которую не удалось решить. В документации CapSkip названы два сбоя, которые повторная отправка повторила бы: ERROR_RECAPTCHA_INVALID_SITEKEY при опросе, означающий, что hCaptcha отклонила sitekey для этой страницы, и ERROR_PROXY_CONNECT_REFUSED. Цикл выбрасывает их и любые другие ошибки снова без изменений.

Цикл также идёт на новый круг, когда сайт отвечает кодом вне диапазона от 200 до 299, потому что отклонённый токен может означать устаревший rqdata. Код 403 может иметь и другие причины, например неверный пароль в форме входа, поэтому попытки прекращаются на третьей.

// ERROR_CAPTCHA_UNSOLVABLE is the one failure where a new task can help.
async function login() {
  for (let attempt = 1; attempt <= 3; attempt += 1) {
    const challenge = await getChallenge(); // a new rqdata for every attempt
    try {
      const solved = await solve(challenge);
      const resp = await postForm(solved);
      if (resp.ok) return resp;
      console.log(`attempt ${attempt}: the site answered HTTP ${resp.status}`);
    } catch (err) {
      // The message is the text of the app's reply: a bare code or small JSON.
      const unsolvable = err instanceof ApiException
        && err.message.includes("ERROR_CAPTCHA_UNSOLVABLE");
      if (!unsolvable) throw err;
      console.log(`attempt ${attempt}: ERROR_CAPTCHA_UNSOLVABLE`);
    }
  }
  throw new Error("hCaptcha not passed in 3 attempts");
}

login()
  .then(async (resp) => console.log(resp.status, await resp.text()))
  .catch((err) => {
    console.error(`${err.name}: ${err.message}`);
    if (err.cause) console.error(err.cause.code); // for example ECONNREFUSED
    process.exitCode = 1;
  });

SDK добавляет три собственные ошибки. ValidationException означает, что он отклонил вызов до отправки чего-либо: пустой sitekey, URL страницы не в абсолютном виде с http или https, неверное значение invisible или параметр, которого не принимает API hCaptcha в CapSkip, например domain из 2Captcha. NetworkException при отправке задания означает, что по адресу CapSkip никто не ответил, а TimeoutException означает, что предел опроса прошёл. Новое задание ничего из этого не меняет, поэтому цикл выбрасывает их снова. Сообщение ApiException содержит ответ приложения в виде текста, поэтому цикл сопоставляет его через includes().

Вот что скрипт делал на имитациях, с capskip 1.6.0 на Node.js 24.22.0. Node.js 20.11.1 и 25.2.1 завершали каждый случай так же, с теми же кодами выхода. Тестовая обвязка меняла только адреса и, в строках про тайм-аут, предел опроса.

Настройка имитацииЧто сделал скрипт
Сайт Enterprise; имитация выдаёт результат на третьем опросе или через 20 секунд после отправки заданияОдно задание и статус 200 в обоих случаях. В первом опросы пришлись примерно на 0, 0.3 и 0.8 секунды. Во втором они пришлись примерно на 0, 0.3, 0.8, 1.8, 3.8, 7.8, 12.8, 17.8 и 22.8 секунды на всех трёх средах выполнения, поэтому токен дошёл до скрипта через 2.8 секунды после того, как был готов
Невидимый сайт; сайт, который не выдаёт rqdatain.php получил invisible=1 в первом случае и не получил поле data во втором, и обе отправки формы прошли
ERROR_CAPTCHA_UNSOLVABLE дважды в одном запуске, трижды в другомДва сбоя: статус 200 на третьей задаче и три разных значения rqdata. Три сбоя: Error “hCaptcha not passed in 3 attempts” и код выхода 1
Имитация сайта отклоняет первый токен“attempt 1: the site answered HTTP 403”, затем вторая задача с новым rqdata и статус 200
Кэшированная проверка повторно использована для второго входа (первый прошёл); поле data пропущено; форма отправлена без User-Agent из ответаКаждая из этих попыток получила 403 от имитации сайта, и каждый запуск закончился ошибкой Error “hCaptcha not passed in 3 attempts”. Это правила моей имитации, а не настоящего сайта
Пустой sitekey, pageurl с ftp, invisible со словом maybe, параметр domain; строка прокси, которая не является прокси, ключ, которого CapSkip не знает, тип задания отклонёнValidationException в первых четырёх случаях, при этом в in.php не отправлено ни одного задания. ApiException с ERROR_PROXY_FORMAT, ERROR_KEY_DOES_NOT_EXIST и ERROR_TASK_NOT_SUPPORTED в остальных, каждый раз после одного задания. Код выхода каждый раз 1
Опросы, отвечающие ERROR_PROXY_CONNECT_REFUSED или ERROR_RECAPTCHA_INVALID_SITEKEYТолько одна задача. Скрипт остановился на первой ошибке
Прокси 203.0.113.10:8080 или user:[email protected]:8080; имитация никогда не готова (лимит сокращён до 1,5 секунды); по адресу CapSkip нет процессаin.php получил строку прокси без изменений, с proxytype HTTP, и прогон прошёл; TimeoutException: timeout 1.5 exceeded; NetworkException: connect ECONNREFUSED, затем ECONNREFUSED на следующей строке, и ни одного задания
Имитация так и не выдаёт результат при пределе 120 секунд; процесс имитации завершается во время решения (предел сокращён до 4 секунд)Последний опрос состоялся через 118 секунд, а TimeoutException пришёл примерно через 123. После падения имитации SDK продолжал опрашивать и закончил с TimeoutException, а не с ошибкой соединения

Частые ошибки

ОшибкаПричинаИсправить
ApiException с ERROR_WRONG_USER_KEY или ERROR_KEY_DOES_NOT_EXISTВ CapSkip включена API Key Validation, а API_KEY не входит в число его ключей.Задайте в API_KEY ключ из CapSkip.
ApiException с ERROR_TASK_NOT_SUPPORTEDCapSkip отклоняет тип задания. Одна из причин: версия старше 4.0.0Обновитесь до 4.0.0 или новее
ApiException с ERROR_RECAPTCHA_INVALID_SITEKEYhCaptcha отклонил sitekey для этой страницы. Пустой sitekey досюда не доходит, потому что SDK отклоняет его раньшеСверьте sitekey и pageurl со страницей
ValidationExceptionSDK отклонил вызов до отправки чего-либо, и в его сообщении названа проблемаИсправьте вызов
ApiException с ERROR_PROXY_FORMATСтрока прокси в формате, который CapSkip не принимаетИспользуйте IP:PORT, LOGIN:PASSWORD@IP:PORT или IP:PORT:LOGIN:PASSWORD
ApiException с ERROR_PROXY_CONNECT_REFUSEDCapSkip не смог достучаться до цели через проксиПроверьте прокси отдельно, проверьте логин и тип, затем запустите скрипт снова
ApiException с ERROR_CAPTCHA_UNSOLVABLEЗадачу не удалось решитьПолучите новый rqdata и отправьте новую задачу. Цикл делает это сам
TimeoutException: timeout 120 exceededЗадача всё ещё выполнялась к моменту лимита скрипта, либо ID был неизвестен, либо его результат уже прочитан (оба случая приходят как пустое тело)Проверьте Monitor в CapSkip, затем запустите скрипт снова
NetworkException: connect ECONNREFUSEDПо адресу CAPSKIP никто не отвечает: CapSkip остановлен, либо хост или порт указаны неверно. Следующая за ней строка показывает причину: ECONNREFUSED для порта, на котором никто не слушаетЗапустите CapSkip, затем сравните адрес с настройками подключения
Сайт отклоняет форму, хотя токен решёнТокен старше, чем допускает сайт; User-Agent или адрес отличаются от тех, с которыми решалась задача; сайт оценил токен как рискованный; либо rqdata устарел или использован повторно (это проверяет моя имитация, а в документации сказано лишь, что он быстро истекает)Отправляйте сразу после решения, передавайте userAgent из ответа, получайте новый rqdata для каждой задачи и смотрите заметки о прокси ниже

Прокси и режим Server

CapSkip не навязывает прокси для hCaptcha. В документации сказано, что одного адреса хватает для проверки и для редких решений, но hCaptcha оценивает не только браузер, но и сеть, из которой пришёл токен, а постоянный поток решений с одного IP как раз и должны ловить её лимиты частоты запросов. При большом объёме настройте пул прокси в приложении: Settings, Captcha types, hCaptcha, Configure Proxies. Чтобы решение шло из определённой сети, отправьте прокси вместе с заданием, что и делает параметр PROXY. Прокси, отправленный с заданием, всегда главнее списка. CapSkip принимает прокси в виде IP:PORT, LOGIN:PASSWORD@IP:PORT или IP:PORT:LOGIN:PASSWORD. Строка прокси сама по себе уходит с proxytype HTTP. Для SOCKS5 передайте proxy как объект с type, который принимает HTTP, HTTPS, SOCKS5 или SOCKS5H, и uri. На имитации это пришло в in.php как proxytype SOCKS5. Настоящее решение через SOCKS5 я не проверял.

Эта настройка переносит только само решение. Для строгого сайта в документации также сказано отправлять форму с того же адреса, но во встроенном в Node.js fetch нет параметра прокси для отдельного запроса, поэтому отправка формы здесь уходит с ПК, на котором запущен скрипт. Отправку формы через прокси я не проверял.

Режим Server меняет лишь место, где находится решатель. Укажите в настройке CAPSKIP сервер, и остальной скрипт не меняется. Для режима Server CapSkip нужно запускать от имени администратора, а статический публичный IP даёт самый стабильный удалённый доступ. Решатель по-прежнему ваше оборудование, без платы за каждое решение. Тогда CapSkip принимает соединения с любого адреса, поэтому добавьте в Windows Firewall блокирующее правило для тех, кому доступ не нужен, включите API Key Validation в CapSkip и заполните API_KEY. Решение теперь выполняется на VPS с Windows, поэтому hCaptcha видит сеть этой машины или прокси, который вы передаёте.

FAQ

Почему hCaptcha Enterprise на Node.js каждый раз нужен свежий rqdata?

В документации CapSkip сказано, что сайт выдаёт это значение для одной проверки и что оно быстро истекает, поэтому закэшированное значение уже старое. Устаревшее значение может быть одной из причин отклонённого токена, и именно её это руководство может исключить. Как настоящий сайт реагирует на старое значение, нигде, где я мог прочитать, не задокументировано, поэтому отказ моей имитации лишь показывает, как реагирует цикл.

Что делать, если сайт всё равно отклоняет токен, который вернул CapSkip?

Посмотрите на High score mode в строке hCaptcha под Captcha types в Settings. В списке изменений CapSkip сказано, что в 4.0.0 он включён по умолчанию и решение идёт в Google Chrome. Когда режим выключен или Chrome не установлен, решение идёт в CapSkip Custom Browser, а без Chrome настройка недоступна (затенена). Документация API добавляет три правила для строгих сайтов: используйте хороший прокси, отправляйте форму с того же адреса и передавайте User-Agent, названный в ответе.

Можно ли обращаться к CapSkip из скрипта, который работает на размещённой платформе или VPS?

Да, когда CapSkip работает в режиме Server. Укажите в настройке CAPSKIP его адрес, и больше ничего в скрипте менять не нужно. Помните, что решение тогда выполняется на сервере, поэтому дайте задаче прокси, если сайт учитывает сеть. CapSkip остаётся на оборудовании, которым вы владеете.

Что бы ни выдал сайт, решение hCaptcha Enterprise на Node.js остаётся одним циклом: свежий rqdata, новая задача, токен, отправленный сразу. Когда распознавание капчи выполняется на вашей собственной машине, повтор не требует платы за каждое решение. Другие инструменты для Node.js есть на странице сервиса распознавания капчи для Node.js, а каждый параметр описан в разделе hCaptcha документации API.