← Все статьи

Stealth-плагин для Puppeteer - что он латает и что не пытался чинить

4 мин чтения puppeteerautomationguide

Если ваш стек автоматизации на Puppeteer, puppeteer-extra-plugin-stealth наверняка уже лежит в вашем package.json. Это разумное значение по умолчанию: набор небольших заплаток на видимые из JS свойства, которые обычный Chromium выдаёт, а браузер человека нет. Системой личности он никогда не был, и обращение с ним как с таковой - то место, где месяцами работавшие скрипты начинают падать в проде без очевидной причины.

Что он на самом деле латает

Плагин поставляется списком именованных модулей обхода, каждый из которых целится в одно свойство: navigator.webdriver, chrome.runtime, chrome.app, chrome.csi, chrome.loadTimes, iframe.contentWindow, media.codecs, navigator.languages, navigator.permissions, navigator.plugins, sourceurl, user-agent-override, webgl.vendor и другие. Каждый модуль выполняется до скриптов страницы и переписывает геттер так, чтобы свойство читалось как в headful-браузере под управлением человека, а не как в автоматизации по умолчанию. Это список заплаток, применяемых при загрузке страницы и нацеленных на конкретные свойства.

Проблема сопровождения

Каждый модуль в этом списке - ставка на то, что считается «нормальным» в текущей сборке Chromium. Значения по умолчанию для headless, поведение permissions API и точная форма chrome.runtime менялись от релиза к релизу, поэтому модуль, написанный под одну версию, к следующей может устареть или стать неверным. Заплатка, которая перестаралась, - подпись сама по себе: детектору не обязательно ловить автоматизацию напрямую, если можно узнать конкретную форму свойства, которую оставляет за собой stealth-плагин. Поддерживать список модулей в порядке означает перепроверять его на каждом релизе Chromium, который плагин объявляет поддерживаемым, а проект с медленным темпом релизов относительно самого Chromium - реальный операционный риск для тех, кто зависит от него, не читая changelog.

Три вещи, которые в его задачи не входили

Личность, сохраняющаяся между запусками. Плагин правит свойства внутри текущего процесса. Он не генерирует и не хранит хеш canvas, строку рендерера WebGL, список шрифтов или аудиоотпечаток, привязанные к каталогу профиля. Направьте его на свежий userDataDir или на тот же после обновления Chromium - и подложечная подпись устройства может измениться, пока пропатченные свойства остаются прежними. Запустите несколько личностей на одной машине - и все они сообщат один и тот же аппаратный отпечаток под разными куками, а это системе оценки риска кластеризовать проще, чем при полном отсутствии stealth-заплаток.

Привязка прокси, тянущая за собой часовой пояс и локаль. page.authenticate() и --proxy-server= маршрутизируют трафик. Ни то, ни другое не трогает Intl.DateTimeFormat().resolvedOptions().timeZone или navigator.language, поэтому сессия может выходить через берлинский IP, а браузер по-прежнему будет сообщать американский часовой пояс и локаль. Именно такое расхождение и ловят проверки согласованности часового пояса с IP, и ни один модуль обхода в stealth-плагине им не занимается.

Аутентификатор WebAuthn для passkey. Плагин не трогает WebAuthn вообще. Домен CDP WebAuthn в Puppeteer умеет добавить виртуальный аутентификатор на сессию, но ничто не сохраняет учётные данные вместе с профилем, поэтому любой аккаунт, перешедший на вход по passkey, становится недостижим при следующем запуске скрипта.

Проверки, которые стоит прогнать на текущей схеме

Разбор отпечаточной части этой проверки - в чек-листе согласованности отпечатка.

Когда stealth-плагин и есть верный ответ

Одна личность, одна машина, цели, которые не снимают отпечатки агрессивно, аккаунты, не перешедшие на passkey: плагин плюс постоянный userDataDir закрывают этот случай и не стоят ничего сверх установки. Менять его на что-то другое в таком масштабе - накладные расходы на несуществующую задачу.

Где здесь AntiBrow и какова настоящая цена перехода

AntiBrow генерирует отпечаток один раз на профиль и применяет его в движке браузера, ниже слоя JS, поэтому он переживает перезапуски, а не плывёт вместе с машиной. Привязка прокси к профилю выводит часовой пояс, локаль и геолокацию из выходного IP автоматически. У каждого профиля свой виртуальный аутентификатор, поэтому однажды зарегистрированный passkey сохраняется и воспроизводится при следующем входе. Локальных профилей неограниченно на любом тарифе, включая бесплатный, без карты и без срока.

Скажем цену прямо: launch() возвращает стандартные Page и BrowserContext из Playwright, а не из Puppeteer. Код вокруг page.$, page.$eval, ElementHandle или page.setRequestInterception не заработает без изменений на Page из Playwright. Локаторы, автоожидание и перехват запросов отличаются достаточно, чтобы набор тестов и библиотека помощников в стиле Puppeteer столкнулись с настоящим переписыванием, а не с заменой одной строки. Если ваша кодовая база глубоко в идиомах Puppeteer, честно заложите этот переезд до того, как начнёте. Команды, уже сидящие на Playwright, ничего из этого не встретят; те же пробелы с их стороны разобраны в статье когда stealth-плагина для Playwright перестаёт хватать.

Проверьте сами

Создайте бесплатный профиль, привяжите его к любому прокси и повторите проверки canvas и часового пояса на нём, а не на обычном userDataDir. Вызов launch() и опции профиля есть в справочнике SDK.

Попробуйте на бесплатном тарифе.

Неограниченное число локальных профилей, без карты. Проверьте результат детекторами сами.