Как решить DataDome CAPTCHA на Node.js с fetch

datadome captcha in node.js - How to Pass DataDome CAPTCHA in Node.js With fetch

Чтобы решить DataDome CAPTCHA на Node.js, вы читаете страницу блокировки с кодом 403, собираете по ней адрес проверки и отправляете этот адрес в решатель CapSkip для DataDome через dataDome() и повторяете запрос с возвращённым cookie. Ответом служит cookie, а не токен для формы, и в документации CapSkip сказано, что оно обычно работает только с тем User-Agent и IP-адресом, с которыми было получено. В этом руководстве весь цикл собран с CapSkip Node.js SDK и fetch примерно в 100 строк. CapSkip работает на вашем собственном ПК с Windows без платы за каждое решение, поэтому повтор ничего не стоит.

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

  • CapSkip 3.2.0 или новее, запущенный на ПК с Windows. В этом выпуске появилось решение DataDome для ползунка и проверки устройства. Если вы запускаете CapSkip впервые, руководство по настройке проведёт вас через первый запуск.
  • Node.js и пакет capskip версии 1.5.0 или новее. В более ранних версиях нет метода dataDome. Пакет объявляет поддержку Node.js 18 или новее. Я запускал код только на Node.js 24.22.0 с capskip 1.5.0 и undici 8.12.3, поэтому Node.js 18 здесь не проверялся. Пакет undici 8 объявляет Node.js 22.19 или новее. По объявленным в engines требованиям undici 7 нужен Node.js 20.18.1 или новее, а undici 6 нужен Node.js 18.17 или новее, так что для более старого Node.js нужен более старый undici. Ни один из них я не запускал.
  • Командная строка, в которой работают node и npm.
  • Страница, которой вы владеете или которую вам разрешено автоматизировать, за защитой DataDome.
  • Адрес решателя. В режиме Local CapSkip отвечает на 127.0.0.1 и только для этого устройства. В режиме Server он слушает ваш сетевой адрес или публичный IP, чтобы скрипт на другом ПК, VPS или размещённой платформе мог обращаться к нему через API. Оба режима задаются в разделе Настройки подключения.
  • Sticky-прокси, то есть прокси с постоянным IP-адресом, на каждую задачу, когда вы перейдёте от тестов к работе. Cookie привязано к IP-адресу, с которого оно получено, и это объясняет шаг 3.

Установите два пакета командой npm install capskip undici. В Node.js есть встроенный fetch, но у него нет параметра прокси для отдельного запроса. Пакет undici добавляет его через ProxyAgent, поэтому код импортирует fetch тоже из undici. Передача ProxyAgent из undici 8.12.3 во встроенный в Node.js 24.22.0 fetch на моей установке завершилась ошибкой fetch failed, в поле cause которой написано invalid onRequestStart method (код ошибки UND_ERR_INVALID_ARG), потому что в этот Node.js встроен undici 7.29.1. Если взять оба из одного пакета, несовпадения не будет.

Как это проверялось: код запускался на трёх имитациях серверов, которые я написал, а не на настоящем сайте с DataDome. Первая копирует проверки, перечисленные в разделе DataDome на странице CapSkip Документация API, вторая изображает защищённый сайт, который отдаёт страницу блокировки с кодом 403 и принимает только cookie, выданное CapSkip, вместе с тем User-Agent, для которого оно выдано, а третья изображает прямой прокси-сервер. SDK был настоящим, capskip 1.5.0. Приложение CapSkip работало на моём ПК, но все скрипты были направлены на имитации, поэтому приложение ничего не решало. Здесь нет ничего, что показывало бы, сколько длится настоящее решение или что принимает живой сайт с DataDome. Разбор страницы блокировки проверялся только на страницах, которые копируют формат из документации CapSkip. Путь через прокси проверялся с http-прокси, который пересылает обычные запросы на http-сайт. Туннель, который нужен https-сайту при работе через прокси, не проверялся, как и https-прокси. Правила для cookie взяты из документации CapSkip. Собственная документация DataDome по cookie сообщает, что cookie datadome действует один год, и предупреждает не менять его Domain и Path. К чему привязано cookie, в ней не сказано.

Шаг 1: чтение страницы блокировки и сборка captcha_url

Поместите этот блок и три следующих в один файл с именем datadome-demo.js в показанном порядке. Ничего не запускается до шага 4.

Когда DataDome сомневается в запросе, сайт отвечает статусом 403, иногда 401, и небольшой HTML-страницей вместо содержимого. На этой странице есть объект скрипта с именем dd, и она загружает проверку в iframe с geo.captcha-delivery.com. Именно адрес iframe нужен CapSkip, и в документации он называется captchaUrl. Если iframe уже есть на странице, можно взять его src как есть. Большинство страниц блокировки строят iframe скриптом, поэтому функция ниже собирает такой же адрес из объекта dd.

Значение rt выбирает путь. Буква c означает ползунок под /captcha/, а буква i означает проверку устройства под /interstitial/. Важно и значение t: значение fe обозначает проверку, а значение bv означает, что DataDome заблокировал IP-адрес, с которого была загружена страница. Первый запрос уходит с обычным User-Agent от Chrome. Эта строка в CapSkip не передаётся, поэтому версия Chrome в ней не обязана ни с чем совпадать. Я не проверял, отдаёт ли живой сайт с DataDome ту же проверку при другом User-Agent, так что считайте эту часть непроверенной.

Функция get читает тело ответа целиком, прежде чем вернуть результат, поэтому ни одно соединение не остаётся висеть в ожидании. Передайте ей адрес прокси, и она отправит запрос через ProxyAgent из undici, который принимает http- или https-прокси. SOCKS-прокси я не пробовал.

// npm install capskip undici
const { fetch, ProxyAgent } = require("undici");

const PAGE_URL = "https://www.example.com/"; // a page behind DataDome
const BROWSER = { // for the first request only; the cookie comes with its own User-Agent
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
    + "(KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36",
};

const agents = new Map(); // one ProxyAgent per proxy, so sockets are reused

async function get(headers, proxyUrl) {
  const options = { headers };
  if (proxyUrl) {
    if (!agents.has(proxyUrl)) agents.set(proxyUrl, new ProxyAgent(proxyUrl));
    options.dispatcher = agents.get(proxyUrl);
  }
  const resp = await fetch(PAGE_URL, options);
  return { status: resp.status, text: await resp.text(), setCookies: resp.headers.getSetCookie() };
}

// Build the challenge address from a DataDome block page, or null.
function captchaUrlFrom(page) {
  if (![401, 403].includes(page.status)) return null;
  const found = page.text.match(/var\s+dd\s*=\s*(\{[^}]*\})/);
  if (!found) return null;
  const dd = JSON.parse(found[1].replace(/'/g, "\""));
  const path = dd.rt === "i" ? "interstitial" : "captcha";
  const query = new URLSearchParams({ initialCid: dd.cid, hash: dd.hsh, cid: dd.cookie });
  if (dd.t) query.set("t", dd.t); // fe is a challenge, bv means the address is banned
  query.set("referer", PAGE_URL);
  query.set("s", String(dd.s));
  for (const key of ["e", "b"]) {
    if (dd[key]) query.set(key, String(dd[key]));
  }
  query.set("dm", "cd");
  return `https://${dd.host || "geo.captcha-delivery.com"}/${path}/?${query}`;
}

Функция возвращает null для любого ответа, который не является страницей блокировки, чтобы скрипт мог отличить “нечего решать” от “решено”. Страница, не защищённая DataDome, например адрес-заглушка в коде, просто выводит 200. Каждая страница блокировки несёт новую проверку. Используйте её captchaUrl сразу, а для следующей задачи запросите страницу заново, вместо того чтобы брать старый адрес.

Шаг 2: решение через dataDome()

Метод SDK называется dataDome(captchaUrl, url, options) и следует порядку аргументов 2Captcha. Первые два обязательны. В CapSkip userAgent и proxy необязательны и передаются в объекте options. Если User-Agent не передать, CapSkip использует User-Agent своего браузера и вернёт его в результате как userAgent. Если передать, это должен быть Chrome для Windows с той же основной версией, что и у браузера CapSkip, поэтому проще всего его не указывать. Метод ждёт ответа дольше, с тайм-аутом 300 секунд, как и для reCAPTCHA, потому что CapSkip проходит проверку в настоящем браузере. В документации API сказано, что решение обычно занимает от 3 до 20 секунд, а я это время не замерял.

Прокси здесь тот же, которым будут пользоваться ваши следующие запросы. Передайте его, если скрипт выходит через прокси, и ничего не передавайте, если запросы уходят с машины, на которой работает CapSkip. Если CapSkip стоит на сервере, этой машиной будет сервер. Параметр со значением undefined не отправляется, поэтому для случая без прокси proxyOption может вернуть undefined.

const { CapSkip } = require("capskip");

// Local mode. In Server mode, use your server's address instead.
const solver = new CapSkip({ host: "127.0.0.1", port: 8080 });

function proxyOption(proxyUrl) {
  if (!proxyUrl) return undefined; // your requests leave from the PC that runs CapSkip
  const [scheme, rest] = proxyUrl.split("://");
  return { type: scheme.toUpperCase(), uri: rest }; // e.g. HTTP, user:pass@ip:port
}

function solve(captchaUrl, proxyUrl) {
  // No userAgent option: CapSkip uses its own browser's and reports it.
  return solver.dataDome(captchaUrl, PAGE_URL, { proxy: proxyOption(proxyUrl) });
}

Если проверку устройства пройти нельзя, DataDome переходит к ползунку. Согласно документации CapSkip, тот же вызов следует за этим переходом, так что отдельный код для двух проверок не нужен.

Шаг 3: отправка cookie с его User-Agent и IP-адресом

Результат представляет собой объект с четырьмя важными полями. Поле code содержит cookie точно в том виде, в каком его установил DataDome, с Max-Age, Domain и другими атрибутами, а именно это нужно хранилищу cookie. Поле cookie содержит только пару имени и значения datadome, а именно это нужно заголовку Cookie. Поле userAgent содержит User-Agent, с которым cookie было получено. Поле proxy появляется только тогда, когда вы не передавали прокси и CapSkip решал через собственный список прокси для DataDome. В документации CapSkip сказано, что DataDome обычно отвергает cookie с другого IP-адреса или с другим User-Agent, поэтому отправляйте один и тот же User-Agent каждый раз, а если в результате указан прокси, отправляйте запрос через него. Поле proxy содержит только хост и порт, без схемы и без логина. Функция добавляет http://, поэтому предполагает прокси из списка без логина. Если вашему прокси из списка нужен логин, впишите его в адрес сами, как в http://user:pass@, а после него укажите хост и порт.

DataDome может также прислать сменное cookie в одном из следующих ответов. Функция читает строки Set-Cookie и сохраняет самое новое, как это делал бы браузер.

async function sendWithCookie(result, proxyUrl) {
  // The cookie works only from the address and User-Agent that earned it.
  let via = proxyUrl;
  if (!via && result.proxy) {
    via = `http://${result.proxy}`; // CapSkip solved through its own list
  }
  const headers = { "User-Agent": result.userAgent, Cookie: result.cookie };
  const page = await get(headers, via);
  for (const line of page.setCookies) { // DataDome may rotate the cookie
    const fresh = line.match(/^datadome=([^;]+)/);
    if (fresh) headers.Cookie = `datadome=${fresh[1]}`;
  }
  return { page, headers, via };
}

Cookie задаётся в заголовке вручную, поэтому ничто не управляет им за вас. Так сделано намеренно: cookie, User-Agent и прокси остаются в одном месте. Текст после схемы уходит в CapSkip в том виде, как вы его написали, поэтому не используйте в логине прокси символы, которые требуют процентного кодирования.

Шаг 4: полный рабочий пример

Последний блок собирает шаги в один процесс и добавляет повтор. Сохраните его в конце того же файла datadome-demo.js и запустите командой node datadome-demo.js. Каждая попытка заново запрашивает страницу, поэтому в каждой попытке новая проверка. Неудачное решение вызывает ApiException, и цикл пробует снова. Cookie, на которое всё равно пришёл 403, тоже отправляет цикл на новый круг, уже с новой страницей блокировки. Страница, которая отвечает 403 без страницы блокировки DataDome, останавливает цикл с ошибкой, потому что решать нечего. Каждый повтор загружает с сайта ещё одну страницу блокировки, поэтому держите число попыток небольшим.

const { ApiException } = require("capskip");

const PROXY_URL = null; // e.g. "http://user:[email protected]:8080"; null means no proxy

async function fetchProtected(proxyUrl) {
  for (let attempt = 1; attempt <= 3; attempt += 1) {
    const block = await get(BROWSER, proxyUrl); // a fresh block page, a fresh challenge
    const captchaUrl = captchaUrlFrom(block);
    if (captchaUrl === null) {
      if (block.status >= 400) {
        throw new Error(`HTTP ${block.status} without a DataDome block page`);
      }
      return { page: block, headers: BROWSER, via: proxyUrl }; // no block page here
    }
    let result;
    try {
      result = await solve(captchaUrl, proxyUrl);
    } catch (err) {
      if (!(err instanceof ApiException)) throw err;
      if (err.message.includes("ERROR_PROXY_")) throw err; // the address is the problem
      console.log(`attempt ${attempt}: ${err.message}`); // the next attempt gets a new page
      continue;
    }
    const sent = await sendWithCookie(result, proxyUrl);
    if (sent.page.status !== 200) {
      console.log(`attempt ${attempt}: HTTP ${sent.page.status} with the cookie`);
      continue;
    }
    return sent;
  }
  throw new Error("DataDome not passed in 3 attempts");
}

async function main() {
  const { page, headers, via } = await fetchProtected(PROXY_URL);
  console.log(page.status, page.text.slice(0, 40));
  const again = await get(headers, via); // reuse the cookie while it lasts
  console.log(again.status);
}

main().catch((err) => {
  console.error(`${err.name}: ${err.message}`);
  process.exitCode = 1;
});

SDK отклоняет captchaUrl с t=bv ещё до отправки чего-либо, потому что решение по такому адресу пройти не может. Это исключение ValidationException не является ApiException, поэтому цикл выбрасывает его снова, а не повторяет попытку. Пусть оно дойдёт до вашего кода: это сигнал сменить адрес и начать заново. Цикл также выбрасывает снова две ошибки, которые называют адрес, ERROR_PROXY_BANNED и ERROR_PROXY_CONNECT_REFUSED, потому что в документации CapSkip сказано, что повторная отправка через тот же адрес снова не удалась бы. Сообщение ApiException содержит текст ответа приложения, поэтому цикл сравнивает его через includes(), а не проверяет точное совпадение. Увидев одну из этих ошибок, смените прокси перед новым запуском. Если вам нужны ротируемые прокси, смотрите руководство по ротации прокси.

При запуске на имитациях скрипт на Node.js 24.22.0 вёл себя так:

Настройка имитацииЧто сделал скрипт
Страница блокировки с ползунком, на каждое решение пришёл ответОдна задача, затем статус 200 с cookie и снова 200 при повторном использовании
Страница блокировки с проверкой устройстваВыполнился тот же код: captchaUrl указывал на /interstitial/, и результат был тем же
Первые два решения отклоненыДве строки попыток, затем статус 200 на третьем задании
Первые три решения отклоненыТри строки с попытками, затем Error “DataDome not passed in 3 attempts” и код выхода 1
Результат указывает прокси, как это делает собственный список CapSkipЗапросы с cookie прошли через этот прокси и получили 200
Ваш собственный прокси передан в решениеin.php получил адрес прокси и тип HTTP, и все запросы шли через прокси
Страница блокировки с t=bvValidationException, и имитация CapSkip не получила ни одного запроса
В скрипте решение принудительно завершено с ошибкой ERROR_PROXY_BANNEDЦикл выбросил её снова после одной задачи, без второй попытки
Страница с кодом 403, не являющаяся страницей блокировки DataDomeError “HTTP 403 without a DataDome block page” и код выхода 1, без запроса к CapSkip
Cookie отправлено с другим User-AgentИмитация сайта ответила 403
Cookie выдано через прокси, отправлено без негоИмитация сайта ответила 403, а 200, когда то же cookie шло через прокси

Последние две строки проверяют правила моей имитации, а они следуют документации CapSkip. Это не доказательство того, как ведёт себя настоящий сервис.

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

ОшибкаПричинаИсправить
ValidationExceptioncaptchaUrl, который не является адресом проверки DataDome, страница блокировки с t=bv, pageurl не с http или https, либо userAgent не от обычного Chrome для Windows. Сообщение говорит, какой из этих случаев имеет местоСоберите адрес заново из свежей страницы блокировки, смените адрес, если он заблокирован, и уберите параметр userAgent
ApiException с сообщением ERROR_WRONG_USERAGENTUser-Agent относится к Chrome для Windows, но его основная версия отличается от версии браузера CapSkip. Имитация выдала это для Chrome 120Не передавайте userAgent и используйте тот, который указан в результате
ApiException с ERROR_BAD_PARAMETERSТак ответило работающее приложение CapSkip на адрес, который не был адресом проверки, когда я отправил его вручную. SDK отклоняет такой адрес ещё до отправки чего-либоСоберите адрес заново из свежей страницы блокировки
ApiException с ERROR_CAPTCHA_UNSOLVABLEПроверка не была пройденаЗапросите страницу заново и отправьте её новый captchaUrl
ApiException с ERROR_PROXY_BANNEDDataDome заблокировал IP-адрес во время решения. Страница блокировки с t=bv перехватывается раньше, как ValidationExceptionСмените прокси и запросите страницу заново
ApiException с ERROR_PROXY_CONNECT_REFUSEDCapSkip сообщает об этом, когда не может подключиться через прокси. Воспроизвести это мне не удалосьПроверьте адрес, логин и тип прокси, затем запустите скрипт снова
TimeoutExceptionCapSkip не дал ответа за 300 секунд ожидания. Это не ApiException, поэтому цикл выбрасывает эту ошибку сноваПроверьте, что CapSkip запущен и решает, затем запустите скрипт снова
NetworkExceptionCapSkip не запущен либо неверно указаны хост или порт. Порт, на котором ничего не слушает, дал ECONNREFUSED в NetworkException, а это не ApiExceptionЗапустите CapSkip и проверьте настройки подключения
Всё равно 403 при отправке cookieЗапрос ушёл с другого IP-адреса, с другим User-Agent, или cookie истеклоКаждый раз отправляйте userAgent и proxy из результата или запустите цикл заново с новой страницей блокировки

Запуск решателя на сервере

Скрипту для DataDome CAPTCHA на Node.js нужны от CapSkip две вещи: хост и порт. В режиме Local это 127.0.0.1 и ваш собственный ПК. В режиме Server CapSkip слушает ваш сетевой адрес или публичный IP, поэтому скрипт на другом ПК, VPS или размещённой платформе может обращаться к нему через тот же API. Смените хост и порт в конструкторе CapSkip. Для стабильного удалённого доступа рекомендуется статический публичный IP. Режим Server всё равно работает на вашем оборудовании и всё равно без счётчика. Он открывает порт для любого адреса, поэтому добавьте в Windows Firewall блокирующее правило для адресов, которым доступ к нему не нужен. Изменится одно: решение выполняется на этой машине, поэтому cookie получено с адреса сервера. Если вы не передаёте прокси, запрос с cookie тоже должен уходить с этого сервера. Если прокси есть, передавайте один и тот же и в решение, и в свои запросы. Подробности смотрите в разделе Настройки подключения с описанием переключения режимов.

FAQ

Возвращает ли решатель DataDome токен?

Нет. Он возвращает cookie datadome. Это cookie вы отправляете в заголовке Cookie того запроса, который был заблокирован, вместе с тем же User-Agent. Поля формы для заполнения нет.

Нужен ли мне прокси для DataDome?

Для проверки нет. Для первого запуска хватит одного адреса. Согласно документации CapSkip, cookie работает только с адреса, с которого оно получено, поэтому когда запросов станет много, планируйте один sticky-прокси на задачу и передавайте один и тот же прокси и в решение, и в свои запросы. Только для прокси этому руководству нужна библиотека сверх SDK, потому что у fetch нет собственного параметра прокси для отдельного запроса.

Работает ли это и для ползунка, и для проверки устройства?

Да, через тот же вызов. Адрес captchaUrl для ползунка находится под /captcha/, а для проверки устройства под /interstitial/. В документации CapSkip сказано, что если проверка устройства переходит в ползунок, вызов следует за ней и туда.

Можно ли решить DataDome CAPTCHA на Node.js без браузера?

Вашему скрипту браузер не нужен. Он отправляет обычные HTTP-запросы в CapSkip и на сайт, а CapSkip проходит проверку в настоящем браузере на машине, где он работает, как описано в документации SDK.

Может ли размещённая платформа запускать это через мой CapSkip?

Да, через режим Server. Настройте CapSkip на прослушивание вашей сети или публичного IP, направьте конструктор CapSkip на этот хост и порт, и цикл работает так же, с одним изменением: решение теперь выполняется на сервере, поэтому давайте решению и своим запросам один и тот же прокси. Решатель остаётся на вашем оборудовании.

Коротко

Решение DataDome CAPTCHA на Node.js состоит из четырёх действий: прочитать страницу блокировки, собрать captchaUrl, вызвать dataDome() и повторить запрос с cookie вместе с User-Agent и IP-адресом, к которым оно относится. На вашей собственной машине решения ничего не стоят. Выбирайте безлимитный сервис распознавания капчи и повторяйте попытки, когда защищённый сайт раз за разом выдаёт новые проверки, но помните, что каждый повтор загружает ещё одну страницу блокировки, поэтому держите число попыток небольшим. Об остальных возможностях SDK читайте на странице сервиса распознавания капчи для Node.js. Тот же процесс на Python показывает руководство по DataDome на Python.