Wie Puppeteer-Erkennung wirklich funktioniert: 12 Signale, die Bots verraten
Automatisierungs-Frameworks erben den Fingerprint eines echten Browsers, verändern ihn aber auf Dutzende beobachtbare Arten. Ein Feldführer zu den 12 JavaScript-, Netzwerk- und Verhaltenssignalen, die Puppeteer und Playwright verraten.
Automatisierungs-Frameworks steuern echte Browser und erben damit deren Fingerprint. Ein von Puppeteer gesteuertes Chrome hat denselben User-Agent, dieselbe Chrome-Version und denselben Chromium-Build wie das Chrome eines Menschen. Oberflächlich betrachtet sehen sie identisch aus.
Aber Automatisierungs-Frameworks verändern den Browser auf Dutzende subtile Arten — die meisten davon aus JavaScript heraus beobachtbar. Erkennung ist keine Magie. Sie ist eine Checkliste.
1. Das navigator.webdriver-Flag
Das einfachste und bekannteste Signal. Läuft Chrome unter einem Automatisierungs-Framework, gibt navigator.webdriver true zurück. Es ist Teil der W3C-WebDriver-Spezifikation, und Chromium implementiert es standardmäßig.
Jedes ernsthafte Automatisierungsprojekt patcht das binnen Sekunden. Puppeteer-extra-stealth überschreibt den Getter. Playwright-Nutzer injizieren Object.defineProperty-Skripte in neue Dokumente. Das Flag ist trivial zu verstecken.
Aber dass es sich trivial verstecken lässt, ist selbst ein Signal. Ein legitimes Chrome muss nichts verstecken. Wenn eine Seite Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver') prüft und der Deskriptor anders aussieht als der eines nativen Browsers — ist die Automatisierung überführt, selbst nachdem das Flag gepatcht wurde.
2. Die Anomalie des chrome-Objekts
Legitimes Chrome exponiert ein globales window.chrome-Objekt mit erheblicher Struktur — chrome.runtime, chrome.loadTimes, chrome.csi. Headless Chrome und ältere Puppeteer-Setups lassen dieses Objekt entweder komplett weg oder exponieren eine abgespeckte Version.
Stealth-Plugins bauen das Objekt nach, aber der Nachbau ist unvollkommen. chrome.runtime.onConnect mag vorhanden sein, während chrome.runtime.PlatformOs fehlt. Funktionssignaturen geben womöglich Objekte statt undefined zurück. Jede Abweichung ist ein positives Signal.
3. Inkonsistenzen der Permissions API
Echte Browser liefern kohärente Ergebnisse, wenn sie über die Permissions API abgefragt werden. navigator.permissions.query({name: 'notifications'}) sollte 'default' zurückgeben, wenn der Nutzer die Berechtigung weder explizit erteilt noch verweigert hat.
Headless Chrome gibt standardmäßig 'denied' zurück, weil es keine UI gibt, in der ein Berechtigungsdialog erscheinen könnte. Automatisierungs-Frameworks patchen das — aber oft falsch. Ein häufiges Verräterzeichen: Die Abfrage einer ungewöhnlichen Berechtigung wie 'clipboard-read' liefert ein Ergebnis, das nicht dem entspricht, was echtes Chrome in der aktuellen Version tut.
4. Plugin- und MIME-Type-Arrays
navigator.plugins liefert in einem echten Browser ein PluginArray mit Einträgen wie dem PDF-Viewer und dem Chromium-PDF-Plugin. Im Headless-Modus ist dieses Array leer.
Stealth-Plugins fügen Fake-Einträge hinzu, aber die Einträge haben häufig falsche Eigenschaften — fehlendes length, falsche Beschreibungsfelder oder Plugin-Objekte, die sich beim Test mit instanceof nicht wie Plugin-Instanzen verhalten.
5. Sprach- und Locale-Mismatches
navigator.language und navigator.languages sollten zum Accept-Language-HTTP-Header passen. Sie sollten außerdem konsistent mit der Zeitzone sein, die Intl.DateTimeFormat().resolvedOptions().timeZone meldet.
Ein Bot, der behauptet, in Kyjiw zu sein (Zeitzone Europe/Kyiv), aber Accept-Language: en-US,en;q=0.9 sendet und navigator.language === 'en-US' meldet, ist möglich, aber statistisch ungewöhnlich. Kombiniert mit einer IP aus einem Rechenzentrum in Frankfurt wird das Bild klar.
6. WebGL-Renderer-Strings
WebGL2RenderingContext.getParameter(WebGLDebugRendererInfo.UNMASKED_RENDERER_WEBGL) liefert Hersteller und Modell der GPU. Auf einer echten Maschine könnte das sein:
ANGLE (Intel, Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0)
Headless Chrome in einem Container liefert oft:
ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero)), SwiftShader driver)
Jeder GPU-String, der SwiftShader oder Subzero enthält, ist ein starkes Bot-Signal.
7. Bildschirm- und Viewport-Anomalien
Ein echtes Browserfenster hat window.outerWidth und window.outerHeight, die das Browser-Chrome berücksichtigen — Adressleiste, Tabs, Lesezeichenleiste. Die Differenz zwischen äußeren und inneren Dimensionen beträgt vertikal typischerweise 80–140 Pixel.
Headless-Browser haben oft outerHeight === innerHeight, weil es kein Chrome anzuzeigen gibt. Puppeteer mit headless: false behebt das, aber viele Automatisierungs-Setups laufen weiterhin mit gleichen Dimensionen.
8. Font-Enumeration
Echte Nutzer haben Hunderte installierter Schriften, variierend nach OS, Sprache und installierten Anwendungen. Windows 11 mit installiertem Office hat 500+ Schriften. Automatisierungs-Container auf Alpine Linux haben 30.
Font-Enumeration über document.fonts.check() oder Offscreen-Canvas-Rendering deckt das sofort auf. Ein Browser, der sich als Windows-11-Chrome ausgibt, aber nur 30 Schriften exponiert, ist mit an Sicherheit grenzender Wahrscheinlichkeit Automatisierung.
9. Entropie der Mausbewegung
Echte Nutzer bewegen Mäuse mit Jitter. Kurven sind nichtlinear. Die Geschwindigkeit variiert. Zwischen zwei Klicks liegen typischerweise Hunderte mousemove-Events.
Automatisierungs-Frameworks synthetisieren Bewegung — entweder mit geraden Linien zu Koordinaten oder mit programmierten Kurven, denen das Mikrozittern einer menschlichen Hand fehlt. Selbst Bezier-Kurven-Simulationen haben tendenziell verdächtig glatte Beschleunigungsprofile.
Bot-Erkennungssysteme sammeln Bewegungs-Traces und scoren sie gegen ein Modell menschlicher Bewegung. Traces mit zu konsistenter Geschwindigkeit, zu sauberen Kurven oder fehlenden Mikro-Events werden markiert.
10. Timing-Signaturen
Automatisierungs-Frameworks führen JavaScript mit anderen Timing-Charakteristiken aus als nutzergesteuerte Browser. Das performance.now()-Timing zwischen Events, insbesondere zwischen pointerdown und pointerup, folgt einer anderen Verteilung.
Echte Klick-Haltezeiten liegen bei 50–150ms mit hoher Varianz. Puppeteers Standard-Klick-Haltezeit ist ein fester Wert — oft 30ms oder 100ms — mit nahezu keiner Varianz. Hundert Klicks mit identischen Haltezeiten sind der endgültige Beleg für Automatisierung.
11. TLS-Fingerprint-Mismatch
Dieses Signal umgeht JavaScript vollständig. Wenn Puppeteer sich verbindet, nutzt es Chromiums TLS-Stack — genau wie ein echtes Chrome. Aber undici- oder axios-basierte Scraper, die sich per User-Agent-Spoofing als Chrome ausgeben, nutzen den TLS-Stack von Node.js, der eine deutlich andere Client-Hello-Signatur (JA4-Hash) hat.
Ein Request, der User-Agent: Chrome/124.0.6367.60 behauptet, aber einen Node.js-TLS-Fingerprint präsentiert, ist die sofortige Bestätigung eines Nicht-Browser-Clients.
12. Leckage des CDP-Protokolls
Puppeteer und Playwright kommunizieren mit Chrome über das Chrome DevTools Protocol. In manchen Konfigurationen hinterlässt dieses Protokoll erkennbare Artefakte — zusätzliche Eigenschaften am Runtime-Objekt, veränderte Error.stack-Formate oder spezifische console.debug-Ausgaben, die nur erscheinen, wenn ein CDP-Client verbunden ist.
Insbesondere der Befehl Runtime.enable verändert, wie Stack-Traces gerendert werden. Bots, die nie Fehler auslösen, vermeiden dieses Signal, aber jeder Automatisierungs-Flow, der auf eine Exception trifft (und die meisten tun es), hinterlässt Fingerprints.
Diese Signale schichten
Kein einzelnes Signal ist ein Beweis für Automatisierung. Ein echter Nutzer mit einem ungewöhnlichen GPU-Treiber kann einen seltsamen WebGL-String liefern. Ein datenschutzbewusster Nutzer hat vielleicht navigator.plugins modifiziert.
Die Stärke moderner Erkennung kommt aus dem Kombinieren von Signalen — 12 schwache Positive sind stärker als ein starkes, weil ein echter Nutzer selten mehr als 2–3 Anomalien gleichzeitig auslöst.
Erkennungssysteme weisen jedem Signal ein Gewicht zu und berechnen einen Score. Oberhalb des Schwellenwerts wird der Besucher als Automatisierung behandelt. Der exakte Schwellenwert und die Gewichte sind die Teile, die am schwersten richtig hinzubekommen sind — zu strikt, und legitime Nutzer werden blockiert; zu nachsichtig, und ausgefeilte Bots schlüpfen durch.
Die Signale selbst sind der einfache Teil. Nahezu jedes Automatisierungs-Framework leakt alle 12. Das eigentliche Engineering besteht darin zu entscheiden, welche Kombinationen zählen, welche False Positives sind und wie das Modell zu aktualisieren ist, wenn Automatisierungs-Frameworks sich anpassen.