Блогер разделил читателей и ботов на Cloudflare Worker без JavaScript

Блогер разделил читателей и ботов на Cloudflare Worker без JavaScript

Владелец блога построил на Cloudflare Worker классификатор запросов, который отделяет читателей-людей от ботов и автоматизации, не полагаясь на JavaScript. Классификатор использует четыре источника: сетевые метаданные (номер автономной системы, ASN), Fetch Metadata (заголовки Sec-Fetch-Mode и Sec-Fetch-Dest, показывающие, что запрос, это навигация по странице), заголовки Accept и Accept-Language и, наконец, User-Agent, включая сверку с именованными краулерами и проверку подписей Web Bot Auth. Правила применяются по порядку: сначала проверяются подписи и известные клиенты, затем, принадлежность к списку хостинг-сетей, затем, соответствие заявленной версии браузера (Chromium 76+, Firefox 90+, Safari/iOS 16.4+) и наличие Fetch Metadata, и только потом, заголовки Accept.

За два полных вторых UTC-суток эти правила вывели 277 из 372 запросов с браузерным User-Agent из категории «Браузеры», 74,5% трафика. Из оставшихся 95 запросов, которые классификатор всё же засчитал как браузерный HTML, отдельная система, Cloudflare Web Analytics, работающая через отдельный скрипт-счётчик, зафиксировала за то же окно только 14 просмотров страниц. Автор подчёркивает: ни меньшее число «браузеров», ни совпадение со скрипт-счётчиком сами по себе не доказывают точность подсчёта аудитории, расхождение остаётся открытым вопросом, а не решённой задачей.

Первый тревожный сигнал автор получил 3 сентября, сравнив дневные показатели: 578 уникальных идентификаторов клиентов по данным собственной аналитики против 52 по данным скрипт-счётчика, на первый взгляд, читателей оказалось в одиннадцать раз больше, чем показывал альтернативный счётчик. Но при более аккуратном сопоставлении (одинаковое окно, сравнение просмотров страниц, а не идентификаторов, исключение служебного раздела /stats) разрыв сократился до более сопоставимых 1209 против 113. 2 сентября 100 из 113 дневных клиентов загрузили ровно одну страницу, а 156 из 164 браузерных просмотров пришли без referrer, сами по себе эти факты автоматизацию не доказывают, но один клиент, классифицированный как мобильный, запросил 31 разные страницы за одну и ту же секунду, что и стало для автора поводом усомниться в собственных цифрах.

В 72-часовой выборке логов, завершившейся 3 сентября, 430 из 844 успешных запросов страниц пришли с сетей, которые классификатор относит к хостинговым, то есть выглядели как обычная навигация браузера, но по сетевому происхождению были отнесены к «облачным браузерам». Крупнейший кластер внутри этой группы, 374 запроса от одного клиента Google Cloud, выдававшего себя за Chrome Mobile 114; заголовки в этом случае не позволяли отличить его от настоящего мобильного браузера, различить их удалось только по сети.

В процессе разбора автор обнаружил и исправил баг в собственной проверке заголовка Accept: старая функция ошибочно засчитывала Accept: text/html;q=0 (заголовок, явно исключающий HTML) как согласие на HTML и не признавала Accept: text/* (заголовок, который HTML как раз допускает). Причина, сравнение подстрок без учёта параметра качества (q) и правил медиа-диапазонов из RFC 9110. После починки 6 сентября на 12 отобранных локальных тестовых случаях 7 неверных результатов сменились на верные; автор отдельно оговаривает, что это проверка на регрессию, а не оценка ошибки в проде, и что сохранённые булевы флаги не позволяют посчитать, сколько исторических запросов баг затронул на практике.

Отдельно автор разобрал подписи Web Bot Auth: в накопленной с момента запуска верификатора выборке, по 5 сентября включительно, база данных D1 хранит девять запросов с подтверждённой подписью (включая краулеров и намеренные тесты), и его собственное более раннее утверждение, что ни одного подписанного запроса не поступало, оказалось ошибочным. При этом текущая группировка статистики объединяет запросы с подписью и запросы от ИИ-ассистентов в одну категорию «ИИ-агенты», хотя подпись доказывает лишь идентичность подписавшего, а не роль клиента и не то, что запрос инициировал человек через ассистента, это смешение автор называет дефектом группировки и обещает исправить.

Из открытых классификаторов, isbot, Anubis и GoatCounter, автор позаимствовал отдельные приёмы (распознавание заявленных ботов, сетевую классификацию, сохранение причины классификации), но подчёркивает: чтение чужого кода не доказывает ни новизну собственной комбинации приёмов, ни то, что остальные счётчики упускают тот же трафик. Мотивацию он объясняет прямо: он не хочет блокировать автоматизацию, включая браузеры без графического интерфейса на домашних подключениях и облачных агентов вроде Meshclaws или Hermes, работающих через playwright или cypress, а хочет полной видимости и прозрачной категоризации того, кто читает его статьи, вплоть до идеи собственного механизма цитирования по образцу arXiv.

Ключевые факты

  • Правила по сетевым метаданным и HTTP-заголовкам (без JavaScript) вывели 277 из 372 запросов с браузерным User-Agent из категории «Браузеры», 74,5% трафика за двое полных суток.
  • После чистки счётчик страниц всё равно расходится с Cloudflare Web Analytics: 95 браузерных HTML-запросов против 14 зафиксированных просмотров за то же окно, причина расхождения не установлена.
  • Первое сравнение «578 против 52» выглядело как разрыв в 11 раз, но после поправки на окна и метрики более сопоставимые числа, 1209 против 113.
  • В 72-часовой выборке логов 430 из 844 успешных запросов страниц пришли с хостинг-сетей; крупнейший кластер, 374 запроса от одного клиента Google Cloud, выдававшего себя за Chrome Mobile 114.
  • Автор нашёл и 6 сентября исправил баг проверки заголовка Accept (7 из 12 тестовых случаев дали верный результат вместо неверного) и обнаружил девять реально подписанных Web Bot Auth запросов, опровергнув собственное более раннее утверждение, что таких запросов не было вовсе.

Почему это важно

Пост фиксирует практическую проблему любого сайта: чем больше трафика идёт от краулеров, ассистентов и облачных агентов, тем менее надёжны стандартные счётчики аудитории, построенные на User-Agent и JavaScript-скрипте. Автор показывает на своих же данных, что «браузерный» User-Agent, это не доказательство читателя-человека: три четверти таких запросов у него на деле пришли с хостинг-сетей или не прошли проверку заголовков навигации. Ценность материала не в сенсации, а в методе: он разложил утверждение «столько-то читателей» на проверяемые компоненты и честно показал, где доказательства заканчиваются.

Кому это важно

В первую очередь, владельцам блогов и небольших сайтов, которые принимают решения (что писать, о чём есть спрос, стоит ли что-то монетизировать) на основе цифр аналитики. Во вторую, инженерам, которые строят похожие edge-классификаторы на Cloudflare Workers или аналогах и сталкиваются с теми же источниками ошибок: заголовки Accept, версии браузеров, списки хостинг-сетей. Материал также полезен всем, кто разбирается с ростом трафика от ИИ-ассистентов и агентов (ChatGPT-User, PerplexityBot, GPTBot и им подобных) и пытается отделить его от обычных читателей, не блокируя ценный доступ.

Как это применить

Собственно классификатор устроен как цепочка правил с приоритетом: сначала проверяются подписи Web Bot Auth и именованные клиенты, затем, принадлежность IP к курируемому списку хостинг-сетей (это стоит выше проверки заголовков навигации), затем, соответствие заявленной версии браузера порогам (Chromium 76+, Firefox 90+, Safari/iOS 16.4+) вместе с наличием Fetch Metadata, и только в конце, корректная проверка Accept с учётом параметра качества и медиа-диапазонов по RFC 9110, а не простым сравнением подстрок. Каждой классификации сохраняется причина, а не только итоговый ярлык, это то, что автор прямо называет самым полезным приёмом, позаимствованным у открытого проекта GoatCounter, и что позволило ему находить собственные баги, а не просто верить итоговым цифрам.

Можно ли доверять

Это личный блог одного автора, а не независимое исследование: все цифры и выводы, самоотчёт о собственной системе, без внешней проверки. Сильная сторона материала, прозрачность: автор сам отделяет то, что доказано, от того, что нет (например, прямо пишет, что 12 тестовых случаев, это проверка на регрессию, а не оценка ошибки в проде, и что сохранённые флаги не позволяют посчитать масштаб прошлого бага), ссылается на код и первичные измерения и убирает из более ранней версии статьи вывод, который сам счёл неподтверждённым. Слабая сторона, это ровно один сайт с относительно небольшим трафиком (десятки-сотни запросов в день), и выводы не обязательно переносятся на сайты другого масштаба или профиля.

Риски и подводные камни

Главный риск, путать разные виды доказательств: сетевой ASN показывает, откуда пришёл запрос, а не кто им управляет; подпись Web Bot Auth подтверждает подписавшего, а не то, что запрос инициировал человек через ассистента, и сам автор нашёл у себя именно такое смешение в группировке статистики «ИИ-агенты». Курированный список хостинг-сетей, компромисс: он не трогает сети совместного доступа и потребительские VPN, а значит часть автоматизации проходит мимо него. User-Agent и почти все проверяемые заголовки в принципе подделываемы, и классификатор не претендует на нулевую долю ошибок ни в одну, ни в другую сторону. Наконец, ключевое расхождение, 95 против 14 просмотров за одно и то же окно, на момент публикации остаётся неразрешённым: причина не установлена, а не спрятана под допущение.

«Мне хочется знать даже про headless-браузеры на домашних подключениях, таких агентов, как Meshclaws или облачные агенты Hermes, работающие через playwright, cypress или любую другую автоматизацию. Мы не хотим ошибочно их считать или блокировать, наоборот, я хочу их принять: читать мои статьи может кто угодно.»

— автор блога, о том, зачем ему нужна полная видимость трафика, а не блокировка автоматизации