Så fungerar Puppeteer-detektering i praktiken: 12 signaler som avslöjar botar
Automationsramverk ärver en riktig webbläsares fingeravtryck men ändrar det på dussintals observerbara sätt. En fältguide till de 12 JavaScript-, nätverks- och beteendesignaler som försvarare använder för att fånga Puppeteer och Playwright.
Automationsramverk styr riktiga webbläsare, vilket innebär att de ärver webbläsarens fingeravtryck. En Puppeteer-styrd Chrome har samma user agent, samma Chrome-version och samma Chromium-build som en människas Chrome. Ytligt sett ser de identiska ut.
Men automationsramverk ändrar webbläsaren på dussintals subtila sätt — de flesta observerbara från JavaScript. Detektering är ingen magi. Det är en checklista.
1. Flaggan navigator.webdriver
Den enklaste och mest kända signalen. När Chrome körs under ett automationsramverk returnerar navigator.webdriver true. Det är en del av W3C WebDriver-specifikationen och Chromium implementerar det som standard.
Varje seriöst automationsprojekt lagar detta inom några sekunder. Puppeteer-extra-stealth skriver över gettern. Playwright-användare injicerar Object.defineProperty-skript på nya dokument. Flaggan är trivial att dölja.
Men själva det faktum att den kan döljas trivialt är i sig en signal. En legitim Chrome behöver inte dölja någonting. När en sida kontrollerar Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver') och deskriptorn ser annorlunda ut än i en inbyggd webbläsare — då avslöjas automationen även efter att flaggan har lagats.
2. Avvikelsen i chrome-objektet
Legitim Chrome exponerar ett globalt window.chrome-objekt med omfattande struktur — chrome.runtime, chrome.loadTimes, chrome.csi. Headless Chrome och äldre Puppeteer-uppsättningar utelämnar antingen detta objekt helt eller exponerar en nedskalad version.
Stealth-plugins återskapar objektet, men återskapandet är ofullständigt. chrome.runtime.onConnect kan finnas utan chrome.runtime.PlatformOs. Funktionssignaturer kan returnera objekt i stället för undefined. Varje avvikelse är en positiv signal.
3. Inkonsekvenser i Permissions API
Riktiga webbläsare returnerar sammanhängande resultat vid förfrågningar genom Permissions API. navigator.permissions.query({name: 'notifications'}) bör returnera 'default' om användaren inte uttryckligen har beviljat eller nekat behörighet.
Headless Chrome returnerar 'denied' som standard, eftersom det inte finns något gränssnitt för att visa en behörighetsdialog. Automationsramverk lagar detta — men ofta felaktigt. Ett vanligt avslöjande: en förfrågan om en ovanlig behörighet som 'clipboard-read' returnerar ett resultat som inte stämmer med vad riktiga Chrome gör i den aktuella versionen.
4. Arrayer av plugins och MIME-typer
navigator.plugins i en riktig webbläsare returnerar en PluginArray med poster som PDF-visaren och Chromium PDF-pluginet. I headless-läge är denna array tom.
Stealth-plugins lägger till falska poster, men posterna har ofta felaktiga egenskaper — saknad length, felaktiga beskrivningsfält, eller plugin-objekt som inte beter sig som Plugin-instanser när de testas med instanceof.
5. Avvikelser i språk och lokal
navigator.language och navigator.languages bör stämma med HTTP-huvudet Accept-Language. De bör också vara konsekventa med den tidszon som rapporteras av Intl.DateTimeFormat().resolvedOptions().timeZone.
En bot som påstår sig vara i Kyiv (tidszonen Europe/Kyiv) men skickar Accept-Language: en-US,en;q=0.9 och rapporterar navigator.language === 'en-US' är möjlig men statistiskt ovanlig. I kombination med en IP från ett datacenter i Frankfurt blir bilden tydlig.
6. WebGL-renderarsträngar
WebGL2RenderingContext.getParameter(WebGLDebugRendererInfo.UNMASKED_RENDERER_WEBGL) returnerar GPU-leverantör och modell. På en riktig maskin kan detta vara:
ANGLE (Intel, Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0)
Headless Chrome som körs i en container returnerar ofta:
ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero)), SwiftShader driver)
Varje GPU-sträng som innehåller SwiftShader eller Subzero är en stark botsignal.
7. Avvikelser i skärm och viewport
Ett riktigt webbläsarfönster har window.outerWidth och window.outerHeight som tar hänsyn till webbläsarens chrome — adressfält, flikar, bokmärkesfält. Skillnaden mellan yttre och inre dimensioner är vanligtvis 80–140 pixlar vertikalt.
Headless-webbläsare har ofta outerHeight === innerHeight eftersom det inte finns någon chrome att visa. Puppeteer med headless: false åtgärdar detta, men många automationsuppsättningar körs fortfarande med lika dimensioner.
8. Fontuppräkning
Riktiga användare har hundratals installerade typsnitt, som varierar med OS, språk och installerade program. Windows 11 med Office installerat har 500+ typsnitt. Automationscontainrar som kör Alpine Linux har 30.
Fontuppräkning genom document.fonts.check() eller rendering på offscreen-canvas avslöjar detta omedelbart. En webbläsare som rapporterar sig som Windows 11 Chrome men bara exponerar 30 typsnitt är nästan säkert automation.
9. Musrörelsers entropi
Riktiga användare rör musen med skakningar. Kurvorna är ickelinjära. Hastigheten varierar. Mellan två klick finns det vanligtvis hundratals mousemove-händelser.
Automationsramverk syntetiserar rörelse — antingen med raka linjer till koordinater, eller med programmerade kurvor som saknar mikroskakningarna hos en mänsklig hand. Även Bézier-kurvsimuleringar tenderar att ha misstänkt jämna accelerationsprofiler.
Botdetekteringssystem samlar in rörelsespår och poängsätter dem mot en modell av mänsklig rörelse. Spår med alltför jämn hastighet, alltför rena kurvor eller saknade mikrohändelser flaggas.
10. Timingsignaturer
Automationsramverk exekverar JavaScript med andra timingegenskaper än användarstyrda webbläsare. Timingen med performance.now() mellan händelser, särskilt mellan pointerdown och pointerup, följer en annan fördelning.
Riktiga klickhålltider är 50–150 ms med hög varians. Puppeteers standardklickhålltid är ett fast värde — ofta 30 ms eller 100 ms — med nästan ingen varians. Hundra klick med identiska hålltider är definitiv automation.
11. Avvikelse i TLS-fingeravtryck
Denna kringgår JavaScript helt. När Puppeteer ansluter använder den Chromiums TLS-stack — samma som en riktig Chrome. Men undici- eller axios-baserade scrapers som utger sig för att vara Chrome genom förfalskad user agent använder Node.js TLS-stack, som har en tydligt annorlunda Client Hello-signatur (JA4-hash).
En förfrågan som påstår User-Agent: Chrome/124.0.6367.60 men uppvisar ett Node.js-TLS-fingeravtryck är en omedelbar bekräftelse på en klient som inte är en webbläsare.
12. Läckage av CDP-protokoll
Puppeteer och Playwright kommunicerar med Chrome via Chrome DevTools Protocol. I vissa konfigurationer lämnar detta protokoll detekterbara artefakter — extra egenskaper på Runtime-objektet, förändrade Error.stack-format, eller specifika console.debug-utdata som bara dyker upp när en CDP-klient är ansluten.
Kommandot Runtime.enable ändrar särskilt hur stackspår renderas. Botar som aldrig utlöser fel undviker denna signal, men varje automationsflöde som stöter på ett undantag (och de flesta gör det) lämnar fingeravtryck.
Att lägga signalerna i lager
Ingen enskild signal är bevis på automation. En riktig användare med en ovanlig GPU-drivrutin kan returnera en märklig WebGL-sträng. En integritetsmedveten användare kan ha ändrat navigator.plugins.
Styrkan i modern detektering kommer av att kombinera signaler — 12 svaga träffar är starkare än en stark träff, eftersom en riktig användare sällan utlöser mer än 2–3 avvikelser samtidigt.
Detekteringssystem tilldelar varje signal en vikt och beräknar en poäng. Över tröskeln behandlas besökaren som automation. Den exakta tröskeln och vikterna är de svåraste delarna att få rätt — för strikt och legitima användare blockeras; för överseende och sofistikerade botar slinker igenom.
Signalerna i sig är den enkla delen. Nästan varje automationsramverk läcker alla 12. Det verkliga ingenjörsarbetet är att avgöra vilka kombinationer som spelar roll, vilka som är falska positiva, och hur modellen ska uppdateras när automationsramverken anpassar sig.