Большинство «антиботов» 2010-х работают по IP-листам: есть огромная база подозрительных адресов, и если запрос пришёл с одного из них - режем. В 2026 году это проигрышная стратегия: серые IP-пулы стоят $0.001 за адрес, а residential proxies дают чистый американский IP за $3 в день. Кроме того, маркетплейсы массово раздают арендные IP - Apple Private Relay, Google One VPN, iCloud Private Relay - и они выглядят чище, чем IP в офисе.

TL;DR

IP-блокировка ловит 60% дешёвых ботов, но пропускает 95% средних и продвинутых. JS-fingerprint смотрит на сам браузер - Canvas, WebGL, шрифты, плагины, поведение мыши - и собирает уникальный отпечаток. Поведенческие сигналы (тайминги, скролл, клики) дают вторую линию обороны. Вместе это останавливает 99%+ автоматического трафика.

Почему IP-блок устарел

Простая арифметика: один датацентровый IP стоит $0.50-2 в месяц, а residential - $3-8 в день. Бот, ворующий конверсии в партнёрке с payout $5, зарабатывает достаточно за 1-2 успешных лида, чтобы окупить день работы. Бан по IP - это игра в whack-a-mole, где у вас один молоток, а у бота 1.2 миллиарда нор.

Что ещё проигрывают чистые IP-листы:

  • Mobile-карриеры - десятки тысяч пользователей за одним NAT'ом.
  • Корпоративные сети - банить нельзя, реальные пользователи внутри.
  • Apple Private Relay - анонимизирует IP без изменения качества трафика.
  • Headless-браузеры с residential proxy - выглядят как обычный пользователь Mac в Чикаго.

Что такое JS-fingerprint

Идея: каждый браузер уникален в своих мелочах. Версия движка, набор шрифтов, GPU, аудио-стек, разрешение экрана, тайм-зона, плагины. По отдельности - банальные параметры, вместе - отпечаток с энтропией 30+ бит. Этого достаточно, чтобы отличить миллиард пользователей.

Что мы собираем на стороне клиента:

// упрощённый набор сигналов
{
  canvas: hash(canvasRender("TDS-probe", "Inter 16px")),
  webgl: hash(gl.getParameter(gl.RENDERER) + gl.getParameter(gl.VENDOR)),
  audio: hash(offlineAudioContext.fingerprint()),
  fonts: detectAvailableFonts(['Inter', 'Roboto', ...]),
  screen: { w, h, depth, dpr },
  navigator: { ua, platform, lang, hwConcurrency, memory },
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
  plugins: navigator.plugins.length,
  webdriver: !!navigator.webdriver,  // headless flag
  permissions: queryPermissions(['notifications', 'geolocation'])
}

Из этого хешем формируется fp_id, который мы запоминаем 7 дней. Если тот же fp_id появляется на 50 разных IP за час - это не пользователь, это ферма браузеров.

Поведенческие сигналы

Fingerprint статичен. А поведение - нет. Headless-браузер не двигает мышь как человек, не скроллит «рваными» жестами, не задерживается на странице 3 секунды перед кликом. Эти сигналы дают нам второй слой:

Сигнал Что наблюдаем Бот выдаёт
mousemove кривая, ускорения прямая, мгновенная
scroll тайминги, дельты равномерный или нет
click координаты vs target центр элемента
focus/blur время на странице < 500 мс
keypress тайминг между нажатиями постоянный или 0

Скоринг и решения

Все сигналы складываются в score 0-100. Решение принимаем не «бот / не бот», а градусом:

  • 0-20: чистый трафик, пускаем на оффер.
  • 20-60: подозрительный, отправляем на white page.
  • 60-90: бот, скорее всего - клоакинг или 404.
  • 90-100: точно бот, отбиваем 200 OK на пустую страницу.
Почему именно «отбиваем», а не блокируем

Если боту вернуть 403, его оператор поймёт что есть антибот, и подкрутит обход. Если же он получит 200 OK на пустую страницу - он считает что трафик прошёл, и продолжает гнать. Это даёт нам сигнал и экономит вашу партнёрку.

Компромиссы метода

JS-fingerprint не идеален. Честно перечислим минусы:

  • +1 round-trip: нужен JS на стороне клиента, +20-40 мс к latency. Мы митигируем это инлайн-скриптом и параллельной загрузкой.
  • JS-отключенные браузеры: ~0.2% трафика. Их мы фолбэчим на чистый IP-чек.
  • Privacy-режимы: Brave, Firefox-strict, Tor - fingerprint спуфится. Этот сегмент мы оцениваем по поведению, а не fingerprint'у.
  • False positives: 0.3-0.5% реальных пользователей в зоне 20-60. Их теряем на white page, но не теряем как клиентов.

Что мы используем в TDS

Под капотом - собственная имплементация без внешних зависимостей:

  • Edge-обработчик на Rust, 28 мс p95 от запроса до решения.
  • База IP: 1.2 миллиарда адресов с метаданными (DC / ISP / mobile / VPN / TOR / residential), обновление раз в 6 часов.
  • Fingerprint: 24 сигнала, hash в 64-битный fp_id.
  • Behavioural: трекинг 6 событий (mouse, scroll, click, keypress, focus, blur) на странице.
  • Скоринг: gradient-boosted дерево, обучается на 100M кликов в неделю.

Метрики последних 30 дней по нашему трафику: false negative ≤ 0.8%, false positive ≤ 0.4%, среднее время решения 28 мс p95.