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