Как решить GeeTest v3 в Node.js и отправить результат обратно

Почти каждая интеграция капчи предполагает, что один токен входит и один выходит. GeeTest работает не так. Решение возвращает три значения, которые нужно отправлять вместе, а challenge, с которого вы начали, истекает примерно через минуту после получения. Ни одна из этих ошибок не выдаёт внятного сообщения, поэтому GeeTest и славится своей капризностью.
Два входных значения, одно из них недолговечное
| Значение | Пожизненно |
|---|---|
gt | Статично для каждого сайта. Кэшируйте свободно |
challenge | Одноразовый, умирает примерно за 60 секунд |
Оба берутся из собственного init-эндпоинта GeeTest на сайте. Отношение к challenge как к многоразовому — это причина, по которой интеграции проходят тесты и рушатся в продакшене, где задержка в очереди сдвигает решение за пределы срока действия.
Настройка
npm install capskip
const { CapSkip } = require('capskip');
const solver = new CapSkip({
host: '127.0.0.1',
port: 8080,
recaptchaTimeout: 300, // GeeTest uses this, not defaultTimeout
});Это различие имеет значение. defaultTimeout управляет только капчами-картинками; всё интерактивное использует recaptchaTimeout.
Распознавание
const result = await solver.geetest( '81388ea1fc187e0c335c0a8907ff2625', // gt, static per site '7cf6a8b1a2c34d5e6f7089abcdef0123', // challenge, fetched seconds ago 'https://example.com/login', ); console.log(result.challenge, result.validate, result.seccode);
Эти три свойства и есть ответ. result.code тоже заполняется, но для GeeTest он содержит необработанную строку JSON, а не что-то пригодное для отправки, поэтому обращение к нему по привычке приводит к загадочному сбою.
Используйте challenge который возвращается, а не тот, что вы передали. Они не всегда идентичны.
Весь процесс
const { CapSkip } = require('capskip');
const solver = new CapSkip();
const LOGIN = 'https://example.com/login';
// 1. Fresh pair, cache-busted. Cached init responses return spent challenges.
const init = await (await fetch(
`https://example.com/geetest/init?t=${Date.now()}`,
)).json();
// 2. Solve immediately. Nothing slow between here and the previous step.
const result = await solver.geetest(init.gt, init.challenge, LOGIN);
// 3. Post all three together with the real form fields.
const response = await fetch(LOGIN, {
method: 'POST',
body: new URLSearchParams({
geetest_challenge: result.challenge,
geetest_validate: result.validate,
geetest_seccode: result.seccode,
username: '...',
password: '...',
}),
});Временная метка для обхода кэша делает реальную работу. Эндпоинты инициализации GeeTest часто кэшируются CDN или прокси, и кэшированный ответ выдаёт вам капчу, которую кто-то уже использовал.
Эти имена полей — обычное соглашение GeeTest v3, хотя сайт может их переименовать. Проверьте реальную форму, прежде чем делать предположения.
Ловушка пакетной обработки
Именно здесь эргономика Node работает против вас. Очевидная параллельная версия неверна:
// WRONG: every challenge is fetched up front, so the later solves // start against pairs that have already expired. const pairs = await Promise.all(urls.map(fetchPair)); const results = await Promise.all( pairs.map((p, i) => solver.geetest(p.gt, p.challenge, urls[i])));
Держите получение и решение внутри одной задачи, чтобы каждая пара использовалась в течение секунд после выдачи:
// RIGHT: each task fetches its own pair immediately before solving.
const results = await Promise.all(urls.map(async (url) => {
const pair = await fetchPair(url);
return solver.geetest(pair.gt, pair.challenge, url);
}));AsyncCapSkip экспортируется как псевдоним для CapSkip здесь, поэтому отдельного асинхронного клиента искать не нужно. Promise.all — это вся суть.
Ошибки
const {
ValidationException, NetworkException, ApiException, TimeoutException,
} = require('capskip');
try {
const result = await solver.geetest(gt, challenge, pageUrl);
} catch (err) {
if (err instanceof ValidationException) { /* missing gt or challenge */ }
else if (err instanceof NetworkException) { /* CapSkip not running */ }
else if (err instanceof ApiException) { /* usually an expired challenge */ }
else if (err instanceof TimeoutException) { /* exceeded recaptchaTimeout */ }
else throw err;
}Большинство сбоев GeeTest приходят в виде ApiException и означают, что challenge истёк. Повторная попытка с той же парой никогда не работает. Запросите новый.
Часто задаваемые вопросы
Почему нельзя просто использовать result.code?
Потому что ответ GeeTest — это три значения. code содержит необработанный JSON для полноты, тогда как SDK раскрывает полезные части в challenge, validate и seccode. Отправьте их.
Можно ли кэшировать значение gt?
Да. gt — это идентификатор сайта, который редко меняется. challenge — это скоропортящаяся половина, и её нужно получать заново каждый раз.
Обрабатывает ли это GeeTest v4?
The geetest метод нацелен на v3, слайдер-пазл, построенный на gt и challenge пара. v4 изменил модель параметров, поэтому сверьтесь с актуальной Документация по API прежде чем предполагать, что тот же вызов сработает.
Сводка
Получите пару с обходом кэша, решите немедленно, затем отправьте challenge, validate и seccode вместе. Используйте возвращённый challenge, кэшируйте только gt, и держите получение и решение внутри одной задачи при параллельном выполнении.
Другие языки — на Решение для GeeTest странице, более широкий набор Node на Сервис распознавания капчи для Node.js страница, и есть «живая» головоломка на нашей Демо GeeTest v3. CapSkip умеет обход капчи локально, поэтому объём решений ничего не стоит.
