Назад в Центр знаний
Отпечатки

ua-parser-js: Client Hints, боты и предел 500

Запустите сборку ua-parser-js на фиксированных user agent и Client Hints: какую идентичность она сообщает, какие маркеры отбрасывает и как предел 500 символов скрывает токен.

BotBrowser Team

Документация

Нужна структурированная документация по теме Отпечатки?

Эта статья относится к редакционной библиотеке. Для пошаговой настройки, справки и постоянных обновлений переходите сразу в соответствующий раздел docs.

BotBrowser поддерживает повтор разрешенного fixture на том же origin под объявленным профилем и сохраняет ограниченные наблюдения о том, что сообщает страница, но он не может заставить сторонний анализатор согласиться с измеряемым браузером, изменить предел в 500 символов у библиотеки или превратить разобранную метку в вердикт о том, кто находится на другой стороне. Этот разбор закрепляет публичное дерево ua-parser-js на commit bd7480198cd0d237b43a24a964f424727abb78e9 (версия пакета 2.0.10), запускает его собранный анализатор на фиксированном списке строк user agent и наборов заголовков Client Hints и сообщает, что код действительно вернул.

TL;DR

  • Закрепленный анализатор помечает безголовый user agent как Chrome Headless, а его собственный классификатор ботов все равно отвечает isBot=false на ту же строку.
  • User agent приложения Electron сообщается как просто Chrome; имя браузера у анализатора не несет там никакого сигнала автоматизации.
  • Когда передается строка user agent, анализатор игнорирует противоречивый список брендов sec-ch-ua, поэтому Client Hints не отменяют того, что уже сказала строка.
  • Вход обрезается до 500 символов, и токен бота, начинающийся на индексе 500, невидим для классификатора, тогда как токен с индекса 490 находится.
  • Все это описывает один commit и одну сборку; это не утверждение о том, как сайт оценивает сессию.

Contents

UA_INPUT обрезает до 500 символов в PARSE, затем ветви BROWSER_NAME, IS_BOT и CH_IGNORED

Что на самом деле содержит закрепленный пакет

Версия 2.0.10 делит src/ на семь каталогов. src/main держит анализатор, src/bot-detection держит классификатор, src/browser-detection и src/device-detection держат отдельные сопоставители, src/extensions держит списки ботов, краулеров и fetchers, src/enums держит словари, и src/helpers держит мелкие утилиты. Опубликованная сборка dist/ua-parser.pack.js экспортирует UAParser, VERSION и пять перечислений (BROWSER, CPU, DEVICE, OS, ENGINE); подмодуль ботов это отдельная точка входа, экспортирующая isBot, isAIAssistant и isAICrawler.

Обе половины читают одну и ту же строку по разным путям кода, и эта разница является темой разбора. UAParser применяет регулярные выражения к тексту user agent и, когда получает объект заголовков, также к заголовкам Client Hints. isBot делает нечто более узкое: он разбирает user agent через реестр расширений и затем спрашивает, является ли получившийся type браузера одним из cli, crawler, fetcher или library. Библиотека, которая выдает правдоподобное имя браузера, может поэтому сосуществовать с классификатором, который говорит, что строка не бот, и два результата отвечают на разные вопросы.

Две константы в src/main/ua-parser.js решают, сколько текста может видеть каждая половина. Анализатор объявляет UA_MAX_LENGTH = 500, и setUA применяет trim(ua, UA_MAX_LENGTH) до любого сопоставления. Все, что дальше, включая строку, которую классификатор ботов разбирает снова, работает поэтому не более чем с первыми 500 символами того, что было передано.

Запуск анализатора на фиксированных строках user agent

Проба загружает опубликованную сборку и закрепленный подмодуль ботов, затем печатает одну строку на вход. Она читает фиксированный список; она не загружает страницу, не открывает профиль и не обращается к сервису:

node /tmp/uap-full-probe.mjs

Наблюдаемый вывод по секциям:

section: user-agent parse
chrome-win: browser=Chrome/155.0.0.0 engine=Blink os=Windows device=desktop
safari-mac: browser=Safari/18.4 engine=WebKit os=macOS device=desktop
headless-linux: browser=Chrome Headless/155.0.0.0 engine=Blink os=Linux device=desktop
edge-win: browser=Edge/155.0.0.0 engine=Blink os=Windows device=desktop
electron-win: browser=Chrome/155.0.0.0 engine=Blink os=Windows device=desktop
googlebot: browser=undefined/undefined engine=undefined os=undefined device=desktop
iphone-safari: browser=Mobile Safari/18.4 engine=WebKit os=iOS device=mobile

Три детали в этом блоке важнее остальных. Строка headless-linux сообщает отдельное имя браузера, Chrome Headless, что означает: анализатор действительно несет метку вида автоматизации для этой строки. Строка electron-win сообщает просто Chrome, поэтому приложение на Electron неотличимо от настольного браузера в этом поле. И googlebot сообщает undefined для браузера, движка и ОС, но все равно сообщает device=desktop, что показывает: «нет совпадений» выражается отсутствующими полями, а не ошибкой.

Поле device заслуживает отдельной заметки, потому что его легко прочитать как более сильное утверждение, чем оно есть. googlebot и chrome-win оба возвращаются как desktop, и headless-linux тоже; поле это запасная категория, а не измерение. Кто читает device=desktop как «это действительно настольная машина», прочитал значение по умолчанию как доказательство.

Где классификатор ботов расходится с именем браузера

Вторая секция того же запуска спрашивает классификатор обо всех строках; блок ниже содержит выдержку из полного вывода в файле свидетельства:

section: bot classifier
chrome-win: isBot=false isAIAssistant=false isAICrawler=false
headless-linux: isBot=false isAIAssistant=false isAICrawler=false
electron-win: isBot=false isAIAssistant=false isAICrawler=false
googlebot: isBot=true isAIAssistant=false isAICrawler=false

Строка headless-linux самая интересная. Анализатор называет эту строку Chrome Headless, а классификатор называет ее не бот. Оба утверждения верны относительно собственного набора правил: классификатор не смотрит на имя браузера для HeadlessChrome; он смотрит на type браузера, а безголовая сборка Chromium все равно объявляет обычный тип браузера. Отчет, который цитирует один из этих двух ответов и подразумевает другой, неверно описывает библиотеку.

Строка electron-win повторяет закономерность с другой стороны. Анализатор не дает метки автоматизации вовсе, и классификатор соглашается, что бота нет. Приложение Electron поэтому невидимо для обоих полей, если какая-то другая часть стека не смотрит на токен Electron/ напрямую, чего эта сборка не делает ни по одному из двух путей, которые проба задействовала.

Строка googlebot показывает самый сильный результат классификатора, isBot=true, тогда как анализатор ничего не возвращает. Это обратный случай: список краулеров распознает строку, хотя универсальный сопоставитель браузеров не имеет для нее шаблона. Чтение только browser.name заставило бы эту строку выглядеть сбоем разбора, а не совпадением бота.

Для контраста списки, ориентированные на ИИ, ведут себя иначе, чем общий список ботов. В более узкой пробе классификатор ответил isAICrawler=true для GPTBot и ClaudeBot и isAIAssistant=true для ChatGPT-User и Claude-User, с isBot=true для всех четырех. Простая строка HeadlessChrome и строка Electron остались false по всем трем предикатам, что согласуется с таблицей выше.

Где Client Hints перестают иметь значение

Третья секция передает список брендов в стиле Chromium и заголовки платформы, затем меняет только строку user agent:

section: client hints
hints-only: browser=undefined/undefined os=undefined device=desktop
ua-safari-plus-chrome-hints: browser=Safari/18.4 os=macOS device=desktop

Первая строка ожидаема: без user agent и только с заголовками sec-ch-ua анализатор ничего не возвращает по этому пути вызова. Одних заголовков здесь недостаточно, чтобы получить имя браузера, потому что список брендов потребляется шагом разбора Client Hints, а не универсальным сопоставителем, и проба вызвала простой результат.

Вторая строка это неожиданность. Строка user agent говорит Safari, заголовки говорят Chromium и Google Chrome в Windows, а результат это Safari/18.4 на macOS. По этому пути строка побеждает, а противоречивый список брендов игнорируется, поэтому страница, у которой user agent и Client Hints расходятся, сообщается как то, что утверждала строка. Это полезное свойство знать до того, как считать метку анализатора перекрестной проверкой: расхождение, которое вы ожидали увидеть обнаруженным, может пройти насквозь.

Причина, по которой вторая строка сохраняет строку, видна в parseCH(). Когда список брендов присутствует, цикл проходит по каждому бренду, пропускает любое имя, совпадающее с /not.a.brand/i, и переписывает текущее имя только при узком условии: предыдущее имя должно начинаться с Chrom, либо предыдущее имя должно быть Edge, а новый бренд должен быть WebView2. При предыдущем имени Safari ни одно не выполняется, поэтому все бренды рассматриваются и ни один не заменяет разобранное имя. Запись Chromium используется только для версии движка, когда тип элемента ENGINE, а не для имени браузера.

getBrowser().withClientHints() существует и действительно читает заголовки, и возвращает thenable, а не простой объект. Проба подтвердила эту форму, а также подтвердила, что ожидание его на противоречивых входах все равно вернуло Safari/18.4, потому что то же правило согласования сохраняет имя не Chromium. Важное предупреждение для читателя: синхронный getResult() и ожидаемый withClientHints() это разные формы вызова с разными ответами, и отчет должен указывать, какая из них дала метку.

Метка анализатора это лишь одно прочтение той же сессии. Когда вопрос в том, что делает детектор с именами вида автоматизации, которые выдал этот запуск, разбор реестра детекторов BotD запускает другой публичный детектор на явных состояниях, а обнаружение и предотвращение автоматизации перечисляет более широкий набор поверхностей, которые страница может проверить. Чтение их вместе не дает одной библиотеке заменить весь стек.

Где предел в 500 символов скрывает токен

Последняя секция проверяет константу, названную в src/main/ua-parser.js:

section: marker position vs 500-char cap
pad=430: totalLength=500 markerStart=490 isBot=true
pad=440: totalLength=510 markerStart=500 isBot=false

Две строки различаются только тем, сколько заполнения стоит перед токеном GPTBot/1.2. При общей длине 500 и токене, начинающемся на индексе 490, классификатор его находит. При 510 символах и токене, начинающемся на индексе 500, классификатор его не находит. Граница ровно там, где trim(ua, UA_MAX_LENGTH) обрезает строку, поэтому токен не разбирается неправильно; его просто никогда не видят.

Это конкретный режим отказа с ясной формой: страницу можно распознать как бота, когда ее токен попадает в первые 500 символов, и молча пропустить, когда тот же токен вытеснен чуть за границу длинным комментарием или списком расширений. В токене ничего не изменилось, только его смещение. Тест, который добавляет маркер к реалистичному длинному user agent и затем сообщает «не обнаружено», измеряет предел, а не токен.

Смягчение не в том, чтобы поднять константу в копии библиотеки, потому что закрепленное поведение это то, против которого написан остальной код стека. Оно в том, чтобы держать наблюдение честным: записывайте длину входа рядом с вердиктом и, когда ожидается маркер, подтверждайте, что его смещение попадает в предел. Токен из 10 символов на индексе 500 и тот же токен на индексе 490 это одинаковое содержимое строки и разные свидетельства.

Какие списки правил питают классификатор

Классификатор это не один список; это три обертки над тремя реестрами. BotTypes читает type браузера, который выдает расширение Bots, и считает cli, crawler, fetcher и library типами ботов. AIAssistants называет конкретные записи реестра Fetchers, а AICrawlers называет конкретные записи реестра Crawlers. Списки длинны так, что это важно: один закрепленный массив AICrawlers называет 53 отдельные записи краулера у поставщиков настолько разных, как Anthropic, ByteDance, Cloudflare, Hugging Face, Meta, OpenAI, Semrush и Zhipu, а массив AIAssistants называет 8 записей, по одной от Amazon, Anthropic, Cohere, DuckDuckGo, Google, Mistral, OpenAI и Perplexity.

Эта структура объясняет, почему одна и та же строка может быть isBot=true и isAICrawler=false. isBot спрашивает только, является ли разобранный тип типом бота, тогда как isAICrawler спрашивает, совпадает ли разобранное имя браузера с одним из 53 перечисленных имен. Краулер, который совпадает с расширением Bots по типу, но отсутствует в перечисленном списке ИИ-краулеров, это бот и не ИИ-краулер. Ни один ответ не неверен; это два запроса к двум разным наборам.

Списки к тому же стареют. Имя, которое существует сегодня в закрепленном массиве из 53 записей, может быть удалено или переименовано в следующей версии, а новый краулер, появившийся после этого commit, невидим для всех предикатов по построению. Это та же причина, по которой разбор закрепляет commit, а не ветку: ответ является свойством редакции списка в той же мере, что и входной строки.

Что покажет и чего не покажет мутационный тест

Соблазнительный следующий шаг это изменить один символ в user agent и утверждать, что анализатор хрупкий. Проба уже подсказывает, почему это было бы слабым утверждением. Токен Electron/33.0.0 присутствовал в строке и ничего не изменил, потому что ни один из двух путей, которые проба задействовала, его не ищет; мутация, которая убрала бы токен, тоже ничего не изменила бы, и тест выглядел бы как демонстрация устойчивости, а не неиспользуемого поля.

Обратное информативнее. Добавление токена GPTBot/1.2 к строке Chrome переключило isBot с false на true в более узкой пробе, поэтому мутация по этому токену демонстрирует, что классификатор его читает. Проверка того же токена на двух смещениях, 490 и 500, затем изолирует предел, а не токен. Одна мутация не может отделить «анализатор игнорирует этот текст» от «анализатор никогда не видел этот текст», и только пара смещений может.

Та же логика применима к заголовкам. Запуск, который добавляет противоречивый список брендов sec-ch-ua к user agent Safari и не наблюдает изменений, это доказательство, что строка имела приоритет на этом пути вызова. Запуск, который полностью убирает user agent и наблюдает undefined, это доказательство, что одни заголовки не потребляются простым вызовом результата. Два запуска, два разных утверждения; один запуск их спутал бы.

Как читать разобранную идентичность без преувеличения

Результат анализатора подтверждает узкое утверждение: этот commit, с этой строкой и этими заголовками, дал эту метку. Он не подтверждает утверждение, что сессия это браузер, бот или человек, и не подтверждает, что другой сайт придет к тому же выводу, потому что другой сайт может закрепить другую версию, передать другие заголовки или задать другой вопрос.

Держите четыре поля вместе, и запись останется полезной. Commit, потому что и списки шаблонов, и предел правятся между версиями. Вход, дословно и с его длиной, потому что предел делает смещение частью входа. Форма вызова, потому что getResult() и withClientHints() могут расходиться. И предикат, потому что isBot, isAIAssistant и isAICrawler это три отдельных ответа, которые проба показала расходящимися на одном семействе строк.

Страница документации о свойствах navigator описывает, как BotBrowser держит navigator.platform, navigator.userAgentData и связанные значения согласованными с загруженным профилем. Этот разбор добавляет сторону чтения: что широко используемый анализатор делает со строками, которые эти значения порождают, где расходятся его имя браузера и его классификатор ботов, и где его собственный предел входа делает маркер невидимым. Согласованность сообщаемых значений и правильность стороннего разбора это разные вопросы.

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

Practical conclusion

Закрепите commit, запустите анализатор на фиксированных входах и запишите длину, форму вызова и конкретный предикат рядом с каждой меткой. В этом commit безголовый user agent называется Chrome Headless, тогда как классификатор ботов отвечает false, строка Electron называется Chrome, противоречивый список брендов Client Hints игнорируется при наличии строки user agent, а токен, начинающийся на индексе 500, невидим, тогда как тот же токен на индексе 490 находится.

Sources

Next reading

Используйте fingerprinting через Client Hints для того, как меняется набор заголовков, и сокращение user agent и переход на Client Hints для того, что браузеры убрали из строки.

#Ua-Parser-Js#user agent#client hints#Детектирование Ботов#Происхождение Разбора

Эта статья входит в руководство: Что такое отпечаток браузера?

Переведите BotBrowser из исследований в продакшн

Используйте эти руководства, чтобы понять модель, а затем перейти к кроссплатформенной валидации, изолированным контекстам и масштабируемому браузерному развертыванию.