Jak ve skutečnosti funguje detekce Puppeteeru: 12 signálů, které boty prozradí
Automatizační frameworky zdědí otisk skutečného prohlížeče, ale mění ho desítkami pozorovatelných způsobů. Průvodce 12 signály na úrovni JavaScriptu, sítě a chování, jimiž obránci odhalují Puppeteer a Playwright.
Automatizační frameworky ovládají skutečné prohlížeče, což znamená, že dědí otisk prohlížeče. Chrome řízený Puppeteerem má stejný user agent, stejnou verzi Chrome i stejný build Chromia jako Chrome člověka. Na první pohled vypadají identicky.
Automatizační frameworky ale prohlížeč mění desítkami jemných způsobů — většina z nich je pozorovatelná z JavaScriptu. Detekce není žádné kouzlo. Je to kontrolní seznam.
1. Příznak navigator.webdriver
Nejjednodušší a nejznámější signál. Když Chrome běží pod automatizačním frameworkem, navigator.webdriver vrací true. Je součástí specifikace W3C WebDriver a Chromium ho implementuje ve výchozím nastavení.
Každý seriózní automatizační projekt tohle záplatuje během několika sekund. Puppeteer-extra-stealth přepíše getter. Uživatelé Playwrightu vkládají do nových dokumentů skripty s Object.defineProperty. Skrýt tento příznak je triviální.
Ale sama skutečnost, že ho lze triviálně skrýt, je signálem. Legitimní Chrome nemá potřebu cokoli skrývat. Když stránka zkontroluje Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver') a deskriptor vypadá jinak než u nativního prohlížeče — automatizace je odhalena i po záplatování příznaku.
2. Anomálie objektu chrome
Legitimní Chrome vystavuje globální objekt window.chrome s bohatou strukturou — chrome.runtime, chrome.loadTimes, chrome.csi. Bezhlavý (headless) Chrome a starší sestavy Puppeteeru tento objekt buď zcela vynechají, nebo vystaví jeho ořezanou verzi.
Stealth pluginy objekt znovu vytvoří, jenže to znovuvytvoření je nedokonalé. chrome.runtime.onConnect může být přítomné bez chrome.runtime.PlatformOs. Signatury funkcí mohou vracet objekty místo undefined. Každý nesoulad je pozitivním signálem.
3. Nesrovnalosti v Permissions API
Skutečné prohlížeče vracejí konzistentní výsledky při dotazech přes Permissions API. navigator.permissions.query({name: 'notifications'}) by mělo vrátit 'default', pokud uživatel oprávnění výslovně neudělil ani neodepřel.
Bezhlavý Chrome ve výchozím stavu vrací 'denied', protože neexistuje UI, které by zobrazilo výzvu k udělení oprávnění. Automatizační frameworky tohle záplatují — ale často nesprávně. Častý příznak: dotaz na neobvyklé oprávnění jako 'clipboard-read' vrátí výsledek, který neodpovídá tomu, co dělá skutečný Chrome v aktuální verzi.
4. Pole pluginů a MIME typů
navigator.plugins ve skutečném prohlížeči vrací PluginArray se záznamy jako PDF prohlížeč a plugin Chromium PDF. V bezhlavém režimu je toto pole prázdné.
Stealth pluginy přidávají falešné záznamy, ale ty mají často špatné vlastnosti — chybějící length, špatná pole popisu nebo objekty pluginů, které se při testu přes instanceof nechovají jako instance Plugin.
5. Nesoulady jazyka a locale
navigator.language a navigator.languages by měly odpovídat HTTP hlavičce Accept-Language. Měly by být také konzistentní s časovým pásmem hlášeným přes Intl.DateTimeFormat().resolvedOptions().timeZone.
Bot, který tvrdí, že je v Kyjevě (časové pásmo Europe/Kyiv), ale odesílá Accept-Language: en-US,en;q=0.9 a hlásí navigator.language === 'en-US', je možný, ale statisticky neobvyklý. V kombinaci s IP z datového centra ve Frankfurtu se obraz vyjasní.
6. Řetězce WebGL rendereru
WebGL2RenderingContext.getParameter(WebGLDebugRendererInfo.UNMASKED_RENDERER_WEBGL) vrací výrobce a model GPU. Na skutečném stroji to může být:
ANGLE (Intel, Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0)
Bezhlavý Chrome běžící v kontejneru často vrací:
ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero)), SwiftShader driver)
Jakýkoli řetězec GPU obsahující SwiftShader nebo Subzero je silným signálem bota.
7. Anomálie obrazovky a viewportu
Skutečné okno prohlížeče má window.outerWidth a window.outerHeight, které zohledňují chrome prohlížeče — adresní řádek, panely, lištu záložek. Rozdíl mezi vnějšími a vnitřními rozměry je typicky 80–140 pixelů na výšku.
Bezhlavé prohlížeče mají často outerHeight === innerHeight, protože není žádný chrome k zobrazení. Puppeteer s headless: false to napraví, ale mnoho automatizačních sestav stále běží se shodnými rozměry.
8. Výčet písem
Skuteční uživatelé mají stovky nainstalovaných písem, lišících se podle OS, jazyka a nainstalovaných aplikací. Windows 11 s nainstalovaným Office mají přes 500 písem. Automatizační kontejnery běžící na Alpine Linuxu mají 30.
Výčet písem přes document.fonts.check() nebo vykreslování na offscreen canvas to okamžitě odhalí. Prohlížeč hlásící se jako Windows 11 Chrome, ale vystavující pouhých 30 písem, je téměř jistě automatizace.
9. Entropie pohybu myši
Skuteční uživatelé pohybují myší s chvěním. Křivky jsou nelineární. Rychlost se mění. Mezi dvěma kliknutími je typicky stovky událostí mousemove.
Automatizační frameworky pohyb syntetizují — buď přímými čarami k souřadnicím, nebo naprogramovanými křivkami, jimž chybí mikrochvění lidské ruky. I simulace pomocí Bézierových křivek mívají podezřele hladké profily zrychlení.
Systémy detekce botů sbírají stopy pohybu a hodnotí je vůči modelu lidského pohybu. Stopy s příliš konzistentní rychlostí, příliš čistými křivkami nebo chybějícími mikroudálostmi jsou označeny.
10. Signatury časování
Automatizační frameworky vykonávají JavaScript s jinými časovými charakteristikami než prohlížeče řízené uživatelem. Časování performance.now() mezi událostmi, zejména mezi pointerdown a pointerup, sleduje jiné rozdělení.
Skutečné doby podržení kliknutí jsou 50–150 ms s vysokým rozptylem. Výchozí podržení kliknutí u Puppeteeru je pevná hodnota — často 30 ms nebo 100 ms — s téměř nulovým rozptylem. Sto kliknutí s identickými dobami podržení je jednoznačnou automatizací.
11. Nesoulad TLS otisku
Tenhle obchází JavaScript úplně. Když se Puppeteer připojí, používá TLS zásobník Chromia — stejný jako skutečný Chrome. Ale scrapery založené na undici nebo axiosu, které se přes falešný user agent vydávají za Chrome, používají TLS zásobník Node.js, jenž má výrazně odlišnou signaturu Client Hello (hash JA4).
Požadavek, který tvrdí User-Agent: Chrome/124.0.6367.60, ale předkládá TLS otisk Node.js, je okamžitým potvrzením klienta, který není prohlížečem.
12. Únik protokolu CDP
Puppeteer a Playwright komunikují s Chromem přes Chrome DevTools Protocol. V některých konfiguracích tento protokol zanechává detekovatelné artefakty — nadbytečné vlastnosti na objektu Runtime, pozměněné formáty Error.stack nebo specifické výstupy console.debug, které se objeví jen tehdy, když je připojen CDP klient.
Zejména příkaz Runtime.enable mění, jak se vykreslují stack trace. Boti, kteří nikdy nevyvolají chybu, se tomuto signálu vyhnou, ale jakýkoli automatizační tok, který narazí na výjimku (a většina na ni narazí), zanechá otisky.
Vrstvení těchto signálů
Žádný jednotlivý signál není důkazem automatizace. Skutečný uživatel s neobvyklým ovladačem GPU může vrátit divný WebGL řetězec. Uživatel dbající na soukromí může mít upravené navigator.plugins.
Síla moderní detekce vzniká z kombinování signálů — 12 slabých pozitiv je silnějších než jedno silné pozitivum, protože skutečný uživatel jen zřídka spustí víc než 2–3 anomálie současně.
Systémy detekce přiřadí každému signálu váhu a spočítají skóre. Nad prahovou hodnotou je návštěvník považován za automatizaci. Přesná prahová hodnota a váhy jsou tou nejtěžší částí, kterou je třeba nastavit správně — příliš přísně a legitimní uživatelé jsou blokováni; příliš benevolentně a sofistikovaní boti proklouznou.
Samotné signály jsou tou snadnou částí. Téměř každý automatizační framework prozradí všech 12. Skutečná inženýrská práce spočívá v rozhodování, na kterých kombinacích záleží, které jsou falešná pozitiva a jak model aktualizovat, jak se automatizační frameworky přizpůsobují.