nodriver-Test: Ein winziger CDP-Driver, der unter Python 3.14 nicht importiert

Zuletzt aktualisiert am August 18, 2026
nodriver-Test: Ein winziger CDP-Driver, der unter Python 3.14 nicht importiert
KI-Zusammenfassung
nodriver ist eine Python-Browser-Automatisierungsbibliothek von ultrafunkamsterdam, dem Entwickler hinter undetected-chromedriver, und wird als dessen Nachfolger vermarktet. Der architektonische Bruch liegt ganz bewusst bei Selenium und chromedriver: nodriver spricht über das Chrome DevTools Protocol (CDP) direkt mit Chromium, ohne dass ein WebDriver-Binary im Spiel ist, und die API ist asynchron. Playwright und Puppeteer sind eine andere Vergleichsklasse. Auch sie steuern Browser protokollbasiert und sind keine Ableger von WebDriver; die relevanten Unterschiede liegen bei API-Design, Packaging, Browser-Bereitstellung und Kompatibilitätsstrategie. Dieser Test betrachtet nodriver 0.50.3 als Paketbestand, statt es gegen echte Schutzmechanismen zu prüfen.

nodriver ist eine Python-Browser-Automatisierungsbibliothek von ultrafunkamsterdam, dem Entwickler hinter undetected-chromedriver, und wird als dessen Nachfolger vermarktet. Der architektonische Bruch liegt ganz bewusst bei Selenium und chromedriver: nodriver spricht über das Chrome DevTools Protocol (CDP) direkt mit Chromium, ohne dass ein WebDriver-Binary im Spiel ist, und die API ist asynchron. Playwright und Puppeteer sind eine andere Vergleichsklasse. Auch sie steuern Browser protokollbasiert und sind keine Ableger von WebDriver; die relevanten Unterschiede liegen bei API-Design, Packaging, Browser-Bereitstellung und Kompatibilitätsstrategie.

Dieser Test betrachtet nodriver 0.50.3 als Paketbestand, statt es gegen echte Schutzmechanismen zu prüfen. Ich habe das installierte Paket, die Imports, die API-Oberfläche, den Speicherbedarf auf der Platte und die Lizenz untersucht und Browser nur gegen Seiten gestartet, die auf 127.0.0.1 bereitgestellt wurden. Es kam kein Live-Ziel, kein Anti-Bot-Dienst und kein CAPTCHA zum Einsatz. Die Ergebnisse beschreiben Paketverhalten und die standardmäßige Browser-Identifikation; sie belegen keine Wirksamkeit bei der Tarnung.

Drei Dinge fielen innerhalb dieses Rahmens besonders auf. Erstens importiert die Bibliothek unter Python 3.14 überhaupt nicht – ein einzelnes falsches Byte legt das gesamte Paket lahm, bevor man überhaupt etwas aufrufen kann. Zweitens ist sie für einen Driver dieser Kategorie erstaunlich schlank: drei deklarierte Abhängigkeiten plus ein transitives Paket, und in der gemessenen Umgebung rund 17 MB. Drittens unterscheidet sich nodriver auf meiner Testseite sichtbar nur in genau einem Boolean von Standard-Playwright und Standard-Puppeteer – und selbst die Headless-User-Agent-Zeichenkette sagt bei allen drei weiterhin HeadlessChrome. Die restlichen Überraschungen stecken in der Lizenz.

Der Import-Fehler unter Python 3.14

Am wichtigsten ist zuerst der Fehler, der dir als Erstes ins Gesicht springt, weil er noch vor deinem eigenen Code auftritt. Unter Python 3.14 schlägt ein schlichtes import nodriver komplett fehl:

File ".../nodriver/cdp/network.py", line 1345
    #: JSON (±Inf).
             ^
SyntaxError: Non-UTF-8 code starting with '\xb1' on line 1345, but no encoding declared; see PEP 263

Die betroffene Datei ist das automatisch erzeugte cdp/network.py (im Header steht # DO NOT EDIT THIS FILE!). Darin steckt genau ein Byte außerhalb von UTF-8, 0xb1, das für das ± in dem Kommentar #: JSON (±Inf). steht; eine Quelltext-Encoding-Deklaration fehlt. Das Modul wird über nodriver/__init__cdp/__init__network geladen, daher bricht der Parse-Fehler den Import ab. Ein Paket-Scan zeigte keine weitere Quelltextdatei ohne UTF-8-Kodierung.

Die Versionsgrenze muss sauber formuliert werden. Python 3.14.2 lehnt die Datei ab, während Python 3.12.13 sie ungepatcht importiert. Die installierten network.py-Dateien sind in diesen Umgebungen bytegleich (SHA-256 ef755f41800d4efb593736f8b55b331bba68eec373ed62ece433e66e4b491cd6), also erklärt sich das Ergebnis nicht durch unterschiedliche Quelldateien. Dieser Test hat nicht isoliert, welche konkrete Änderung am CPython-Tokenizer dafür verantwortlich ist, und Python 3.13 wurde nicht getestet. Er berichtet die beiden gemessenen Interpreter-Ergebnisse, statt zu behaupten, dass alle früheren Python-Versionen sich wie 3.12 verhalten.

Aus diesen beiden gemessenen Endpunkten folgt nichts über Python 3.13.

Das ist eine Reproduktion, keine Entdeckung. Derselbe Python-3.14-Traceback ist in nodriver Issue #35 dokumentiert, mit einem vorgeschlagenen Fix in Pull Request #36. Version 0.50.3 enthält das Byte weiterhin. Die PyPI-Metadaten führen Klassifikationen nur bis Python 3.13 und beanspruchen keine Unterstützung für 3.14.

Der Workaround ist so klein wie der Fehler: Nutz eine getestete Interpreter-Version oder kodier genau diese eine Datei neu nach UTF-8, wie in Pull Request #36 vorgeschlagen. Nach dieser Neukodierung importiert und introspektiert das Paket unter 3.14 ohne weiteres Hindernis. Der saubere Lauf unter 3.12.13 unten belegt einen ungepatchten Pfad; Python 3.13 wurde in diesem Test nicht geprüft.

Unter Python 3.12.13 gelingt import nodriver ohne Patch. Alle später in diesem Test genannten Browserwerte stammen aus genau dieser Installation. Die Zahlen zu Fußabdruck und Importzeit wurden mit der um ein Byte neu kodierten Kopie unter 3.14 ermittelt und entsprechend gekennzeichnet. Python 3.13 bleibt hier ungetestet; ein einzelnes 3.12-Ergebnis deckt nicht den gesamten früheren Bereich ab.

Wenn du moderne Interpreter standardmäßig einsetzt – und viele Teams wechseln schnell auf eine neue Python-Version – ist das eine echte, wenn auch leicht behebbarere Hürde. Es lohnt sich, das vorher zu wissen, damit du keinen Nachmittag an einem SyntaxError in einer Datei verlierst, die du nie selbst geschrieben hast.

Was nodriver unter der Haube wirklich ist

Die Ein-Satz-Beschreibung („CDP-nativ, kein WebDriver“) klingt erst nach Marketing, bis man sieht, was im Wheel enthalten ist. nodriver bringt seinen vollständigen Satz an DevTools-Protocol-Bindings mit: Das Paket nodriver.cdp enthält 57 Protokoll-Domain-Module – accessibility, dom, network, page, fetch, runtime, target, storage, input, emulation und weitere. Diese Modulanzahl ist der technische Kern hinter dem Versprechen „direktes CDP, kein Selenium“. Statt ein chromedriver-Executable anzusprechen, das WebDriver spricht und übersetzt, erzeugt nodriver Python-Objekte für die CDP-Domains und kommuniziert selbst per WebSocket mit dem Protokoll. cdp/network.py – die Datei mit dem fehlerhaften Byte – ist eines dieser 57 automatisch generierten Module. Deshalb steckt der Bruch in generiertem Code, den niemand manuell pflegt.

Auf dieser Protokollschicht sitzt ein deutlich angenehmeres Objektmodell. Das Tab-Objekt stellt 62 öffentliche Methoden bereit, und die Suchmöglichkeiten sind breiter als bei vielen anderen Tools: Textsuche über find() und find_all(), CSS über select() und select_all(), sowie ein nativer xpath()-Einstiegspunkt. XPath, CSS und Textsuche auf demselben Objekt sind tatsächlich praktisch; andere Bibliotheken zwingen einen dafür mitunter zu evaluate(). Der Config-Konstruktor bietet user_data_dir, headless, browser_executable_path, browser_args, sandbox, lang (Standard 'en-US'), host, port, expert sowie **kwargs. Bei dieser API-Inventur erzeugte Config(headless=True) 16 Chromium-Startflags, darunter --no-first-run, --no-default-browser-check, --remote-allow-origins=* und --homepage=about:blank. In diesem Introspektionsschritt habe ich start() nicht aufgerufen. Die Browser-Tests unten waren ein separater Lauf.

System diagram: Direct CDP Control Path

Die Bibliothek bringt durchaus eine API mit Anti-Detection-Ausrichtung mit – Methoden, deren Existenz ich bestätigt habe, deren Verhalten ich aber nicht gegen irgendein Ziel getestet habe. Ich erwähne das einmal und gehe dann weiter, weil der Effekt gegenüber einem realen Dienst genau das ist, was ich bewusst nicht geprüft habe. Neutral formuliert: nodriver wirkt bei der Benennung zurückhaltender als manche Stealth-Branded-Konkurrenten. Die Geschichte ist architektonisch – CDP-nativ, frisches Profil pro Lauf – statt ein Brett aus detect_and_bypass-artigen Methodennamen. Das ist eine Beobachtung zum API-Design, kein Wirkungsversprechen.

Diese Zählwerte stammen aus dem Import des Pakets und aus normaler Python-Introspektion – inspect, Modul-Walking, Attributzählung. Keine Website wurde berührt. Wer das nachbauen will, bekommt die Zahlen direkt aus dem Paket; das ist keine Bauchgefühl-Messung.

Was es über sich selbst verrät

Measured results chart: Browser disclosure fields in the tested stacks

Eine Frage lässt sich beantworten, ohne je eine echte Abwehr anzufassen: Was gibt ein von nodriver gesteuerter Browser über sich selbst an die Seite weiter, die er öffnet? Ich habe eine Seite gebaut, die die offensichtlichen Dinge ausliest – navigator.webdriver, den User-Agent, Plattform, Sprachen, Plugin- und Hardware-Zahlen, die Form von window.chrome, die Aussagen der Permissions API, Fenster- und Bildschirmgeometrie – und sie auf 127.0.0.1 bereitgestellt. Dann habe ich vier Stacks darauf angesetzt: nodriver, Botasaurus sowie Standard-Playwright und Standard-Puppeteer als Kontrollen. Alle vier steuerten exakt denselben Chrome-Build (Chrome for Testing 151.0.7922.10), also stammt jede Abweichung von der Bibliothek, nicht vom Browser. Headless und headed, jeweils drei Läufe. Jeder Wert unten blieb über alle drei Läufe gleich.

StackModusnavigator.webdriverUser-Agent-Tokennavigator.languages
nodriver 0.50.3headlessfalseHeadlessChrome/151.0.0.0["en-US"]
nodriver 0.50.3headedfalseChrome/151.0.0.0["en-US"]
Botasaurus 4.0.92headless / headedfalseHeadlessChrome/151 / Chrome/151["en-US"]
Playwright 1.56.0headless / headedtrueHeadlessChrome/151 / Chrome/151["en-US","en"]
Puppeteer 24.16.0headless / headedtrueHeadlessChrome/151 / Chrome/151["en-US"]

Der klarste Unterschied ist genau dieser eine Boolean. Unter diesen Standard-Startkonfigurationen meldet nodriver navigator.webdriver als false, während Standard-Playwright und Standard-Puppeteer in headless und headed jeweils true zurückgeben. Der Test belegt einen Unterschied auf Konfigurationsebene; er beweist nicht, dass dieser Wert allein aus dem Fehlen eines WebDriver-Binaries folgt.

In allen vier Stacks ist die Eigenschaft weiterhin der native Getter des Browsers auf Navigator.prototypefunction get webdriver() { [native code] } – niemals eine Eigenschaft des Objekts selbst oder eine ausgetauschte Funktion. Die Eigenschaft wurde nach dem Laden nicht per JavaScript der Seite neu geschrieben. Der Test hat nicht untersucht, welcher Startparameter oder welcher Quellpfad für den Unterschied verantwortlich ist.

Das zweite Detail relativiert das Marketing. Im Headless-Modus meldet der User-Agent von nodriver weiterhin HeadlessChrome/151.0.0.0 – identisch mit Standard-Puppeteer und Standard-Playwright. Im headed-Modus wird daraus Chrome/151.0.0.0, ebenfalls vollständig identisch. Wer glaubt, eine Anti-Detection-Bibliothek maskiere standardmäßig die bekannteste Selbstkennzeichnung in der Browser-Automatisierung, irrt. Das müsste man selbst setzen.

Fast alles andere war zwischen allen vier Stacks gleich, und das ist wichtig, weil es die Geschichte enger macht – jede Eigenschaft unten sah bei nodriver, Botasaurus, Playwright und Puppeteer identisch aus:

EigenschaftWert, auf allen vier Stacks identisch
platformMacIntel
vendorGoogle Inc.
Pluginsfünf
MIME-Typenzwei
pdfViewerEnabledtrue
Logische Kernezwölf
Gemeldeter Gerätespeicher16 GB
Touchpointsnull
window.chromevorhanden, mit app/csi/loadTimes und ohne runtime
WebGL-Renderer-Stringauf allen vier identisch

Die bekannte Stelle, an der sich Permissions API und Notification.permission widersprechen, trat nirgends auf – alle vier meldeten übereinstimmend default und prompt. Ich habe außerdem document und window auf die alten cdc_-Überreste durchsucht, die man von früheren WebDriver-Stacks kannte: in allen vier Fällen leer.

Die einzige Stelle, an der nodriver eher wie ein nackter Automatisierungsbrowser als wie ein Kontrollwerkzeug wirkt, ist die Fenstergeometrie. Headless nodriver meldet outerWidth/outerHeight von 0×0 auf einem 800×600-Bildschirm; headless Playwright meldet 1280×720, weil es von sich aus ein Viewport setzt. Puppeteer liegt wie nodriver bei 0×0. Das ist ein Unterschied in der Standardkonfiguration, keine Fähigkeitsgrenze, und Sie können ihn ändern.

Noch etwas, das du vor dem Einsatz wissen solltest: nodriver bringt keinen Browser mit. Standardmäßig verwendet es also den Chrome, der bereits auf deinem System vorhanden ist. Auf meinem System wurde automatisch /Applications/Google Chrome.app erkannt – Chrome 150.0.7871.187 – und genau diese Version war auch im ausgelieferten User-Agent zu sehen, nicht eine fest verdrahtete. Welche Browser-Version deine Instanz preisgibt, ist also schlicht die Version, die bei dir installiert ist.

Nennen wir es klar: Das hier ist ein Protokoll dessen, was ein automatisierter Stack von sich preisgibt, wenn niemand ihn zum Verstecken auffordert – nützlich auf der Verteidigungsseite, nützlich auch, wenn du wissen willst, was deine eigenen Tools nach außen senden. Es ist kein Maß dafür, ob das irgendeinem bestimmten Dienst etwas ausmacht. Das habe ich nicht getestet, und keine Zeile oben sollte so gelesen werden, als würde sie ein Ergebnis behaupten.

Kann es Inhalte tatsächlich von einer Seite ziehen?

Sich zu erkennen zu geben ist das eine; das richtige HTML zurückzubringen ist die eigentliche Aufgabe. Ich habe nodriver gegen denselben Fixture mit drei Inhaltstypen laufen lassen, den der Rest dieses Benchmark-Repos verwendet, damit die Zahlen zu allen anderen hier gemessenen Tools passen. Die Seite enthält drei Dinge: A, einen statischen Link, dessen Marker wörtlich in den ausgelieferten Bytes steckt; B, einen Node, der während des Parsens durch ein Inline-Skript erzeugt wird, dessen Marker und URL aus Fragmenten zusammengesetzt werden, sodass nur das Ausführen von JavaScript sie sichtbar macht; und C, einen Node, der 800 ms nach dem Load-Event eingefügt wird und auf dieselbe Weise zusammengesetzt ist. Klasse C ist der adversarielle Fall – eine Abfrage zum Load-Event kann sie nicht sehen.

StackStandardabrufMit explizitem Warten
nodriver 0.50.32 von 3 (A + B, verpasst C)3 von 3
Botasaurus 4.0.922 von 33 von 3
Playwright 1.56.02 von 33 von 3
Puppeteer 24.16.02 von 33 von 3

nodriver landet genau dort, wo auch die Schwergewichte landen. browser.get() gefolgt von tab.get_content() ist ein Schnappschuss zum Zeitpunkt des Ladens: JavaScript wird korrekt ausgeführt – Klasse B beweist das, weil es in den ausgelieferten Bytes gar nicht vorkommt – aber alles, was erst nach dem Laden eingefügt wird, wird verpasst. Mit tab.select("#delayed-injected") erhalten Sie alle drei. Derselbe Fallstrick, dieselbe Lösung, wie bei Playwright und Puppeteer. Über drei Wiederholungen und drei vollständige Läufe der gesamten Suite stabil, ohne Ausreißer.

Ich habe das Einfügeintervall variiert, um zu sehen, ab wann der Standardabruf aufgibt. nodriver sieht Klasse C nicht mehr, sobald die Einfügung 100 ms oder später nach dem Load erfolgt – dieselbe Grenze wie bei beiden Standard-Kontrollen. (Botasaurus ist hier die Ausnahme und der wirklich interessante Unterschied zwischen den beiden Anti-Detect-Bibliotheken: Sein get() blockiert standardmäßig bis zum vollständigen Seitenaufbau, deshalb erfasst der Standardabruf auch Einfügungen bei 300 ms noch. Dafür zahlt es etwa 250 ms pro Navigation.)

Das Warten selbst hat eine Eigenart, die man budgetieren sollte. In diesem Sweep arbeitete nodriver tab.select() in groben Polling-Buckets statt die Einfügedauer eng zu verfolgen:

Klasse-C-Verzögerung0 ms100 ms400 ms800 ms1500 ms
nodriver select()124–152 ms1132–11411128–11292132–21772138–2150
Puppeteer waitForSelector113–129 ms203–216512–516911–9191608–1611

Bei diesem Fixture führte eine 100-ms-Einfügung zu rund 1,1 Sekunden für select(). Die installierte Schleife führt await self und danach await self.sleep(0.5) nach einem Fehlschlag aus; der gemessene kombinierte Zyklus lag hier bei etwa einer Sekunde, aber await self ist nicht als universeller Sleep mit fester Dauer belegt. Jeder getestete verzögerte Node wurde gefunden. Puppeteers Wartefunktion folgte diesen Verzögerungen enger. Viele serielle Warteoperationen könnten den Unterschied verstärken, auch wenn dieser Test keine Produktionsseite mit dreißig Selektoren gemessen hat.

Das Starten ist der andere Ort, an dem die „async und schlank“-Erzählung auf die Realität trifft. Beim Hochfahren des Browsers lag nodriver in derselben Größenordnung wie Botasaurus und Puppeteer und deutlich hinter Playwright:

StackBrowser-Start über mehrere Läufe
nodriver 0.50.3910–1583 ms
Botasaurus 4.0.92986–1151 ms
Puppeteer 24.16.0969–1008 ms
Playwright 1.56.0282–365 ms

Im laufenden Betrieb ist nodriver beim Navigieren und Auslesen der schnellste der vier mit 119–129 ms. Beim Lenkrad leicht, beim Anlassen gewöhnlich.

Installation und Footprint: der wirklich gute Teil

Hier verdient nodriver das Etikett „fokussiert“, und das ist ein harmloser, überprüfbarer Installationsfakt – nicht etwa etwas über Scraping. So sieht ein sauberes pip install nodriver bei der Auflösung aus:

Installationsfaktnodriver 0.50.3
Deklarierte direkte Runtime-Abhängigkeitendreiwebsockets, mss, deprecated
Aufgelöste transitive Abhängigkeitwrapt (über deprecated)
Gemessene Gesamtgröße von site-packagesetwa 17,2 MB über 6 dist-info-Verzeichnisse, inklusive des Umgebungspakets pip
Anteil von nodriver selbst3,7 MB
numpy / lxmlkeines von beiden
Browser-Binary zum Installationszeitpunktkeines heruntergeladen

Für eine Kategorie, die normalerweise einen ganzen Rendering-Stack mitbringt, ist das angenehm schlank.

Die Unterscheidung zwischen direkten und transitiven Paketen ist für die Pflege wichtig. nodriver verlangt in seinen Metadaten mss, websockets und deprecated; wrapt kommt dazu, weil deprecated es benötigt. Das sechste dist-info-Verzeichnis in der 17,2-MB-Umgebung ist pip, das ohnehin schon Teil dieser virtuellen Umgebung war. Daher beschreibt „17,2 MB über sechs Distributionen“ die gemessene Umgebung, während „vier hinzugefügte Runtime-Pakete“ beschreibt, was die Installation aufgelöst hat. Das sind zusammenhängende, aber nicht austauschbare Zählungen.

Der Vergleich, der diese Zahl greifbar macht: Auf derselben Maschine bringt das Schwester-Framework Botasaurus 122,3 MB über 44 Pakete auf die Waage – also ungefähr den siebenfachen Speicherbedarf. Das ist der Unterschied zwischen einem fokussierten CDP-Driver und einem Framework mit allem Drum und Dran, und er schneidet in beide Richtungen. nodriver liefert Ihnen einen dünnen, gut lesbaren Abhängigkeitsbaum, den man tatsächlich auditieren kann; Botasaurus gibt mehr out of the box und verlangt dafür mehr Speicher und eine größere Angriffsfläche an Abhängigkeiten. Keines ist abstrakt „besser“ – es hängt davon ab, ob Sie einen Driver oder ein Framework wollen –, aber wenn Ihnen ein kleiner, gut prüfbarer Installationsumfang wichtig ist, ist nodriver für das, was es tut, ungewöhnlich sauber.

Der kleine Plattenbedarf bedeutet nicht einen kleinen Import. In der um ein Byte gepatchten Python-3.14-Kopie dauerte import nodriver etwa 158 ms (Median über frische Subprozess-Imports, ungefähr 151–199 ms). Der ungepatchte 3.14-Import schlägt fehl, daher wird dafür keine Zeit angegeben. Die 57 CDP-Domain-Module werden sofort geladen, und der residente Speicher nach dem Import lag bei 31,5–31,9 MB, bevor überhaupt ein Chrome-Prozess existierte. Ein gestarteter Browser würde deutlich mehr hinzufügen und war in dieser Speicher-Messung nicht enthalten.

Zwei Einschränkungen hängen an diesen Zahlen. Sie stammen von einem einzigen Rechner – macOS arm64 – und die Werte für Fußabdruck und Importzeit wurden unter Python 3.14 mit der um ein Byte gepatchten Kopie erhoben, weil das ungepatchte Paket unter diesem Interpreter gar nicht importiert. Auf einer unterstützten Python-Version braucht man den Patch nicht: Ich habe einen sauberen ungepatchten Import unter 3.12.13 bestätigt, und genau daher stammen alle Browsermessungen. Außerdem wird zur Laufzeit weiterhin ein echtes Chrome-, Chromium-, Edge- oder Brave-Binary benötigt – nodriver steuert einen vorhandenen Browser, es bringt keinen mit, also liegen diese Installationskosten außerhalb der 17 MB; und wie der Abschnitt zur Browser-Identifikation gezeigt hat, ist die Version des Browsers, den deine Maschine installiert hat, auch die Version, die dein Traffic nach außen preisgibt.

Die Lizenz ist die eigentliche Adoptionsentscheidung

System diagram: The license is the real adoption decision

Die meisten Tests eines kostenlosen Tools behandeln „ist Open Source“ als Ende der Lizenzfrage. Bei nodriver ist das erst der Anfang, denn die Lizenz ist AGPL-3.0 – bestätigt sowohl in LICENSE.txt im Wheel als auch in der spdx_id des Repos. Das ist eine starke Form von Network Copyleft und eine deutlich andere Verpflichtung als das, was viele Nachbarn verlangen.

Die meisten Vergleichswerkzeuge, gegen die nodriver antritt, sind permissiv lizenziert, und genau das macht den Unterschied greifbar:

ToolLizenzWenn Sie eine modifizierte Kopie als Netzwerkdienst betreiben
nodriverAGPL-3.0Abschnitt 13 kann verlangen, dass der Betreiber den entsprechenden Quellcode seiner modifizierten Version den Remote-Nutzern anbietet
PlaywrightApache-2.0keine entsprechende Network-Copyleft-Klausel
PuppeteerApache-2.0wie oben
BotasaurusMITwie oben

Der Umfang ist wichtig. AGPL Abschnitt 13 betrifft eine modifizierte Version des abgedeckten Programms, die für entfernte Netzwerkinteraktion genutzt wird. Dieser Test entscheidet nicht, ob separater umliegender Service-Code Teil des abgedeckten Werks ist, und klärt auch keine internen oder konzernbezogenen Grenzfälle. Wenn ein gehostetes Produkt nodriver verändert, sollten Sie den Lizenztext und die Architektur mit juristischer Unterstützung prüfen. Das ist ein technischer Adoptionshinweis, keine Rechtsberatung.

Ich bewerte nicht, ob AGPL gut oder schlecht ist – Copyleft ist eine legitime Wahl, und viele ernsthafte Projekte verwenden es. Ich weise nur darauf hin, dass „nodriver ist frei und Open Source“ zwar wahr, aber unvollständig ist. Die Verpflichtung ist real, sie unterscheidet sich von der permissiven Standardlage in diesem Ökosystem und gehört in die Entscheidung hinein, statt in „frei“ aufzugehen. (PyPI führt übrigens gar keinen Lizenz-Classifier; der AGPL-Text steht im Wheel und die SPDX-ID im Repo, also verlass dich nicht darauf, dass der Paketindex dir das automatisch zeigt.)

Die Metadaten, mit Datum

Zeitpunktbezogene Repository- und Paketdaten, direkt aus GitHub API und PyPI:

FaktWert am 14. Juli 2026
Stars4.511
Forks422
Offene Issues14
ErstelltFebruar 2024
Zuletzt gepushtMai 2026
Neueste PyPI-Version0.50.3
Wheelreines Python, py3-none-any
requires-python>=3.9
Python-Klassifizierungen3.7–3.13

Die sichtbaren Wartungssignale sind gemischt: Das Repository wurde im Mai 2026 gepusht, während das zuletzt gemessene Paket noch das Python-3.14-Importproblem enthielt und der vorgeschlagene Fix zum Forschungszeitpunkt noch nicht veröffentlicht war. Sterne- und Issue-Zahlen entscheiden nicht darüber, ob dieses Tempo Ihrem Wartungsanspruch genügt.

Vor- und Nachteile

Vorteile:

  • Schlanke, gut nachvollziehbare Installation: drei deklarierte Abhängigkeiten plus transitives wrapt, rund 17,2 MB über sechs dist-info-Verzeichnisse in der gemessenen Umgebung (eins davon ist pip), kein numpy/lxml und kein Browser-Download bei der Installation.
  • Echte CDP-Native: bringt 57 eigene DevTools-Protocol-Module mit und spricht das Protokoll direkt, ohne chromedriver/Selenium-Binary im Ablauf.
  • Breite Element-Suche auf Tab (62 öffentliche Methoden) mit erstklassigem XPath-, CSS- und Text-Support – kein Ausweichen auf rohes evaluate() für XPath.
  • Asynchron konzipiert, frisches Profil pro Lauf und eine saubere Config-Oberfläche für die üblichen Schalter (Headless, Executable-Pfad, Args, Sprache, Ports).
  • Holt Inhalte von einer per JavaScript aufgebauten Seite so zuverlässig wie die Schwergewichte: 2 von 3 Inhaltstypen beim Standardabruf, 3 von 3 mit explizitem Warten – identisch zu Standard-Playwright und Standard-Puppeteer auf demselben Fixture, stabil über drei Läufe. Das Navigieren und Auslesen war mit 119–129 ms am schnellsten von allen vieren.
  • Meldet navigator.webdriver standardmäßig als false, wo beide Standard-Kontrollen true zurückgeben, ohne die Eigenschaft zu patchen – der Descriptor bleibt der native Getter des Browsers.
  • Eine klare architektonische Identität als CDP-native Nachfolge von undetected-chromedriver.

Nachteile:

  • Importiert unter Python 3.14 nicht out of the box – eine einzelne nicht-UTF-8-kodierte Datei (cdp/network.py) wirft beim Import einen SyntaxError. Reproduziert gegen das offene Issue #35, in 0.50.3 noch nicht behoben. Also Python ≤3.13 verwenden (unter 3.12.13 sauber bestätigt) oder die Datei neu kodieren.
  • AGPL-3.0 ist eine reale Adoptionsfrage für alle, die eine modifizierte Kopie als Netzwerkdienst betreiben – strenger als die Apache-/MIT-Pendants.
  • Der kleine Plattenbedarf bedeutet keinen kleinen Import: rund 158 ms Kaltstart und etwa 31,5 MB resident, bevor irgendein Browser existiert, weil alle 57 CDP-Module sofort geladen werden.
  • tab.select() pollt mit halber Sekunde Rückoff, daher werden kurze Wartezeiten aufgerundet – 100 ms kosten ungefähr 1,1 s, wo Puppeteers Waiter etwa 210 ms kostet. Immer korrekt, aber über viele kleine Wartezeiten summiert sich das.
  • Headless-Läufe kündigen sich standardmäßig weiterhin mit HeadlessChrome im User-Agent an, genau wie die Standard-Kontrollen; an der offensichtlichsten Selbstkennzeichnung wird nichts versteckt.
  • Zur Laufzeit wird weiterhin ein echtes Chrome-/Chromium-/Edge-/Brave-Binary benötigt; der kleine pip-Install ist nur die halbe Abhängigkeitsgeschichte, und welche Chrome-Version deine Maschine hat, verrät auch die Version, die dein Traffic nach außen zeigt.
  • Die Wirksamkeit gegen Anti-Bot-Systeme ist hier nicht belegt – das gesamte Stealth-Versprechen wurde absichtlich nicht getestet.

Was ich nicht getestet habe und daher nicht beurteilen kann: Speicher pro Tab, CDP-Round-Trip-Latenz, Profilverwaltung, Durchsatz im großen Maßstab, Linux oder Windows, konkret Python 3.13 und – der große Punkt – die reale Anti-Bot-Wirkung gegen einen Live-Dienst. Jeder Browser in diesem Test sprach ausschließlich mit einem Fixture auf 127.0.0.1. Alle Zahlen stammen von einer Maschine (macOS arm64); die Browsermessungen sind Python 3.12.13 ohne Patch, und die älteren Werte für Fußabdruck und Importzeit stammen aus Python 3.14 mit der um ein Byte gepatchten Kopie.

Ich habe auch keine Browser-Upgrade-Matrix gefahren, daher bleibt die Kompatibilität mit künftigen Chrome-Versionen eine operative Prüfung für den Anwender und nicht das Ergebnis dieses Tests.

Für wen es ist – und für wen nicht

nodriver passt, wenn Sie einen schlanken, asynchronen, CDP-nativen Driver für ein echtes Chromium wollen und bereit sind, Browser, Updates und Laufzeit selbst zu tragen. Der kleine Abhängigkeitsbaum ist leichter zu prüfen, und das CDP-first-Design eignet sich gut für Kontrolle auf Protokollebene. Die Eignung für Container ist ungetestet: Dieser Test hat Linux-Images, Browser-Installation, Shared Libraries, Sandboxing oder Prozessbereinigung nicht angefasst.

Zwei Gruppen sollten sich anderweitig umsehen. Wenn du auf Python 3.14 bist und nicht bereit bist, deinen Interpreter zu pinnen oder eine vendorte Datei zu patchen, warte auf den Fix – der Importfehler ist heute ein harter Stopp. Und wenn AGPL-3.0 für die Art und Weise, wie du ausliefern willst – etwa ein gehosteter Dienst mit privaten Änderungen – ein Problem ist, dann ist allein die Lizenz schon Grund genug, vor dem Aufbau eine permissiv lizenzierte Alternative zu prüfen. Beides ist kein Vorwurf an den Code; es sind Einschränkungen, die man lieber jetzt als erst im Compliance-Review erkennt.

Überspring es auch dann, wenn du eigentlich Daten brauchst und keinen Browser, den du per Hand steuern. nodriver gibt dir einen skriptbaren Tab und 62 Methoden; eine gerenderte Seite in saubere, strukturierte Datensätze zu verwandeln, bleibt dennoch deine Aufgabe. Das ist ein anderer Job – und genau dort kommt eine gemanagte API ins Spiel.

Alternativen und wo Thunderbit passt

Zuerst die ehrliche Einordnung: nodriver ist kostenlos, AGPL-lizenziert und selbst gehostet. Sie betreiben den Browser, Sie kümmern sich um Updates, Sie tragen die Verantwortung für Laufzeit und Ausfälle. Für Entwickler, die genau diese Kontrolle wollen, kann kein gemanagter Dienst preislich mit einer Bibliothek mithalten, die man ohnehin schon besitzt.

Innerhalb von Open Source sollte man nach Aufgabe vergleichen, nicht nach Logo. Wenn Sie echte Browser-Driver bewerten, deckt unser Vergleich von Playwright und Puppeteer die beiden naheliegenden Schwergewichte ab, neben denen nodriver steht. Scrapling ist auf der Stealth-Achse der nächste Python-Nachbar, wenn genau das Ihr Suchgrund ist. Für LLM-taugliche Ausgabe statt rohe Browsersteuerung rendert Crawl4AI Seiten und liefert Markdown zurück, und Scrapy bleibt die Referenz für große browserlose Crawls. Wenn Sie mehrere dieser Optionen parallel vergleichen, ordnet die Open-Source-Scraper-Übersicht die Kategorien nebeneinander ein.

Hinweis: Dieser Artikel wird von Thunderbit veröffentlicht. Thunderbit ist ein gemanagter Extraktionsdienst, daher ist der Vergleich aufgabenbezogen und nicht architektonisch. nodriver gibt Ihnen eine Browser-Steuerungsschicht, die Sie selbst hosten und programmieren; Thunderbit übernimmt Rendering und liefert Seiteninhalt oder schematische Datensätze als Dienst. Nutz nodriver, wenn Protokollkontrolle über den Browser und Self-Hosting Voraussetzungen sind. Erwäge einen gemanagten Extraktor, wenn das Ausgabeformat und die operative Übergabe wichtiger sind als der Besitz des Browsers. Details zu veränderlichen Endpunkten, Credits und Batch-Limits gehören auf die Preisseite, nicht in ein Bibliotheks-Benchmark.

Der Trade-off ist, wo die Arbeit lebt: nodriver lässt Browser-Management und Laufzeitpflege auf Ihrer Seite, ohne eine Servicegebühr pro Request; eine gemanagte API übernimmt genau diese Schicht und berechnet die Aufrufe.

Thunderbit für die Web-Datenextraktion testen

Fazit

Nutzen Sie nodriver, wenn Sie einen kleinen, asynchronen, CDP-nativen Chromium-Driver wollen, Ihre Python-Version geprüft haben (3.12.13 importierte hier sauber) und AGPL-3.0 für Ihr Auslieferungsmodell bewertet haben. Der aufgelöste Runtime-Graph fügte vier Pakete hinzu, die gemessene Umgebung belegte rund 17 MB, und die Bibliothek bringt 57 CDP-Domain-Module mit einer Tab-Oberfläche von 62 Methoden und nativen XPath-Support mit. In diesem Fixture lieferte sie standardmäßig 2 von 3 Inhaltstypen und mit Wartefunktion 3 von 3 – genau wie Standard-Playwright und Standard-Puppeteer.

Bewerte die Einschränkungen aber ehrlich. Unter Python 3.14 importiert es gar nicht, bis du ein nicht-UTF-8-Byte korrigierst – ein dokumentierter, noch offener Fehler, kein Rätsel, aber ein harter Stopp an dem Tag, an dem du ihn triffst. Die Lizenz ist AGPL-3.0, was für jeden, der eine modifizierte Kopie als Dienst betreibt, eine echte Entscheidung ist, keine Formalität. Die kleine Installation kauft keinen kleinen Import, weil alle CDP-Module im Vorfeld geladen werden, und das halbe-Sekunden-Polling von select() macht aus kurzen Wartezeiten jeweils etwa eine Sekunde. Bei der Frage, was der Browser standardmäßig preisgibt, ist das Bild enger als das Marketing vermuten lässt: Ein Boolean unterscheidet sich von Standard-Puppeteer, der Headless-User-Agent sagt weiterhin HeadlessChrome, und alle anderen von mir gemessenen Eigenschaften waren auf allen vier Stacks identisch. Und das gesamte Anti-Detection-Versprechen – der Grund, warum viele Menschen überhaupt auf nodriver stoßen – habe ich absichtlich nicht getestet. Ich habe die Bibliothek inventarisiert und gegen eine Seite auf meinem eigenen Rechner gefahren, statt sie gegen Live-Abwehrsysteme zu stellen, und ich sage dir das lieber offen, als dir ein Umgehungsversprechen zu verkaufen, hinter dem ich nicht stehen kann. Bei den Fragen, die ich beantworten konnte, ist nodriver ein gut gebauter, ungewöhnlich schlanker Driver mit zwei scharfen Kanten – einer Python-Versionshürde und einer Copyleft-Lizenz –, die man früh sehen sollte.

Thunderbit für die Web-Datenextraktion testen Get Started Free

FAQs

Warum schlägt import nodriver unter Python 3.14 fehl? Weil cdp/network.py ein nicht-UTF-8-kodiertes ±-Byte ohne Quelltext-Encoding-Deklaration enthält. Python 3.14.2 lehnt die Datei ab und bricht den transitiven Import ab; Python 3.12.13 importiert dieselbe byteidentische Datei ungepatcht. Dieser Test hat die genaue Interpreter-Änderung nicht isoliert und Python 3.13 nicht geprüft. Der Upstream-Pfad ist nodriver Issue #35 und Pull Request #36. Verwende eine von dir getestete Version oder kodier die Datei nach UTF-8 neu.

Was bringt „CDP-nativ, kein WebDriver“ – und was kostet die Installation? nodriver bringt 57 DevTools-Protocol-Domain-Module mit und spricht CDP über WebSocket, statt chromedriver über Selenium aufzurufen. Tab bietet 62 Methoden, darunter nativen XPath-Support. In den Paketmetadaten werden drei Runtime-Anforderungen genannt (websockets, mss, deprecated); deren Auflösung fügt wrapt hinzu. Die gemessene Umgebung nutzte etwa 17,2 MB über sechs dist-info-Verzeichnisse, inklusive pip, ohne numpy, lxml oder einen heruntergeladenen Browser-Binary. Zwei Einschränkungen: Der Import der gepatchten Kopie dauerte etwa 158 ms, weil alle 57 CDP-Module sofort geladen werden, und ein Browser aus der Chrome-Familie wird weiterhin separat benötigt.

Spielt die AGPL-3.0-Lizenz für mein Projekt eine Rolle? Das hängt davon ab, wie Sie ausliefern. AGPL-3.0 ist eine Network-Copyleft-Lizenz: Wenn Sie eine modifizierte Version von nodriver als Dienst für andere betreiben, müssen Sie diesen Nutzern den entsprechenden Quellcode anbieten. Für ein persönliches Skript oder ein internes Tool, das nie nach außen geht, ist das meist unproblematisch. Für ein gehostetes kommerzielles Produkt auf Basis eines gepatchten nodriver ist das eine echte Frage für die Compliance-Verantwortlichen – und die Lizenz ist strenger als die Apache-2.0- und MIT-Lizenzen vergleichbarer Tools.

Was verrät nodriver über sich selbst – und heißt das, dass es Cloudflare schlägt? Der erste Teil ist gemessen, der zweite nicht, und genau dieser Unterschied ist wichtig. Auf einer Seite, die ich von 127.0.0.1 aus bereitgestellt habe, und mit demselben Chrome-Build wie die Kontrollen gilt: navigator.webdriver ist false, während Standard-Playwright und Standard-Puppeteer beide true melden. Dieser Wert wird beim Browserstart gesetzt, nicht durch das Patchen der Eigenschaft – der Descriptor ist weiterhin Chromes eigener nativer Getter. Abgesehen von diesem Boolean war fast alles identisch mit den Kontrollen: derselbe Plattform-String, fünf Plugins, zwölf Kerne, 16 GB gemeldeter Gerätespeicher, dieselbe Form von window.chrome, kein Widerspruch in der Permissions API und keine cdc_-Überreste auf document oder window. Headless-Läufe melden im User-Agent weiterhin HeadlessChrome/151.0.0.0, genau wie beide Kontrollen – das wird nicht für Sie maskiert. Was all das nicht sagt, ist, ob davon irgendetwas gegen einen echten Anti-Bot-Dienst funktioniert. Ich habe nodriver nie auf eine Live-Seite gesetzt, nie einen Anti-Bot-Dienst kontaktiert und nie ein CAPTCHA angefasst; das war absichtlich außerhalb des Scopes. Die Disclosure-Tabelle oben sagt, was der Stack mitteilt. Sie sagt nichts darüber, wer zuhört oder was er damit macht.

Kann nodriver JavaScript-gerenderte Inhalte korrekt verarbeiten? Ja, mit der üblichen Einschränkung, wann Sie lesen. In einem Fixture mit drei Inhaltstypen lieferte ein Standard-browser.get() + tab.get_content() 2 von 3 – JavaScript wird korrekt ausgeführt (die synchron eingefügte Klasse existiert in den ausgelieferten Bytes gar nicht und kam trotzdem zurück), aber die Abfrage erfolgt am Load-Event und verpasst daher alles, was erst danach eingefügt wird. Mit tab.select("#delayed-injected") kamen 3 von 3 zurück. Das ist identisch zu Standard-Playwright und Standard-Puppeteer auf derselben Seite. Planen Sie einen kleinen Haken ein: select() pollt mit halber Sekunde Rückoff, sodass 100 ms Wartezeit ungefähr 1,1 Sekunden kosten.

Ke
Ke
CTO bei Thunderbit | Senior Data Scientist & ML-Experte Mit fast zehn Jahren Erfahrung in Machine Learning und Data Science ist Ke Shen Absolvent der Columbia University und ehemaliger Senior Data Scientist bei Walmart Labs. Mit tiefgreifender, von Fachkollegen anerkannter Expertise in Python, R, Java und Statistik teilt er praxiserprobte Einblicke dazu, wie sich komplexe KI-Algorithmen von der Theorie in eine produktionsreife Architektur überführen lassen.
Inhaltsverzeichnis
Thunderbit · KI-Web-Daten-Agent

Daten von jeder Seite in 1 Klick extrahieren

Vertraut von über 250.000 Nutzern
kostenloser Plan verfügbar
Von der Webseite zur Tabelle
Beschreibe einfach, was du brauchst — Thunderbits KI-Agent erfasst es und exportiert es nach Excel, Google Sheets, Airtable oder Notion. Kostenlos loslegen.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week