Как решить 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 умеет обход капчи локально, поэтому объём решений ничего не стоит.
