nodriver-review: een compacte CDP-driver die niet wil importeren op Python 3.14

Laatst bijgewerkt op August 18, 2026
nodriver-review: een compacte CDP-driver die niet wil importeren op Python 3.14
AI-samenvatting
nodriver is een Python-bibliotheek voor browserautomatisering van ultrafunkamsterdam, de maker achter undetected-chromedriver, en profileert zich als de opvolger daarvan. De architectuurbreuk zit vooral bij Selenium en chromedriver: nodriver praat rechtstreeks met Chromium via het Chrome DevTools Protocol (CDP), zonder WebDriver-binary ertussen, en de API is asynchroon. Playwright en Puppeteer vormen een andere vergelijkingsgroep. Ook zij sturen browsers aan via protocollen, niet als afstammelingen van WebDriver; de relevante verschillen zitten in API-ontwerp, packaging, browserlevering en compatibiliteitsbeleid. Deze review bekijkt nodriver 0.50.3 en test het dus niet tegen live anti-botmaatregelen.

nodriver is een Python-bibliotheek voor browserautomatisering van ultrafunkamsterdam, de maker achter undetected-chromedriver, en zet zichzelf neer als de opvolger daarvan. De echte architectuurbreuk zit vooral bij Selenium en chromedriver: nodriver praat rechtstreeks met Chromium via het Chrome DevTools Protocol (CDP), zonder WebDriver-binary ertussen, en de API is asynchroon. Playwright en Puppeteer vallen in een andere vergelijkingsgroep. Ook die sturen browsers aan via protocollen, niet als afstammelingen van WebDriver; de relevante verschillen zitten in API-ontwerp, packaging, browserlevering en compatibiliteitsbeleid.

Deze review bekijkt nodriver 0.50.3 en test het dus niet tegen live anti-botmaatregelen. Ik heb het geïnstalleerde pakket, imports, API-oppervlak, schijfgebruik en licentie geïnventariseerd, en daarna alleen browsers geopend op pagina’s die werden geserveerd vanaf 127.0.0.1. Er kwam geen live doelwit, anti-botdienst of CAPTCHA aan te pas. De resultaten gaan over pakketgedrag en wat de browser standaard prijsgeeft; ze bewijzen niets over anti-detectie-effectiviteit.

Binnen die afbakening vielen drie dingen op. Ten eerste importeert de bibliotheek op Python 3.14 helemaal niet — één verdwaalde byte breekt het hele pakket nog vóór je iets kunt aanroepen. Ten tweede is het voor deze categorie opvallend klein: drie directe afhankelijkheden plus één transitief pakket, en ongeveer 17 MB in de gemeten omgeving. Ten derde verschilt nodriver op mijn testpagina zichtbaar van stock Playwright en stock Puppeteer slechts op één boolean — en de headless user-agent vermeldt in alle drie nog steeds HeadlessChrome. De rest van de verrassingen zit in de licentie.

De importbreuk op Python 3.14

Begin met het probleem dat je het eerst raakt, omdat het optreedt voordat je eigen code draait. Op Python 3.14 faalt een gewone import nodriver meteen:

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

Het bestand dat faalt is het automatisch gegenereerde cdp/network.py (de header zegt # DO NOT EDIT THIS FILE!). Er staat één niet-UTF-8 byte in, 0xb1, voor het ±-teken in de opmerking #: JSON (±Inf)., en er ontbreekt een broncode-encoding declaration. De module wordt geladen via nodriver/__init__ → cdp/__init__ → network, dus de parsefout breekt de import af. Een scan van het pakket vond geen ander bronbestand met niet-UTF-8-inhoud.

De versiegrens vraagt wel om zorgvuldige formulering. Python 3.14.2 wijst dit bestand af, terwijl Python 3.12.13 het ongewijzigd importeert. De geĂŻnstalleerde network.py-bestanden zijn in die omgevingen byte-identiek (SHA-256 ef755f41800d4efb593736f8b55b331bba68eec373ed62ece433e66e4b491cd6), dus andere bronartefacten verklaren het verschil niet. Deze review heeft niet vastgesteld welke exacte tokenizer-wijziging in CPython verantwoordelijk is, en Python 3.13 is niet getest. Ik rapporteer de twee gemeten interpreter-resultaten en beweer niet dat elke eerdere Python zich gedraagt als 3.12.

Uit deze twee gemeten eindpunten volgt geen conclusie over Python 3.13.

Dit is een reproductie, geen ontdekking. Dezelfde Python 3.14-traceback staat in nodriver issue #35, met een voorgestelde oplossing in pull request #36. Versie 0.50.3 bevat de byte nog steeds. PyPI-metadata noemt classifiers tot en met Python 3.13 en claimt geen ondersteuning voor 3.14.

De workaround is net zo klein als de bug: gebruik een geteste interpreterversie of encodeer dat ene bestand opnieuw naar UTF-8, zoals pull request #36 voorstelt. Na die hercodering importeert en introspecteert het pakket op 3.14 zonder dat er een tweede blokkade opduikt. De schone 3.12.13-run hieronder bevestigt één ongepatchte route; Python 3.13 is in deze review niet getest.

Op Python 3.12.13 slaagt import nodriver zonder patch. Alle browsercijfers verderop in deze review komen uit die installatie. De footprint- en importtimingcijfers gebruiken de met één byte hercodede kopie op 3.14 en zijn als zodanig gelabeld. Python 3.13 blijft hier ongetest; één 3.12-resultaat dekt niet het hele eerdere bereik.

Als je standaard al met recente interpreters werkt — en veel teams stappen snel over op een nieuwe Python-versie — dan is dit een echte, al is hij eenvoudig op te lossen, blokkade. Goed om te weten, zodat je geen middag verliest aan een SyntaxError in een bestand dat je nooit zelf hebt geschreven.

Wat nodriver onder de motorkap eigenlijk is

De korte omschrijving (“CDP-native, no webdriver”) klinkt al snel als marketing, tot je kijkt wat er in de wheel zit. nodriver bundelt zijn eigen volledige set DevTools Protocol-bindings: het nodriver.cdp-pakket bevat 57 protocol-domeinmodules — accessibility, dom, network, page, fetch, runtime, target, storage, input, emulation en meer. Dat moduleaantal is de mechaniek achter de claim “direct CDP, geen Selenium”. In plaats van een chromedriver-executable aan te spreken die WebDriver praat en alles voor je vertaalt, genereert nodriver Python-objecten voor de CDP-domeinen en spreekt het zelf het protocol over een WebSocket. cdp/network.py — het bestand met de foute byte — is één van die 57 automatisch gegenereerde modules, en daarom zit de breuk in code die niemand handmatig bewerkt.

Boven op die protocollaag ligt een vriendelijker objectmodel. Het Tab-object biedt 62 publieke methoden, en het zoekoppervlak is breder dan de meeste drivers nemen: tekstreeks zoeken via find() en find_all(), CSS via select() en select_all(), en een native xpath()-ingang. XPath, CSS en tekstzoek op hetzelfde object is echt handig; andere libraries laten je voor XPath soms terugvallen op evaluate(). De Config-constructor exposeert user_data_dir, headless, browser_executable_path, browser_args, sandbox, lang (standaard 'en-US'), host, port, expert, plus **kwargs. Tijdens deze API-inventarisatie leverde Config(headless=True) 16 Chromium-launchflags op, waaronder --no-first-run, --no-default-browser-check, --remote-allow-origins=* en --homepage=about:blank. In deze introspectiestap heb ik start() niet aangeroepen. De browsertests verderop zijn een aparte run.

System diagram: Direct CDP Control Path

De bibliotheek biedt wel degelijk een API-oppervlak dat op anti-detectie is gericht — methoden waarvan ik het bestaan heb bevestigd, maar waarvan ik het gedrag niet tegen een doelwit heb getest. Dat noem ik één keer en laat het daarbij, omdat het effect op een echte dienst precies het onderdeel is dat ik bewust níét heb getest. Neutraal geformuleerd: de naamgeving van nodriver is terughoudender dan bij sommige stealth-branded concurrenten. Het verhaal is vooral architectonisch — CDP-native, elke run een schoon profiel — en niet een muur van detect_and_bypass-achtige methoden. Zie dat als observatie over API-ontwerp, niet als uitspraak over uitkomst.

Die aantallen kwamen uit het importeren van het pakket en het gebruiken van Python’s eigen introspectie — inspect, module-walking, attribuuttelling. Er is geen website aangeraakt. Wie het wil reproduceren, krijgt de cijfers rechtstreeks uit het pakket; dit zijn geen aannames.

Wat het zelf prijsgeeft

Measured results chart: Browser disclosure fields in the tested stacks

Hier is een vraag die je kunt beantwoorden zonder ook maar in de buurt van een echte verdediging te komen: wat geeft een browser prijs als nodriver die aanstuurt, aan de pagina die het bekijkt? Ik schreef een pagina die de voor de hand liggende dingen uitleest — navigator.webdriver, de user-agent, platform, talen, plugin- en hardware-aantallen, de vorm van window.chrome, wat de Permissions API zegt, en venster- en schermgeometrie — serveerde die vanaf 127.0.0.1 en zette er vier stacks op: nodriver, Botasaurus, en stock Playwright en stock Puppeteer als controles. Alle vier draaiden op dezelfde Chrome-build (Chrome for Testing 151.0.7922.10), dus alles wat verschilt komt door de library en niet door de browser. Headless en headed, drie runs elk. Elke waarde hieronder bleef over alle drie runs gelijk.

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"]

Het duidelijkste verschil is die ene boolean. Onder deze standaard launchconfiguraties rapporteert nodriver navigator.webdriver als false, terwijl stock Playwright en Puppeteer in zowel headless als headed runs true teruggeven. De test laat een verschil op configuratieniveau zien; hij bewijst niet dat die waarde louter voortkomt uit het ontbreken van een WebDriver-binary.

In alle vier stacks is die eigenschap nog steeds de native getter van de browser op Navigator.prototype — function get webdriver() { [native code] } — en nooit een own-property op de instantie of een vervangen functie. De eigenschap is na het laden niet door paginajavaScript herschreven. De test heeft niet vastgesteld welke launchargumenten of welk pad de verschillende waarde veroorzaken.

Het tweede detail tempert de marketing. In headless mode meldt de user-agent van nodriver nog steeds HeadlessChrome/151.0.0.0 — identiek aan stock Puppeteer en identiek aan stock Playwright. In headed mode wordt dat Chrome/151.0.0.0, opnieuw identiek. Als je aannam dat een anti-detectie-library standaard de beroemdste zelf-identificerende string in browserautomatisering maskeert, doet deze dat niet. Dat moet je zelf instellen.

Bijna alles was verder gelijk over alle vier stacks, en dat is het vermelden waard omdat het de vergelijking versmalt — elk van de onderstaande eigenschappen kwam op nodriver, Botasaurus, Playwright en Puppeteer hetzelfde terug:

EigenschapWaarde, identiek op alle vier stacks
platformMacIntel
vendorGoogle Inc.
Pluginsvijf
MIME-typestwee
pdfViewerEnabledtrue
Logische corestwaalf
Gerapporteerd device memory16 GB
Touch pointsnul
window.chromeaanwezig, met app/csi/loadTimes en geen runtime
WebGL-rendererstringidentiek in alle vier

De oude klassieker waarbij de Permissions API en Notification.permission elkaar tegenspreken, kwam nergens voor — alle vier rapporteerden default en prompt consistent. Ik heb ook document en window doorzocht op de cdc_-achtige restanten die oudere WebDriver-stacks achterlieten: in alle vier leeg.

De ene plek waar nodriver er méér uitziet als een kale geautomatiseerde browser dan een volledig afgeschermde control, is de venstergeometrie. Headless nodriver rapporteert outerWidth/outerHeight van 0×0 op een scherm van 800×600; headless Playwright rapporteert 1280×720 omdat het zelf een viewport instelt. Puppeteer zit, net als nodriver, op 0×0. Dat is een verschil in standaardconfiguratie, niet in capaciteit, en is te wijzigen.

Nog één ding dat je moet weten vóór je het inzet: nodriver levert geen browser mee, dus out-of-the-box gebruikt het gewoon de Chrome die al op je machine staat. Bij mij betekende dat automatische detectie van /Applications/Google Chrome.app — Chrome 150.0.7871.187 — en de prijsgegeven user-agent was die versie, niet een vastgepinde. De browserversie die jouw verkeer prijsgeeft, is dus gewoon de versie die op jouw vloot geïnstalleerd staat.

Noem dit wat het is, en wat het niet is. Dit is een vastlegging van wat een geautomatiseerde stack prijsgeeft wanneer niemand hem vraagt iets te verbergen — nuttig als je aan de verdedigende kant zit, en ook nuttig als je wilt weten wat je eigen tooling uitzendt. Het is geen maat voor de vraag of dat voor een specifieke dienst iets uitmaakt. Ik heb dat niet getest, en geen enkele rij hierboven moet als uitkomst worden gelezen.

Krijgt het ook echt content van een pagina af?

Jezelf kenbaar maken is één ding; de juiste HTML teruggeven is het werk. Ik heb nodriver op dezelfde fixture met drie contentklassen gezet die de rest van deze benchmarkrepo gebruikt, zodat de cijfers aansluiten op alle andere hier gemeten tools. De pagina bevat drie dingen: A, een statische link waarvan het kenmerk letterlijk in de geserveerde bytes staat; B, een node die tijdens het parsen door een inline script wordt opgebouwd, met markering en URL samengesteld uit fragmenten zodat alleen het uitvoeren van JavaScript ze onthult; en C, een node die 800 ms na het load event wordt geïnjecteerd, op dezelfde manier opgebouwd. Klasse C is de lastige — een lezing bij het load event kan die niet zien.

StackStandaardlezingMet een expliciete wait
nodriver 0.50.32 van 3 (A + B, mist C)3 van 3
Botasaurus 4.0.922 van 33 van 3
Playwright 1.56.02 van 33 van 3
Puppeteer 24.16.02 van 33 van 3

nodriver komt precies uit waar de zwaargewichten ook uitkomen. browser.get() gevolgd door tab.get_content() is een snapshot op het load-moment: JavaScript wordt netjes uitgevoerd — klasse B bewijst dat, want klasse B staat nergens in de geserveerde bytes — maar alles wat ná load wordt geïnjecteerd, wordt gemist. Voeg tab.select("#delayed-injected") toe en je krijgt alle drie. Dezelfde valkuil, dezelfde oplossing, als bij Playwright en Puppeteer. Stabiel over drie herhalingen en drie aparte runs van de hele suite, zonder flakiness.

Ik heb de injectievertraging gevarieerd om te zien waar de standaardlezing afhaakt. nodriver ziet klasse C niet meer zodra de injectie 100 ms of later na load plaatsvindt — dezelfde grens als beide stock controls. (Botasaurus is hier de uitzondering, en juist dat maakt het het interessantste verschil tussen de twee anti-detectielibraries: zijn get() blokkeert standaard tot de volledige pagina geladen is, waardoor de standaardlezing injectie op 300 ms nog steeds vangt. Daar betaalt het ongeveer 250 ms per navigatie voor.)

De wait zelf heeft een eigenaardigheid om rekening mee te houden. In deze sweep landde nodriver’s tab.select() in grove poll-buckets in plaats van de injectievertraging nauwkeurig te volgen:

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

In deze fixture leidde een injectie van 100 ms tot ongeveer 1,1 seconde voor select(). De geïnstalleerde loop doet await self en daarna await self.sleep(0.5) na een miss; de gemeten gecombineerde cyclus kwam hier rond één seconde uit, maar await self is niet aangetoond als een universele vaste slaapduur. Elke geteste vertraagde node werd gevonden. De waiter van Puppeteer volgde deze vertragingen nauwkeuriger. Veel opeenvolgende waits kunnen dat verschil optellen, al heb ik hier geen productiepagina met dertig selectors gebenchmarkt.

Opstarten is de andere plek waar het async-en-lean verhaal de praktijk raakt. Het opstarten van de browser zette nodriver in dezelfde buurt als Botasaurus en Puppeteer, en duidelijk achter Playwright:

StackBrowserlaunch, over runs
nodriver 0.50.3910–1583 ms
Botasaurus 4.0.92986–1151 ms
Puppeteer 24.16.0969–1008 ms
Playwright 1.56.0282–365 ms

Zodra het draait, is nodriver’s navigate-and-read van de vier het snelst met 119–129 ms. Licht aan het stuur, gewoon bij het starten.

Installatie en footprint: het echt goede deel

Hier verdient nodriver zijn label “gericht”, en dat is een verifieerbaar installatiefait zonder iets over scraping te zeggen. Dit is wat een schone pip install nodriver oplevert:

Installatiefaitnodriver 0.50.3
Gedeclareerde directe runtime-afhankelijkhedendrie — websockets, mss, deprecated
Opgeloste transitieve afhankelijkheidwrapt (via deprecated)
Gemeten totaal in site-packagesongeveer 17,2 MB over 6 dist-info-directories, inclusief de omgevingseigen pip
Aandeel van nodriver zelf3,7 MB
numpy / lxmlgeen van beide
Browser-binary bij installatieniets gedownload

Voor een categorie die vaak een hele renderingstack meeneemt, is dat opvallend licht.

Het onderscheid tussen directe en transitieve pakketten is belangrijk voor onderhoud. nodriver vraagt om mss, websockets en deprecated; wrapt komt mee omdat deprecated het nodig heeft. Het zesde dist-info-directory in de 17,2 MB-omgeving is pip, dat al onderdeel was van die virtual environment. Dus “17,2 MB over zes distributies” beschrijft de gemeten omgeving, terwijl “vier toegevoegde runtimepakketten” beschrijft wat de installatie heeft opgelost. Het zijn verwante aantallen, geen verwisselbare.

De vergelijking die de cijfers betekenis geeft: op dezelfde machine weegt het zusterframework Botasaurus 122,3 MB over 44 pakketten — grofweg zeven keer zoveel schijfruimte. Dat is het verschil tussen een gerichte CDP-driver en een framework met alles erop en eraan, en dat werkt twee kanten op. nodriver geeft je een dunne, leesbare afhankelijkheidsstructuur die je echt kunt auditen; Botasaurus geeft meer out of the box en rekent daarvoor af in schijfruimte en dependency-oppervlak. Geen van beide is in abstracte zin “beter” — het hangt ervan af of je een driver of een framework wilt — maar als je waarde hecht aan een kleine, inspecteerbare installatie, is nodriver ongewoon netjes voor wat het doet.

Die kleine schijfruimte betekent niet automatisch een kleine import. Op de byte-gefixte Python 3.14-kopie duurde import nodriver ongeveer 158 ms (mediaan over verse subprocess-imports, ruwweg 151–199 ms). De ongewijzigde 3.14-import faalt, dus daarvoor wordt geen timing gerapporteerd. De 57 CDP-domeinmodules laden eager, en het resident memory na import was 31,5–31,9 MB voordat er überhaupt een Chrome-proces bestond. Een gestart browserproces zou aanzienlijk meer toevoegen en is niet meegenomen in deze geheugentelling.

Twee kanttekeningen bij die cijfers. Ze komen van één machine — macOS arm64 — en de footprint- en importtimings zijn gemeten op Python 3.14 tegen de kopie met één byte-patch, omdat het ongewijzigde pakket daar helemaal niet importeert. Op een ondersteunde Python heb je die patch niet nodig: ik heb een schone ongepatchte import op 3.12.13 bevestigd, en daarvan komen alle browsermetingen. En er is nog steeds een echte Chrome-, Chromium-, Edge- of Brave-binary nodig op runtime — nodriver stuurt een bestaande browser aan, het levert er geen mee, dus die installkost zit buiten de 17 MB, en zoals het disclosure-gedeelte liet zien is de Chrome-versie die jouw machine heeft ook de versie die je verkeer prijsgeeft.

De licentie is de echte adoptiebeslissing

System diagram: The license is the real adoption decision

De meeste reviews van een gratis tool behandelen “het is open source” als het einde van het licentieverhaal. Bij nodriver is dat juist het begin, want de licentie is AGPL-3.0 — bevestigd in zowel LICENSE.txt in de wheel als de spdx_id in de repo. Dat is een sterke netwerk-copyleftlicentie, en iets wezenlijk anders dan wat de buren vragen.

De peers waarmee mensen nodriver meestal vergelijken, zijn permissief gelicentieerd, en dat maakt het contrast concreet:

ToolLicentieAls je een aangepaste kopie als netwerkdienst draait
nodriverAGPL-3.0Sectie 13 kan van een operator verlangen dat de broncode van de aangepaste versie aan externe gebruikers wordt aangeboden
PlaywrightApache-2.0geen equivalente netwerk-copyleftclausule
PuppeteerApache-2.0idem
BotasaurusMITidem

De reikwijdte is belangrijk. AGPL-sectie 13 gaat over een aangepaste versie van het gedekte programma die voor externe netwerkinteractie wordt gebruikt. Deze review beslist niet of omringende servicecode samen met het gedekte werk telt, en lost ook geen grensgevallen van intern gebruik of bedrijfsafbakening op. Als een hosted product nodriver wijzigt, bekijk dan de licentietekst en architectuur samen met juridisch advies. Dit is een technische adoptievlag, geen juridisch advies.

Ik oordeel niet over de vraag of AGPL goed of slecht is — copyleft is een legitieme keuze en veel serieuze projecten gebruiken het. Ik wijs erop dat “nodriver is free and open source” waar is, maar onvolledig. De verplichting bestaat echt, verschilt van de permissieve standaard in dit deel van het ecosysteem, en hoort mee te wegen in de beslissing in plaats van platgeslagen te worden tot “gratis”. (PyPI levert voor zover relevant helemaal geen license classifier mee; de AGPL-tekst staat in de wheel en de SPDX-id in de repo, dus reken niet op de package index om dit voor je te tonen.)

De metadata, gedateerd

Tijdstipgebonden repo- en pakketcijfers, rechtstreeks uit de GitHub API en PyPI:

FeitWaarde op 14 juli 2026
Stars4.511
Forks422
Open issues14
Aangemaaktfebruari 2024
Laatst gepushtmei 2026
Laatste PyPI-release0.50.3
Wheelpuur Python py3-none-any
requires-python>=3.9
Python-classifiers3.7–3.13

De waarneembare onderhoudssignalen zijn gemengd: de repository is in mei 2026 gepusht, terwijl het laatst gemeten pakket nog steeds het Python 3.14-importprobleem bevatte en de voorgestelde fix op de onderzoeksdatum nog niet was uitgebracht. Stars en open issues beantwoorden niet of dat ritme jouw onderhoudsnorm haalt.

Plus- en minpunten

Pluspunten:

  • Kleine, overzichtelijke installatie: drie gedeclareerde afhankelijkheden plus transitief wrapt, ~17,2 MB over zes dist-info-directories in de gemeten omgeving (één daarvan is pip), geen numpy/lxml en geen browserdownload bij installatie.
  • Echt CDP-native: bundelt zijn eigen 57 DevTools Protocol-bindings en praat het protocol rechtstreeks, zonder chromedriver-/Selenium-binary ertussen.
  • Breed zoekoppervlak op Tab (62 publieke methoden) met first-class XPath-, CSS- en tekstzoekfuncties — geen terugval naar ruwe evaluate() nodig voor XPath.
  • Asynchroon ontworpen, elke run een schoon profiel, en een nette Config-surface voor de gebruikelijke knoppen (headless, executable path, args, lang, ports).
  • Haalt content van een JavaScript-gebouwde pagina net zo goed op als de zwaargewichten: 2 van 3 contentklassen op een standaardlezing, 3 van 3 met een expliciete wait — identiek aan stock Playwright en Puppeteer op dezelfde fixture, stabiel over drie runs. Navigate-and-read was met 119–129 ms het snelst van de vier.
  • Rapporteert navigator.webdriver standaard als false waar beide stock controls true teruggeven, zonder de eigenschap te patchen — de descriptor blijft de native getter van de browser.
  • Een duidelijke architecturale identiteit als CDP-native opvolger van undetected-chromedriver.

Minpunten:

  • Importeert out of the box niet op Python 3.14 — één niet-UTF-8 byte in cdp/network.py veroorzaakt een SyntaxError bij import. Gereproduceerd tegen open issue #35, nog niet gefixt in 0.50.3. Pin op ≤3.13 (schoon getest op 3.12.13) of encodeer het bestand opnieuw.
  • AGPL-3.0 is een reĂ«le afweging voor iedereen die een aangepaste kopie als netwerkdienst draait — strenger dan de Apache/MIT-peers.
  • De kleine schijfruimte betekent niet een kleine import: ~158 ms cold-start en ~31,5 MB resident voordat er een browser bestaat, omdat alle 57 CDP-modules eager laden.
  • tab.select() pollt met een halve seconde back-off, dus korte waits ronden naar boven af — een 100 ms wait kost ongeveer 1,1 s waar de waiter van Puppeteer ongeveer 210 ms kost. Wel altijd correct, maar het telt op bij veel kleine waits.
  • Headless runs kondigen zich standaard nog steeds aan als HeadlessChrome in de user-agent, precies zoals stock controls; de standaardconfiguratie verbergt de meest voor de hand liggende zelfidentificerende string niet.
  • Heeft op runtime nog steeds een echte Chrome/Chromium/Edge/Brave-binary nodig; de lichte pip-install is maar de helft van het verhaal, en welke Chrome jouw machine heeft, is ook de versie die je verkeer prijsgeeft.
  • Effectiviteit tegen anti-botsystemen is hier niet geverifieerd — het volledige stealth-idee is bewust niet getest.

Wat ik niet heb getest, en dus niet kan claimen: geheugen per tab, CDP-roundtrip-latency, profielbeheer, throughput op schaal, Linux of Windows, specifiek Python 3.13, en — de grote — echte anti-bot-effectiviteit tegen een live dienst. Elke browser in deze review praatte uitsluitend met een fixture op 127.0.0.1. Alle cijfers komen van één machine (macOS arm64); de browsermetingen zijn Python 3.12.13 zonder patch, en de oudere footprint- en importtimingcijfers zijn Python 3.14 op de één-byte-gepatche kopie.

Ik heb ook geen browser-upgrade-matrix gedraaid, dus compatibiliteit met toekomstige Chrome-releases blijft een operationele check voor de gebruiker, niet een resultaat van deze review.

Voor wie het is, en wie beter verder kan kijken

nodriver past als je een lichte, asynchrone, CDP-native driver voor een live Chromium wilt en het prima vindt om zelf browser, updates en runtime te beheren. De kleine dependency tree is makkelijker te auditen, en het CDP-first ontwerp past bij protocolniveau-besturing. Geschiktheid voor containers is ongetest: deze review heeft geen Linux-images, browserinstallatie, gedeelde libraries, sandboxing of procesopruiming onderzocht.

Twee groepen kunnen beter elders kijken. Zit je op Python 3.14 en wil je je interpreter niet pinnen of een vendorbestand patchen, wacht dan tot de fix uitkomt — de importbreuk is vandaag een harde stop. En als AGPL-3.0 een probleem is voor hoe je wilt shippen — bijvoorbeeld een hosted dienst met private aanpassingen — dan is de licentie alleen al reden genoeg om eerst een permissief alternatief te overwegen. Geen van beide is een aanval op de code; het zijn beperkingen die je liever nu leert kennen dan tijdens een compliance-review.

Sla het ook over als je eigenlijk data nodig hebt, niet een browser die je handmatig bestuurt. nodriver geeft je een scriptbare tab en 62 methoden; een gerenderde pagina omzetten in schone, gestructureerde records blijft alsnog jouw code. Dat is een andere taak, en precies daar komt een managed API in beeld.

Alternatieven, en waar Thunderbit past

Eerst de eerlijke framing: nodriver is gratis, AGPL en self-hosted. Jij draait de browser, beheert de updates en draagt de runtime en alle fouten daarin. Voor een developer die precies die controle wil, concurreert geen managed service op prijs met een bibliotheek die je al hebt.

Vergelijk binnen open source op taak, niet op logo. Als je echte browserdrivers afweegt, behandelt onze Playwright- en Puppeteer-vergelijking de twee voor de hand liggende zwaargewichten waar nodriver qua vorm naast staat. Scrapling is de dichtstbijzijnde Python-buurman op de stealth-as als dat je reden is om te kijken. Voor LLM-klare output in plaats van ruwe browsercontrole rendert Crawl4AI pagina’s en geeft Markdown terug, en Scrapy blijft het referentiekader voor grote browserloze crawls. Als je er meerdere tegelijk vergelijkt, zet de open-source scraper-roundup de categorieën naast elkaar.

Openheid: dit artikel wordt gepubliceerd door Thunderbit. Thunderbit is een managed extractieservice, dus de vergelijking is taakgericht en niet architectuurgericht. nodriver geeft je een browsercontrolelaag die je zelf host en programmeert; Thunderbit handelt rendering af en levert pagina-inhoud of schema-gestructureerde records als service terug. Gebruik nodriver wanneer protocolniveau-browsercontrole en self-hosting vereisten zijn. Overweeg een managed extractor wanneer het outputrecord en de operationele overdracht belangrijker zijn dan het bezitten van de browser. Veranderbare details over eindpunten, credits en batchlimieten horen thuis op de prijspagina, niet in een bibliotheekbenchmark.

De afweging zit in waar het werk leeft: nodriver laat browserbeheer en runtime-onderhoud bij jou, zonder kosten per request; een managed API neemt die laag over en rekent per call.

Probeer Thunderbit voor webdata-extractie

Conclusie

Gebruik nodriver als je een kleine, asynchrone, CDP-native Chromium-driver wilt, hebt bevestigd dat hij werkt op jouw Python-versie (hier importeerde 3.12.13 schoon), en de implicaties van AGPL-3.0 voor jouw distributiewijze hebt bekeken. De resolved runtime-graph voegde vier pakketten toe, de gemeten omgeving nam ~17 MB in beslag, en de bibliotheek levert 57 CDP-domeinmodules met een Tab-oppervlak van 62 methoden en native XPath. Op deze fixture leverde hij standaard 2 van 3 contentklassen en met een wait 3 van 3, net als stock Playwright en Puppeteer.

Weeg de kanttekeningen wel eerlijk mee. Op Python 3.14 importeert het helemaal niet totdat je één niet-UTF-8 byte repareert — een gedocumenteerd, nog open probleem, geen mysterie, maar wel een harde stop op de dag dat je erop stuit. De licentie is AGPL-3.0, wat een echte beslissing is voor iedereen die een aangepaste kopie als dienst draait, geen formaliteit. De kleine installatie koopt geen kleine import, omdat al die CDP-modules upfront laden, en select()’s halve-seconde polling maakt korte waits ongeveer een seconde per stuk. Op de default-disclosurevraag die ik daadwerkelijk kon beantwoorden, is het beeld nauwer dan de marketing suggereert: één boolean wijkt af van stock Puppeteer, de headless user-agent zegt nog steeds HeadlessChrome, en alle andere gemeten eigenschappen waren identiek over alle vier stacks. En het hele anti-detectie-idee — de reden waarom veel mensen überhaupt bij nodriver uitkomen — heb ik bewust niet getest. Ik heb de bibliotheek geïnventariseerd en aangestuurd op een pagina op mijn eigen machine in plaats van hem tegen live verdedigingen te laten lopen, en ik zeg dat liever eerlijk dan dat ik je een bypassbelofte geef die ik niet kan dragen. Op de vragen die ik wel kon beantwoorden, is nodriver een goed opgebouwde, ongewoon lichte driver met twee scherpe randen — een Python-versiegrens en een copyleftlicentie — die je liever ziet aankomen.

Probeer Thunderbit voor webdata-extractie Get Started Free

Veelgestelde vragen

Waarom faalt import nodriver op Python 3.14?
Omdat cdp/network.py een niet-UTF-8 ±-byte bevat zonder source-encoding declaration. Python 3.14.2 wijst het bestand af en breekt de transitieve import af; Python 3.12.13 importeert hetzelfde byte-identieke bestand ongewijzigd. Deze review heeft de exacte interpreterwijziging niet geïsoleerd en Python 3.13 niet getest. Het upstream-pad loopt via nodriver issue #35 en pull request #36. Gebruik een versie die je zelf getest hebt, of encodeer het bestand opnieuw naar UTF-8.

Wat levert “CDP-native, no webdriver” op, en wat kost installatie?
nodriver bundelt 57 DevTools Protocol-domeinmodules en praat CDP via een WebSocket in plaats van chromedriver via Selenium aan te roepen. Tab biedt 62 methoden, inclusief native XPath. De pakketmetadata noemt drie runtimevereisten (websockets, mss, deprecated); door de resolutie komt wrapt erbij. De gemeten omgeving gebruikte ongeveer 17,2 MB over zes dist-info-directories, inclusief pip, zonder numpy, lxml of gedownloade browserbinary. Twee kanttekeningen: de import van de gepatchte kopie duurde ~158 ms omdat alle 57 CDP-modules eager laden, en je hebt nog steeds apart een browser uit de Chrome-familie nodig.

Maakt de licentie AGPL-3.0 uit voor mijn project?
Dat hangt af van hoe je uitrolt. AGPL-3.0 is een netwerk-copyleftlicentie: als je een aangepaste versie van nodriver als dienst draait die anderen gebruiken, moet je die aangepaste broncode aanbieden. Voor een persoonlijk script of intern hulpmiddel dat nooit extern zichtbaar is, speelt dit meestal niet. Voor een gehost commercieel product dat op een gepatchte nodriver draait, is het een serieuze vraag voor degene die compliance beheert — en strenger dan de Apache-2.0- en MIT-licenties van vergelijkbare tools.

Wat laat nodriver over zichzelf zien — en betekent dat dat het Cloudflare verslaat?
Het eerste deel is gemeten; het tweede niet, en dat onderscheid telt. Op een pagina die ik vanaf 127.0.0.1 serveerde, en met dezelfde Chrome-build als de controles, komt navigator.webdriver terug als false, terwijl stock Playwright en stock Puppeteer allebei true rapporteren. Die waarde wordt bij browserstart gezet, niet door de eigenschap te patchen — de descriptor is nog steeds Chrome’s eigen native getter. Verder komt vrijwel alles exact overeen met de controles: dezelfde platformstring, vijf plugins, twaalf cores, 16 GB gerapporteerd device memory, dezelfde window.chrome-vorm, geen tegenstelling in de Permissions API en geen cdc_-achtige restanten op document of window. Headless runs melden nog steeds HeadlessChrome/151.0.0.0 in de user-agent, net als beide controls — dat wordt niet voor je gemaskeerd. Wat dit allemaal niet zegt, is of het werkt tegen een echte anti-botdienst. Ik heb nodriver nooit tegen een live site gezet, nooit contact gehad met een anti-botservice en nooit een CAPTCHA aangeraakt; dat viel bewust buiten scope. De disclosure-tabel hierboven zegt wat de stack prijsgeeft. Ze zegt niets over wie er luistert, of wat die daarmee doen.

Kan nodriver JavaScript-gerenderde content goed verwerken?
Ja, met de gebruikelijke kanttekening over wanneer je leest. Op een fixture met drie contentklassen gaf een standaard browser.get() + tab.get_content() 2 van 3 terug — JavaScript wordt correct uitgevoerd (de synchroon geïnjecteerde klasse staat nergens in de geserveerde bytes en kwam toch terug), maar het wordt gelezen op het load event, dus alles wat daarna wordt geïnjecteerd ontbreekt. Voeg tab.select("#delayed-injected") toe en je krijgt 3 van 3. Dat is identiek aan stock Playwright en stock Puppeteer op dezelfde pagina. Houd wel rekening met één eigenaardigheid: select() pollt met een halve seconde back-off, dus een wait van 100 ms kost ongeveer 1,1 seconde.

Ke
Ke
CTO bij Thunderbit | Senior Data Scientist & ML-expert Met bijna tien jaar ervaring in machine learning en data science is Ke Shen alumnus van Columbia University en voormalig Senior Data Scientist bij Walmart Labs. Met diepgaande, door vakgenoten erkende expertise in Python, R, Java en statistiek deelt hij praktijkgerichte inzichten over hoe je complexe AI-algoritmen van theorie naar productieklare architectuur brengt.
Inhoudsopgave
Thunderbit · AI-webdata-agent

Gegevens extraheren van elke pagina in 1 klik

Vertrouwd door 250.000+ gebruikers
gratis plan beschikbaar
Van webpagina naar spreadsheet
Beschrijf wat je nodig hebt — Thunderbit's AI Agent scrapt het en exporteert naar Excel, Google Sheets, Airtable of Notion. Gratis om te beginnen.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week