nodriver-recension: En liten CDP-drivrutin som inte går att importera i Python 3.14

Senast uppdaterad August 18, 2026
nodriver-recension: En liten CDP-drivrutin som inte går att importera i Python 3.14
AI-sammanfattning
nodriver är ett Python-bibliotek för webbläsarautomatisering från ultrafunkamsterdam, skaparen bakom undetected-chromedriver, och det presenterar sig som det projektets efterföljare. Det viktiga arkitekturklivet är bort från Selenium och chromedriver: nodriver pratar med Chromium via Chrome DevTools Protocol (CDP), utan någon WebDriver-binär i kedjan, och API:et är asynkront. Playwright och Puppeteer tillhör en annan jämförelsegrupp. De är också protokollstyrda webbläsarkontroller, inte arvtagare till WebDriver; de praktiska skillnaderna handlar om API-design, paketering, hur webbläsaren levereras och vilka kompatibilitetsregler som gäller. Den här recensionen inventerar nodriver 0.50.3 i stället för att testa det mot skarpa skydd.

nodriver är ett Python-bibliotek för webbläsarautomatisering från ultrafunkamsterdam, skaparen bakom undetected-chromedriver, och det presenteras som projektets efterföljare. Den viktiga arkitekturförändringen är att lämna Selenium och chromedriver bakom sig: nodriver pratar med Chromium via Chrome DevTools Protocol (CDP), utan någon WebDriver-binär i kedjan, och API:et är asynkront. Playwright och Puppeteer hör till en annan jämförelsegrupp. De är också protokollstyrda webbläsarkontroller, inte arvtagare till WebDriver; de praktiska skillnaderna handlar om API-design, paketering, hur webbläsaren levereras och vilka kompatibilitetsregler som gäller.

Den här recensionen går igenom nodriver 0.50.3 i stället för att testa det mot skarpa skydd. Jag mätte installerat paket, import, API-yta, diskfotavtryck och licens, och startade sedan webbläsare bara mot sidor som servades från 127.0.0.1. Inga live-mål, anti-bot-tjänster eller CAPTCHA:er var inblandade. Resultaten beskriver paketets beteende och vad standardkonfigurationen avslöjar om webbläsaren; de säger inget om hur väl det klarar av detektion.

Tre saker stack ut inom den ramen. För det första går biblioteket inte att importera alls i Python 3.14 — en enda felplacerad byte räcker för att fälla hela paketet innan du ens hinner anropa något. För det andra är det, för att vara en drivrutin i den här kategorin, ovanligt litet: tre deklarerade beroenden plus ett transitive beroende som löses in, och ungefär 17 MB i den testade miljön. För det tredje skiljer sig nodriver på min sida bara från standard-Playwright och standard-Puppeteer på en enda boolean — och headless user-agent-strängen säger fortfarande HeadlessChrome i alla tre. Resten av överraskningarna finns i licensen.

Importfelet i Python 3.14

Börja med det som slår först, eftersom det händer innan någon av dina egna rader körs. I Python 3.14 misslyckas ett vanligt import nodriver direkt:

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

Filen som fallerar är den autogenererade cdp/network.py (rubriken säger # DO NOT EDIT THIS FILE!). Den innehåller en enda icke-UTF-8-byte, 0xb1, för ± i kommentaren #: JSON (±Inf)., och saknar deklaration för källkodens teckenkodning. Modulen laddas via nodriver/__init__ → cdp/__init__ → network, så parse-felet avbryter importen. En genomsökning av paketet hittade inga andra källfiler utanför UTF-8.

Versionsgränsen kräver noggrann formulering. Python 3.14.2 avvisar filen, medan Python 3.12.13 importerar den utan patch. De installerade network.py-filerna är byteidentiska i de miljöerna (SHA-256 ef755f41800d4efb593736f8b55b331bba68eec373ed62ece433e66e4b491cd6), så det är inte olika källartefakter som förklarar utfallet. Den här recensionen isolerade inte den exakta ändringen i CPython-tokenizern som ligger bakom, och Python 3.13 testades inte. Den rapporterar alltså de två uppmätta interpreter-resultaten i stället för att påstå att alla tidigare Python-versioner beter sig som 3.12.

Ingen slutsats om Python 3.13 följer av de två mätpunkterna.

Det här är en reproduktion, inte en ny upptäckt. Samma traceback i Python 3.14 finns dokumenterad i nodriver issue #35, med en föreslagen åtgärd i pull request #36. Version 0.50.3 innehåller fortfarande byten. PyPI-metadata listar klassificerare upp till Python 3.13 och gör inget anspråk på stöd för 3.14.

Workarounden är lika liten som felet: använd en testad interpreter-version eller koda om just den filen till UTF-8, som pull request #36 föreslår. Efter omkodningen importeras paketet och kan inspekteras i 3.14 utan att något annat hinder dyker upp. Den rena körningen i 3.12.13 nedan verifierar en opatchad väg; Python 3.13 testades inte i den här recensionen.

I Python 3.12.13 lyckas import nodriver utan någon patch. Alla webbläsarsiffror längre fram i recensionen kommer från den installationen. Siffrorna för fotavtryck och importtid använder den byte-omkodade kopian i 3.14 och är märkta därefter. Python 3.13 är fortfarande otestat här; ett resultat från 3.12 täcker inte hela det tidigare spannet.

Om du som standard kör moderna interpretrar — och många team hoppar snabbt på en ny Python — är det här en verklig, om än enkelt rättbar, spärr. Bra att känna till den, så du slipper slösa bort en eftermiddag på ett SyntaxError i en fil du aldrig själv skrev.

Vad nodriver faktiskt är, under huven

Den korta beskrivningen ("CDP-native, no webdriver") låter som marknadsföring tills man ser vad som faktiskt följer med i wheel-filen. nodriver paketerar sin egen fullständiga uppsättning DevTools Protocol-bindningar: paketet nodriver.cdp innehåller 57 protokoll-domänmoduler — accessibility, dom, network, page, fetch, runtime, target, storage, input, emulation och så vidare. Det modultalet är mekanismen bakom påståendet "direkt CDP, inget Selenium". I stället för att starta ett chromedriver-exekverbart program som talar WebDriver och översätter åt dig, genererar nodriver Python-objekt för CDP-domänerna och pratar protokollet direkt över WebSocket. cdp/network.py — filen med den felaktiga byten — är en av dessa 57 autogenererade moduler, vilket är anledningen till att felet lever i genererad kod som ingen handredigerar.

Ovanpå protokollagret finns en vänligare objektmodell. Objektet Tab exponerar 62 publika metoder, och sökytan är bredare än i de flesta drivrutiner: textmatchning via find() och find_all(), CSS via select() och select_all(), samt en inbyggd xpath()-ingång. XPath, CSS och textsökning i samma objekt är faktiskt bekvämt; andra bibliotek tvingar dig ibland att gå ner till evaluate() för XPath. Konstruktorn Config exponerar user_data_dir, headless, browser_executable_path, browser_args, sandbox, lang (standard 'en-US'), host, port, expert samt **kwargs. Under den här API-inventeringen gav Config(headless=True) 16 Chromium-startflaggor, inklusive --no-first-run, --no-default-browser-check, --remote-allow-origins=* och --homepage=about:blank. Jag anropade inte start() i just denna introspektionsfas. Webbläsartesterna längre ned var en separat körning.

System diagram: Direct CDP Control Path

Biblioteket har också ett API-lager som är inriktat på anti-detektion — metoder vars existens jag bekräftade men vars beteende jag inte testade mot något mål. Jag nämner det en gång och går vidare, eftersom effekten mot en verklig tjänst är precis det jag valde att inte pröva. Om man ska säga det neutralt: nodriver använder ett mer återhållsamt namnbruk än vissa stealth-profilerade konkurrenter. Berättelsen är arkitektonisk — CDP-native, ny profil för varje körning — snarare än en vägg av metodnamn i stil med detect_and_bypass. Läs det som en observation om API-design, inte som ett resultatlöfte.

De här talen kommer från att paketet importerades och att Pythons egen introspektion användes — inspect, genomgång av moduler och räkning av attribut. Ingen webbplats berördes. Om du vill köra samma sak igen kommer siffrorna direkt ur paketet; det är inga känslor.

Vad den själv avslöjar

Measured results chart: Browser disclosure fields in the tested stacks

Här är en fråga du kan besvara utan att gå i närheten av ett riktigt skydd: när nodriver driver en webbläsare, vad berättar den webbläsaren frivilligt om sig själv för sidan den tittar på? Jag byggde en sida som läser av det uppenbara — navigator.webdriver, user-agenten, plattform, språk, plugin- och hårdvarantal, formen på window.chrome, vad Permissions API säger, samt fönster- och skärmgeometri — serverade den på 127.0.0.1 och pekade fyra stackar mot den: nodriver, Botasaurus, samt standard-Playwright och standard-Puppeteer som kontroller. Alla fyra körde samma Chrome-version (Chrome for Testing 151.0.7922.10), så allt som skiljer sig beror på biblioteket och inte på webbläsaren. Headless och headed, tre körningar vardera. Alla värden nedan var stabila över de tre repetitionerna.

StackLägenavigator.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"]

Den tydligaste skillnaden är just den där enda booleska variabeln. Med dessa standard-startinställningar rapporterar nodriver navigator.webdriver som false, medan standard-Playwright och standard-Puppeteer rapporterar true, både i headless och headed. Testet visar en skillnad i konfiguration; det bevisar inte att värdet enbart följer av frånvaron av en WebDriver-binär.

I alla fyra stackarna är egenskapen fortfarande webbläsarens egen getter på Navigator.prototype — function get webdriver() { [native code] } — aldrig en egen instans-egenskap eller en ersatt funktion. Egenskapen skrevs inte om av sidans JavaScript efter laddning. Testet identifierade inte vilken startflagga eller vilken källväg som orsakade det olika värdet.

Den andra detaljen dämpar marknadsföringen. I headless-läge annonserar nodriver fortfarande HeadlessChrome/151.0.0.0 i user-agenten — identiskt med standard-Puppeteer, identiskt med standard-Playwright. Kör du headed blir det Chrome/151.0.0.0, också identiskt. Om du antog att ett anti-detektion-bibliotek maskerar den mest kända självidentifierande strängen i webbläsarautomatisering som standard, så gör det inte det. Det får du i så fall ställa in själv.

Nästan allt annat var likadant i alla fyra stackar, och det är värt att säga rakt ut eftersom det snävar in berättelsen — varje egenskap nedan lästes ut på samma sätt i nodriver, Botasaurus, Playwright och Puppeteer:

EgenskapVärde, identiskt i alla fyra stackar
platformMacIntel
vendorGoogle Inc.
Pluginsfem
MIME-typertvå
pdfViewerEnabledtrue
Logiska kärnortolv
Rapporterat enhetsminne16 GB
Pekpunkternoll
window.chromefinns, med app/csi/loadTimes och utan runtime
WebGL-renderersträngidentisk i alla fyra

Den gamla klassikern där Permissions API och Notification.permission motsäger varandra dök inte upp någonstans — alla fyra rapporterade default och prompt i samstämmighet. Jag sökte också igenom document och window efter de cdc_-liknande rester som äldre WebDriver-stackar var kända för: tomt i alla fyra.

Det enda ställe där nodriver ser mer ut som en naken automatiserad webbläsare än en kontroll är fönstergeometrin. I headless-läge rapporterar nodriver outerWidth/outerHeight som 0×0 på en 800×600-skärm; headless Playwright rapporterar 1280×720 eftersom det sätter en viewport åt dig. Puppeteer hamnar tillsammans med nodriver på 0×0. Det är en skillnad i standardkonfiguration, inte i kapacitet, och den kan du själv ändra.

En sista sak som är bra att känna till innan du tar det i drift: nodriver levererar ingen egen webbläsare, så direkt efter installation använder det den Chrome som redan finns på maskinen. På min dator betydde det att /Applications/Google Chrome.app upptäcktes automatiskt — Chrome 150.0.7871.187 — och det som avslöjades i user-agenten var den versionen, inte någon låst. Din miljös exponerade webbläsarversion är alltså den version som faktiskt finns installerad.

Säg vad det här är och vad det inte är. Det är en logg över vad en automatiserad stack avslöjar när ingen ber den att dölja något — användbart om du sitter på försvarssidan, användbart om du vill veta vad din egen verktygskedja sänder ut. Det är inte ett mått på om något av detta spelar roll för en viss tjänst. Det testade jag inte, och ingen rad ovan ska tolkas som att den innebär ett resultat.

Kan det faktiskt plocka ut innehåll från en sida?

Att avslöja sig själv är en sak; att returnera rätt HTML är det egentliga jobbet. Jag körde nodriver mot samma fixture med tre innehållsklasser som resten av detta benchmark-repo använder, så siffrorna går att jämföra med varje annat verktyg som mätts här. Sidan innehåller tre saker: A, en statisk länk vars markör är en literal i de bytes som servas; B, en nod som byggs av ett inline-script under parse, där markör och URL sätts ihop av fragment så att bara exekvering av JavaScript avslöjar dem; och C, en nod som injiceras 800 ms efter load-händelsen, uppbyggd på samma sätt. Klass C är den adversariella — en läsning vid load-händelsen kan inte se den.

StackStandardläsningMed uttrycklig väntan
nodriver 0.50.32 av 3 (A + B, missar C)3 av 3
Botasaurus 4.0.922 av 33 av 3
Playwright 1.56.02 av 33 av 3
Puppeteer 24.16.02 av 33 av 3

nodriver hamnar exakt där de stora namnen hamnar. browser.get() följt direkt av tab.get_content() ger en ögonblicksbild vid laddning: den renderar JavaScript korrekt — klass B bevisar det, eftersom klass B inte finns i de serverade bytesen — men missar allt som injiceras efter load. Lägg till tab.select("#delayed-injected") så får du alla tre. Samma fallgrop, samma lösning, som i Playwright och Puppeteer. Stabilt över tre upprepningar och tre separata körningar av hela sviten, utan flakighet.

Jag svepte injektionsfördröjningen för att se var standardläsningen ger upp. nodriver slutar se klass C när injektionen landar 100 ms eller mer efter load — samma gräns som för båda standardkontrollerna. (Botasaurus är avvikaren här, och det är den verkligen intressanta skillnaden mellan de två anti-detect-biblioteken: dess get() blockerar som standard tills sidladdningen är komplett, så standardläsningen fångar fortfarande injektion vid 300 ms. Det kostar ungefär 250 ms per navigering.)

Väntan i sig har en egen liten egenhet. I den här svepningen landade nodrivers tab.select() i grova polling-bucketar snarare än att följa injektionsfördröjningen tätt:

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

I det här fallet ledde en injektion vid 100 ms till ungefär 1,1 sekunder för select(). Den installerade loopen gör await self och sedan await self.sleep(0.5) efter ett misslyckat försök; den uppmätta kombinerade cykeln landade nära en sekund här, men await self är inte fastslaget som en universell sömn med bestämd längd. Alla testade fördröjda noder hittades. Puppeteers väntare följde fördröjningarna mer exakt. Många sekventiella väntar kan därmed dra isär resultatet, även om den här recensionen inte benchmarkade en produktsida med trettio selektorer.

Starten är det andra ställe där den asynkrona och slimmade berättelsen möter verkligheten. Att dra i gång webbläsaren placerade nodriver i samma härad som Botasaurus och Puppeteer, och tydligt efter Playwright:

StackWebbläsarstart, över flera körningar
nodriver 0.50.3910–1583 ms
Botasaurus 4.0.92986–1151 ms
Puppeteer 24.16.0969–1008 ms
Playwright 1.56.0282–365 ms

När den väl kör är nodrivers navigera-och-läs den snabbaste av de fyra med 119–129 ms. Lätt i styrningen, ordinär i startögonblicket.

Installation och fotavtryck: den verkligt bra delen

Här förtjänar nodriver verkligen sin etikett som "fokuserat", och det handlar om en verifierbar installationsfakta snarare än något om scraping. Det här är vad ett rent pip install nodriver löser in till:

Installationsfaktanodriver 0.50.3
Deklarerade direkta runtime-beroendentre — websockets, mss, deprecated
Resolved transitive dependencywrapt (via deprecated)
Mätt totalt i site-packagescirka 17,2 MB över 6 dist-info-kataloger, inklusive miljöns pip
nodrivers egen andel av detta3,7 MB
numpy / lxmlingetdera
Webbläsarbinär vid installationstillfälletingen hämtas

För en kategori som ofta drar in ett helt renderingsstack är det här en lätt installation.

Skillnaden mellan direkta och transitive paket spelar roll för underhållet. nodrivers metadata begär mss, websockets och deprecated; wrapt följer med eftersom deprecated kräver det. Den sjätte dist-info-katalogen i 17,2 MB-miljön är pip, som redan ingick i den virtuella miljön. Så "17,2 MB över sex distributioner" beskriver den uppmätta miljön, medan "fyra tillagda runtime-paket" beskriver vad installationen faktiskt löste in. Det är relaterade siffror, inte utbytbara.

Kontrasten som gör siffran meningsfull: på samma maskin väger syster-ramverket Botasaurus 122,3 MB över 44 paket — ungefär sju gånger diskfotavtrycket. Det är skillnaden mellan en fokuserad CDP-drivrutin och ett heltäckande ramverk, och det slår åt båda håll. nodriver ger dig ett tunt, läsbart beroendeträd som faktiskt går att granska; Botasaurus ger dig mer direkt ur lådan och tar betalt i disk och beroenden för det. Ingen av dem är "bättre" i abstrakt mening — det beror på om du vill ha en drivrutin eller ett ramverk — men om du värdesätter en liten och inspekterbar installation är nodriver ovanligt rent för vad det gör.

Det lilla diskfotavtrycket betyder inte en liten import. I den bytefixade Python 3.14-kopian tog import nodriver ungefär 158 ms (median över färska subprocess-importer, ungefär 151–199 ms). Den opatchade 3.14-importen misslyckas, så ingen timing rapporteras för den. Alla 57 CDP-domänmoduler laddas eager, och efter import låg resident minnesanvändning på 31,5–31,9 MB innan någon Chrome-process fanns. En startad webbläsare skulle lägga till betydligt mer och ingick inte i den här minnesmätningen.

Två reservationer hör till just de siffrorna. De kommer från en maskin — macOS arm64 — och fotavtrycks- samt importtidsvärdena togs i Python 3.14 på den byte-patchade kopian, eftersom det opatchade paketet inte importeras alls i den interpretern. På en stödd Python behöver du inte patchen: jag bekräftade en ren opatchad import i 3.12.13, och det är där alla webbläsarmätningar kommer ifrån. Och en riktig Chrome-, Chromium-, Edge- eller Brave-binär krävs fortfarande vid körning — nodriver kör en befintlig webbläsare, det levererar ingen egen, så den kostnaden ligger utanför 17 MB, och som avslöjandeavsnittet visade är det vilken Chrome din maskin råkar ha som blir versionen som trafiken annonserar.

Licensen är det verkliga beslutet för adoption

System diagram: The license is the real adoption decision

De flesta recensioner av ett fritt verktyg behandlar "det är open source" som slutet på licenssamtalet. För nodriver är det början, eftersom licensen är AGPL-3.0 — bekräftat både i wheel-filens LICENSE.txt och i repo:ns spdx_id. Det är en stark nätverkscopyleft-licens, och det är ett materiellt annorlunda åtagande än vad grannarna kräver.

De närmaste alternativen som de flesta väger nodriver mot är permissiva, vilket gör kontrasten tydlig:

VerktygLicensOm du kör en modifierad kopia som nätverkstjänst
nodriverAGPL-3.0Avsnitt 13 kan kräva att operatören erbjuder motsvarande källkod till den modifierade versionen till fjärranvändare
PlaywrightApache-2.0ingen motsvarande nätverkscopyleft-klausul
PuppeteerApache-2.0som ovan
BotasaurusMITsom ovan

Omfattningen spelar roll. AGPL avsnitt 13 riktar sig mot en modifierad version av det omfattade programmet när den används för fjärrinteraktion via nätverk. Den här recensionen avgör inte om separat omgivande tjänstekod utgör del av det omfattade verket, och löser inte heller gränsfall för intern användning eller företagsgränser. Om en hostad produkt modifierar nodriver, gå igenom licenstexten och arkitekturen tillsammans med juridisk rådgivare. Det här är en teknisk signal för adoption, inte juridisk rådgivning.

Jag tar inte ställning till om AGPL är bra eller dåligt — copyleft är ett legitimt val och många seriösa projekt använder det. Jag pekar på att "nodriver är fritt och öppet" är sant men ofullständigt. Förpliktelsen är verklig, den skiljer sig från den permissiva standarden i den här delen av ekosystemet, och den hör hemma i beslutet i stället för att plattas ihop till "gratis". (PyPI, för vad det är värt, skickar inte med någon licensklassificerare alls; AGPL-texten finns i wheel-filen och SPDX-id:t finns i repot, så lita inte på paketindexet för att lyfta fram detta åt dig.)

Metadata, daterad

Repo- och paketdata vid en given tidpunkt, direkt från GitHub API och PyPI:

FaktaVärde den 14 juli 2026
Stjärnor4 511
Forks422
Öppna ärenden14
Skapadfebruari 2024
Senast pushadmaj 2026
Senaste PyPI-version0.50.3
Wheelren Python py3-none-any
requires-python>=3.9
Python-klassificerare3.7–3.13

Underhållssignalerna är blandade: repot fick en push i maj 2026, medan det senast mätta paketet fortfarande innehöll importproblemet i Python 3.14 och den föreslagna fixen inte hade släppts vid forskningsdatumet. Stjärnor och antal öppna ärenden avgör inte om den takten når din toleransnivå för underhåll.

För- och nackdelar

Fördelar:

  • Liten, överskådlig installation: tre deklarerade beroenden plus det transitive wrapt, cirka 17,2 MB över sex dist-info-kataloger i den mätta miljön (en av dem är pip), ingen numpy/lxml och ingen webbläsarnedladdning vid installation.
  • Verkligt CDP-native: paketerar sina egna 57 DevTools Protocol-moduler och pratar protokollet direkt, utan chromedriver/Selenium-binär i kedjan.
  • Bred yta för att hitta element på Tab (62 publika metoder) med förstklassigt stöd för XPath, CSS och textsökning — inget behov av att falla tillbaka till rå evaluate() för XPath.
  • Asynkront från början, ny profil för varje körning och ett rent Config-gränssnitt för vanliga inställningar (headless, sökväg till exekverbart, argument, språk, portar).
  • Hämtar innehåll från en JavaScript-byggd sida lika bra som de större verktygen: 2 av 3 innehållsklasser vid en standardläsning, 3 av 3 med uttrycklig väntan — identiskt med standard-Playwright och standard-Puppeteer på samma fixture, stabilt över tre körningar. Navigera-och-läs var snabbast av de fyra med 119–129 ms.
  • Rapporterar navigator.webdriver som false som standard där båda standardkontrollerna rapporterar true, utan att patcha egenskapen — deskriptorn är fortfarande webbläsarens egen native getter.
  • En tydlig arkitektonisk identitet som den CDP-native efterföljaren till undetected-chromedriver.

Nackdelar:

  • Går inte att importera i Python 3.14 out of the box — en enda icke-UTF-8-byte (cdp/network.py) kastar SyntaxError vid import. Reproducerat mot öppet ärende #35, fortfarande inte fixat i 0.50.3. Lås till ≤3.13 (verifierat rent i 3.12.13) eller koda om filen.
  • AGPL-3.0 är ett verkligt övervägande för alla som kör en modifierad kopia som nätverkstjänst — strängare än Apache/MIT-alternativen.
  • Det lilla diskfotavtrycket betyder inte en liten import: cirka 158 ms kallstart och cirka 31,5 MB resident innan någon webbläsare finns, eftersom alla 57 CDP-moduler laddas eager.
  • tab.select() pollar med en halvsekunds back-off, så korta väntor rundas upp — en väntan på 100 ms kostar cirka 1,1 s där Puppeteers väntare kostar cirka 210 ms. Rätt varje gång, men det blir dyrt över många små väntor.
  • Headless-körningar annonserar fortfarande HeadlessChrome i user-agenten som standard, precis som standardkontrollerna; inget i standardkonfigurationen döljer den mest uppenbara självidentifierande strängen.
  • Kräver fortfarande en riktig Chrome/Chromium/Edge/Brave-binär vid körning; den lätta pip-installationen är bara halva beroendebilden, och vilken Chrome din maskin har är den version som trafiken avslöjar.
  • Effektiviteten mot något anti-bot-system är inte verifierad här — hela stealth-premissen är medvetet otestad.

Det jag inte testade, och därför inte kan uttala mig om: minne per tab, CDP-latens för tur och retur, profilhantering, throughput i stor skala, Linux eller Windows, specifikt Python 3.13, och — det stora — verklig anti-bot-effektivitet mot någon live-tjänst. Alla webbläsare i den här recensionen pratade bara med en fixture på 127.0.0.1. Alla siffror gäller en maskin (macOS arm64); siffrorna för webbläsardrift är Python 3.12.13 utan patch, och de äldre fotavtrycks- och importtidsvärdena är Python 3.14 på den byte-patchade kopian.

Jag körde inte heller någon matris för webbläsaruppgraderingar, så kompatibilitet med framtida Chrome-versioner är fortfarande en operativ kontroll för den som inför det, inte ett resultat av den här recensionen.

Vem det passar för, och vem som bör välja något annat

nodriver passar om du vill ha en lätt, asynkron, CDP-native drivrutin för en live-Chromium och är bekväm med att själv äga webbläsaren, uppdateringarna och runtime-miljön. Det lilla beroendeträdet är lättare att granska, och CDP-först-designen passar bra för protokollnivåstyrning. Lämplighet i containrar är fortfarande otestad: den här recensionen prövade inte Linux-bilder, installation av webbläsare, delade bibliotek, sandboxning eller processstädning.

Två grupper bör titta någon annanstans. Om du kör Python 3.14 och inte vill låsa din interpreter eller patcha en vendorerad fil, vänta tills fixen är släppt — importfelet är ett hårt stopp i dag. Och om AGPL-3.0 ställer till det för hur du planerar att leverera — en hostad tjänst med privata modifieringar — räcker licensen i sig som skäl att väga ett permissivt alternativ innan du bygger vidare här. Det är inget klander mot koden; det är begränsningar du hellre vill upptäcka nu än under en compliance-granskning.

Hoppa också över det om det du egentligen behöver är data, inte en webbläsare du kör manuellt. nodriver ger dig en scriptbar tab och 62 metoder; att förvandla en renderad sida till rena, strukturerade poster är fortfarande din kod att skriva. Det är ett annat jobb, och där kommer en managed API-lösning in.

Alternativ, och var Thunderbit passar in

Först den ärliga ramen: nodriver är gratis, AGPL och self-hosted. Du kör webbläsaren, du hanterar uppdateringarna, du äger runtime-miljön och varje fel i den. För en utvecklare som vill ha exakt den kontrollen finns ingen managed tjänst som konkurrerar på pris med ett bibliotek du redan har.

Bland open source-alternativen: jämför efter uppgift, inte efter logga. Om du väger riktiga webbläsardrivrutiner täcker vår jämförelse mellan Playwright och Puppeteer de två uppenbara tungviktarna som nodriver står bredvid i formen. Scrapling är den närmaste Python-grannen på den stealth-orienterade axeln om det är därför du tittar. För LLM-klart utdata i stället för rå webbläsarkontroll renderar Crawl4AI sidor och lämnar tillbaka Markdown, och Scrapy är fortfarande referensramverket för stora browserlösa crawls. Om du jämför flera av dessa samtidigt lägger sammanställningen av open-source-scrapers kategorierna sida vid sida.

Information: den här artikeln publiceras av Thunderbit. Thunderbit är en managed extraction-tjänst, så jämförelsen är uppgiftsbaserad snarare än arkitektonisk. nodriver ger dig ett lager för webbläsarkontroll som du hostar och programmerar själv; Thunderbit hanterar rendering och returnerar sidinnehåll eller schemaformade poster som tjänst. Använd nodriver när protokollnivåstyrning av webbläsaren och self-hosting är krav. Överväg en managed extractor när resultatformatet och den operativa överlämningen är viktigare än att äga webbläsaren. Ändringsbara detaljer om endpoint, credits och batchgränser hör hemma på prissidan, inte inuti ett bibliotekstest.

Avvägningen är var arbetet ligger: nodriver behåller webbläsarhantering och runtime-underhåll på din sida utan en avgift per förfrågan; ett managed API tar över det lagret och debiterar för anrop.

Testa Thunderbit för extrahering av webdata

Slutsats

Använd nodriver om du vill ha en liten, asynkron, CDP-native Chromium-drivrutin, har verifierat den på din Python-version (3.12.13 importerade rent här), och har granskat AGPL-3.0 för hur du levererar. Dess inlösta runtime-graf lade till fyra paket, den mätta miljön tog ungefär 17 MB, och biblioteket levererar 57 CDP-domänmoduler med en Tab-yta på 62 metoder och native XPath. I den här fixture:n returnerade det 2 av 3 innehållsklasser som standard och 3 av 3 med väntan, i linje med standard-Playwright och standard-Puppeteer.

Men bedöm också begränsningarna nyktert. I Python 3.14 importeras det inte alls förrän du fixar en icke-UTF-8-byte — ett dokumenterat, fortfarande öppet problem, inte ett mysterium, men ett hårt stopp den dag du träffar det. Licensen är AGPL-3.0, vilket är ett verkligt beslut för alla som kör en modifierad kopia som tjänst, inte en formalitet. Den lilla installationen ger inte en liten import, eftersom alla CDP-moduler laddas upfront, och select()s halvsekundspoll gör att korta väntor kostar omkring en sekund vardera. På frågan om standardexponering som jag faktiskt kunde besvara är bilden smalare än marknadsföringen antyder: en enda boolean skiljer sig från standard-Puppeteer, den headless user-agenten säger fortfarande HeadlessChrome, och varje annan egenskap jag mätte var identisk i alla fyra stackar. Och hela anti-detektion-premissen — anledningen till att många alls hittar nodriver — är något jag medvetet inte testade. Jag inventerade biblioteket och körde det mot en sida på min egen maskin i stället för att ställa det mot live-skydd, och jag föredrar att säga det rent ut i stället för att ge dig ett bypass-påstående jag inte kan stå bakom. På de frågor jag faktiskt kunde besvara är nodriver en välbyggd, ovanligt slimmad drivrutin med två skarpa kanter — en Python-versionvägg och en copyleft-licens — som du vill se komma.

Testa Thunderbit för extrahering av webdata Get Started Free

Vanliga frågor

Varför misslyckas import nodriver i Python 3.14? För att cdp/network.py innehåller en icke-UTF-8-byte för ± utan deklaration av källkodens teckenkodning. Python 3.14.2 avvisar filen och avbryter den transitive importen; Python 3.12.13 importerar den byteidentiska filen utan patch. Den här recensionen isolerade inte den exakta interpreter-ändringen och testade inte Python 3.13. Den relevanta tråden uppströms är nodriver issue #35 och pull request #36. Använd en version du har testat, eller koda om filen till UTF-8.

Vad ger "CDP-native, no webdriver" dig, och vad kostar det att installera? nodriver paketerar 57 DevTools Protocol-domänmoduler och pratar CDP över WebSocket i stället för att anropa chromedriver via Selenium. Tab exponerar 62 metoder, inklusive native XPath. Paketmetadata deklarerar tre runtime-krav (websockets, mss, deprecated); när de löses in tillkommer wrapt. Den mätta miljön använde cirka 17,2 MB över sex dist-info-kataloger, inklusive pip, utan numpy, lxml eller nedladdad webbläsarbinär. Två reservationer: importen i den patchade kopian tog cirka 158 ms eftersom alla 57 CDP-moduler laddas eager, och en webbläsare i Chrome-familjen krävs fortfarande separat.

Spelar AGPL-3.0-licensen någon roll för mitt projekt? Det beror på hur du levererar. AGPL-3.0 är en nätverkscopyleft-licens: om du kör en modifierad version av nodriver som en tjänst andra använder, är du skyldig att erbjuda dem den modifierade källkoden. För ett personligt skript eller ett internt verktyg som aldrig exponeras utåt är det normalt inget problem. För en hostad kommersiell produkt byggd på en patchad nodriver är det en riktig fråga att lyfta med den som ansvarar för compliance — och den är strängare än Apache-2.0- och MIT-licenserna hos jämförbara verktyg.

Vad avslöjar nodriver om sig självt — och betyder det att det slår Cloudflare? Första delen är mätt; den andra är det inte, och skillnaden är viktig. På en sida jag servade från 127.0.0.1, med samma Chrome-build som kontrollerna: navigator.webdriver kommer tillbaka som false, medan standard-Playwright och standard-Puppeteer båda rapporterar true. Det värdet sätts vid webbläsarstart, inte genom att patcha egenskapen — deskriptorn är fortfarande Chromes egen native getter. Utöver den booleanska skillnaden matchade nästan allt kontrollernas värden exakt: samma plattformssträng, fem plugins, tolv kärnor, 16 GB rapporterat enhetsminne, samma form på window.chrome, ingen motsägelse i Permissions API och inga cdc_-liknande rester i document eller window. Headless-körningar annonserar fortfarande HeadlessChrome/151.0.0.0 i user-agenten, precis som båda kontrollerna — det maskeras inte åt dig. Det här säger däremot inget om huruvida något av detta fungerar mot en verklig anti-bot-tjänst. Jag pekade aldrig nodriver mot en live-sajt, kontaktade ingen anti-bot-tjänst och berörde ingen CAPTCHA; det låg utanför ramen medvetet. Tabellen ovan beskriver vad stacken själv annonserar. Den säger inget om vem som lyssnar, eller vad de gör med det.

Hanterar nodriver JavaScript-renderat innehåll korrekt? Ja, med den vanliga reservationen om när du läser. På en fixture med tre innehållsklasser gav en standard browser.get() + tab.get_content() 2 av 3 — den exekverar JavaScript korrekt (den synkront injicerade klassen finns inte i de serverade bytesen och kom ändå tillbaka), men den läser vid load-händelsen, så den missar allt som injiceras efteråt. Om du lägger till tab.select("#delayed-injected") får du 3 av 3. Det är identiskt med standard-Playwright och standard-Puppeteer på samma sida. Räkna med en egenhet: select() pollar med en halvsekunders back-off, så en väntan på 100 ms kostar ungefär 1,1 sekunder.

Ke
Ke
CTO på Thunderbit | Senior data scientist & ML-expert Med nästan ett decenniums erfarenhet inom maskininlärning och datavetenskap är Ke Shen alumn från Columbia University och tidigare Senior Data Scientist på Walmart Labs. Med djup, av branschen erkänd expertis inom Python, R, Java och statistik delar han väl beprövade insikter om hur man förvandlar komplexa AI-algoritmer från teori till produktionsklar arkitektur.
Innehållsförteckning
Thunderbit · AI-agent för webbdata

Extrahera data från vilken sida som helst på 1 klick

Betrodd av över 250 000 användare
gratis plan tillgänglig
Från webbsida till kalkylark
Beskriv vad du behöver — Thunderbits AI-agent samlar in det och exporterar till Excel, Google Sheets, Airtable eller Notion. Gratis att börja med.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week