nodriver je knihovna pro automatizaci prohlížeče v Pythonu od ultrafunkamsterdam, autora za undetected-chromedriver, a prezentuje se jako jeho nástupce. Její zásadní odklon je hlavně od Selenium a chromedriveru: nodriver komunikuje s Chromiem přes Chrome DevTools Protocol (CDP), bez prostředníka v podobě WebDriver binárky, a celé API je asynchronní. Playwright a Puppeteer spadají do trochu jiné kategorie. Také řídí prohlížeč přes protokol, ale nejsou potomky WebDriveru; praktické rozdíly jsou hlavně v návrhu API, balení, dodání prohlížeče a zásadách kompatibility.
Tahleta recenze se zaměřuje na nodriver 0.50.3 a netestuje ho proti živým ochranám. Zkoumal jsem nainstalovaný balíček, importy, podobu API, velikost na disku i licenci a prohlížeče jsem spouštěl jen proti stránkám servírovaným z 127.0.0.1. Žádný živý cíl, anti-bot služba ani CAPTCHA v tom nebyly. Výsledky popisují chování balíčku a to, co browser ve výchozím nastavení prozrazuje; neprokazují účinnost proti detekci.
V rámci tohohle vymezení vystoupily tři věci. Za prvé, na Pythonu 3.14 se knihovna vůbec nenaimportuje — jediný cizí bajt shodí celý balíček ještě předtím, než můžeš zavolat cokoli dalšího. Za druhé, na driver v téhle kategorii je překvapivě malý: tři deklarované závislosti plus jedna tranzitivní a v měřeném prostředí zhruba 17 MB. A za třetí, na mé vlastní stránce se nodriver od stock Playwrightu a stock Puppeteeru viditelně liší jen v jedné boolovské hodnotě — a headless user-agent přitom ve všech třech případech pořád říká HeadlessChrome. Zbytek překvapení je hlavně v licenci.
Problém s importem na Pythonu 3.14
Začněme od zjištění, které tě zasáhne jako první, protože nastane ještě před spuštěním tvého kódu. Na Pythonu 3.14 prostý import nodriver skončí chybou:
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
Selhávající soubor je automaticky generovaný cdp/network.py (v hlavičce uvádí # DO NOT EDIT THIS FILE!). Obsahuje jeden neběžný UTF-8 bajt, 0xb1, který patří ke znaku ± v komentáři #: JSON (±Inf)., a nemá deklarované kódování zdrojáku. Modul se načítá přes nodriver/__init__ → cdp/__init__ → network, takže chyba parsování import zastaví. Sken balíčku nenašel žádný další zdrojový soubor mimo UTF-8.
Hranici mezi verzemi je potřeba formulovat opatrně. Python 3.14.2 soubor odmítne, zatímco Python 3.12.13 ho načte bez opravy. Nainstalované network.py jsou v obou prostředích byteově shodné (SHA-256 ef755f41800d4efb593736f8b55b331bba68eec373ed62ece433e66e4b491cd6), takže rozdíl nevysvětlují jiné zdrojové artefakty. Tahle recenze nezkoumala přesnou změnu v tokenizéru CPythonu, která to způsobuje, a Python 3.13 jsem netestoval. Zaznamenává tedy dva měřené výsledky interpreteru, místo aby tvrdila, že každý starší Python se chová jako 3.12.
Z těchto dvou měřených koncových bodů neplyne žádný závěr o Pythonu 3.13.
Nejde o objev, ale o reprodukci. Stejný traceback z Pythonu 3.14 je uveden v nodriver issue #35 a navrhovaná oprava je v pull requestu #36. Verze 0.50.3 ten bajt stále obsahuje. Metadata na PyPI uvádějí klasifikátory jen do Pythonu 3.13 a podporu 3.14 netvrdí.
Obejití je stejně malé jako samotná chyba: používat ověřenou verzi interpreteru nebo ten jeden soubor přegenerovat do UTF-8, jak navrhuje pull request #36. Po tomhle přegenerování se balíček na 3.14 naimportuje a jde introspektovat bez další překážky. Čistý běh na 3.12.13 níže potvrzuje jednu neopravovanou cestu; Python 3.13 v této recenzi testován nebyl.
Pokud standardně používáš novější interpretery — a spousta týmů přechází na nový Python rychle — je to reálná, i když snadno opravitelné překážka. Je dobré o ní vědět, abys neztratil odpoledne kvůli SyntaxError ve فایلu, který jsi nikdy nepsal.
Co nodriver ve skutečnosti je pod kapotou
Jednověté shrnutí („CDP nativně, bez webdriveru“) zní trochu jako marketing, dokud se nepodíváš, co je v balíčku. nodriver obsahuje vlastní kompletní sadu bindingů pro DevTools Protocol: balíček nodriver.cdp má 57 modulů protokolových domén — accessibility, dom, network, page, fetch, runtime, target, storage, input, emulation a další. Právě tenhle počet modulů stojí za tvrzením „přímé CDP, žádné Selenium“. Místo toho, aby volal extertní chromedriver, který mluví WebDriverem a všechno překládá, nodriver generuje Python objekty pro CDP domény a komunikuje protokolem přímo přes WebSocket. cdp/network.py — ten soubor s chybným bajtem — je jedním z těch 57 automaticky generovaných modulů, a proto je problém v kódu, který se ručně neupravuje.
Na této protokolové vrstvě pak sedí přívětivější objektový model. Objekt Tab vystavuje 62 veřejných metod a možnosti vyhledávání jsou širší než u většiny driverů: textové hledání přes find() a find_all(), CSS přes select() a select_all() a také nativní vstupní bod xpath(). XPath, CSS a textové hledání na jednom objektu je opravdu praktické; jiné knihovny tě někdy nutí přepnout na evaluate() kvůli XPath. Konstruktor Config nabízí user_data_dir, headless, browser_executable_path, browser_args, sandbox, lang (výchozí 'en-US'), host, port, expert a **kwargs. Při tomhle průzkumu API vyvolalo Config(headless=True) 16 launch flagů Chromiumu, včetně --no-first-run, --no-default-browser-check, --remote-allow-origins=* a --homepage=about:blank. V této fázi jsem start() nespouštěl. Níže uvedené browser testy byly samostatný běh.

Knihovna sice nabízí i API orientované na obcházení detekce — metody, jejichž existenci jsem potvrdil, ale jejichž chování jsem proti žádnému cíli netestoval. Zmiňuji to jednou a dál se tím nezabývám, protože přesně ten efekt na skutečné službě je to, co jsem se záměrně rozhodl neověřovat. Neutrálně řečeno: pojmenování v nodriver je zdrženlivější než u některých „stealth“ konkurentů. Jeho příběh je architektonický — nativní CDP, čerstvý profil pro každý běh — a ne seznam názvů ve stylu detect_and_bypass. Ber to jako pozorování návrhu API, ne jako tvrzení o výsledcích.
Tahle čísla vznikla importem balíčku a introspekcí v Pythonu — inspect, procházení modulů, počítání atributů. Žádný web se při tom neoslovil. Když si to budeš chtít zopakovat, výsledky vycházejí přímo z balíčku, nejsou pocitové.
Co o sobě prozrazuje

Na jednu otázku se dá odpovědět bez jakékoli živé ochrany: když nodriver řídí browser, co o sobě ten browser řekne stránce, kterou navštíví? Napsal jsem stránku, která čte zjevné věci — navigator.webdriver, user-agent, platformu, jazyky, počet pluginů a hardware zdrojů, podobu window.chrome, odpověď Permissions API, rozměry okna a obrazovky — naservíroval ji z 127.0.0.1 a nasměroval na ni čtyři stacky: nodriver, Botasaurus a jako kontrolu stock Playwright a stock Puppeteer. Všechny čtyři řídily stejnou verzi Chromia (Chrome for Testing 151.0.7922.10), takže cokoli se liší, je dané knihovnou, ne browserem. Headless i headed režim, tři běhy každý. Každá níže uvedená hodnota platila ve všech třech.
| Stack | Režim | navigator.webdriver | Token v user-agentu | navigator.languages |
|---|---|---|---|---|
| nodriver 0.50.3 | headless | false | HeadlessChrome/151.0.0.0 | ["en-US"] |
| nodriver 0.50.3 | headed | false | Chrome/151.0.0.0 | ["en-US"] |
| Botasaurus 4.0.92 | headless / headed | false | HeadlessChrome/151 / Chrome/151 | ["en-US"] |
| Playwright 1.56.0 | headless / headed | true | HeadlessChrome/151 / Chrome/151 | ["en-US","en"] |
| Puppeteer 24.16.0 | headless / headed | true | HeadlessChrome/151 / Chrome/151 | ["en-US"] |
Nejzřetelnější rozdíl je ta jediná boolovská hodnota. Při těchto výchozích launch konfiguracích vrací nodriver navigator.webdriver jako false, zatímco stock Playwright i Puppeteer hlásí true, a to v headless i headed režimu. Test ukazuje rozdíl na úrovni konfigurace; nedokazuje, že hodnota plyne jen z absence WebDriver binárky.
Ve všech čtyřech stackách je vlastnost stále nativní getter pro Navigator.prototype — function get webdriver() { [native code] } — nikdy ne vlastní property instance ani přepsaná funkce. Vlastnost nebyla po načtení přepsána JavaScriptem na stránce. Test neurčil, který launch argument nebo zdrojová cesta stojí za odlišnou hodnotou.
Druhá věc už trochu koriguje marketing. V headless režimu user-agent nodriveru pořád hlásí HeadlessChrome/151.0.0.0 — přesně jako stock Puppeteer a stock Playwright. V headed režimu se změní na Chrome/151.0.0.0, opět stejně. Pokud jsi čekal, že anti-detection knihovna ve výchozím nastavení maskuje nejslavnější samoidentifikační řetězec browserové automatizace, tak nemaskuje. To by sis musel nastavit sám.
Skoro všechno ostatní bylo ve všech čtyřech stackách stejné, a je dobré to říct přímo, protože to zužuje příběh — každý z níže uvedených parametrů měl u nodriveru, Botasauru, Playwrightu i Puppeteeru stejnou hodnotu:
| Vlastnost | Hodnota shodná u všech čtyř stacků |
|---|---|
platform | MacIntel |
vendor | Google Inc. |
| Pluginy | pět |
| MIME typy | dva |
pdfViewerEnabled | true |
| Logická jádra | dvanáct |
| Hlásená paměť zařízení | 16 GB |
| Dotykové body | nula |
window.chrome | přítomen, s app/csi/loadTimes a bez runtime |
| Řetězec rendereru WebGL | shodný ve všech čtyřech |
Starý známý rozpor mezi Permissions API a Notification.permission se nikde neobjevil — všichni čtyři hlásili default a prompt ve shodě. Prošel jsem také document a window kvůli zbytkům ve stylu cdc_, které bývaly známé u starších WebDriver stacků: všude prázdno.
Jediné místo, kde nodriver působí spíš jako „holý“ automatizovaný browser než jako kontrolní varianta, je geometrie okna. Headless nodriver hlásí outerWidth/outerHeight 0×0 na obrazovce 800×600; headless Playwright hlásí 1280×720, protože si pro tebe nastavuje viewport. Puppeteer je spolu s nodriverem na 0×0. To je rozdíl ve výchozí konfiguraci, ne v možnostech, a lze ho změnit.
Ještě jedna důležitá věc před nasazením: nodriver nedodává vlastní browser, takže ve výchozím stavu používá to Chrome, které už je v počítači nainstalované. U mě to znamenalo automatické nalezení /Applications/Google Chrome.app — Chrome 150.0.7871.187 — a user-agent prozrazoval právě tuhle verzi, ne nějakou připnutou. Verze browseru, kterou tvoje infrastruktura prozradí, je prostě verze, kterou tvoje infrastruktura zrovna má.
Říkejme tomu přesně. Je to záznam toho, co automatizovaný stack prozradí, když od něj nikdo nechce nic skrývat — užitečné pro obránce i pro každého, kdo chce vědět, co jeho nástroje vysílají navenek. Není to však měření toho, jestli to má nebo nemá vliv na konkrétní službu. To jsem netestoval a žádný řádek výše by se neměl vykládat jako tvrzení o výsledku.
Dokáže skutečně dostat obsah ze stránky?
Prozrazovat o sobě něco je jedna věc; vrátit správné HTML je druhá. Nodriver jsem spustil proti stejné testovací sestavě se třemi typy obsahu, kterou používá i zbytek tohoto benchmarkového repozitáře, takže čísla odpovídají všem ostatním měřeným nástrojům. Stránka obsahuje tři prvky: A, statický odkaz s označením, které je doslova přítomné v servírovaných bajtech; B, uzel vytvořený inline skriptem během parsování, přičemž jeho označení i URL jsou složeny z fragmentů tak, že je odhalí jen spuštění JavaScriptu; a C, uzel vložený 800 ms po load eventu, složený stejným způsobem. Třída C je ta záludná — čtení při load eventu ji nevidí.
| Stack | Výchozí čtení | S explicitním čekáním |
|---|---|---|
| nodriver 0.50.3 | 2 z 3 (A + B, chybí C) | 3 z 3 |
| Botasaurus 4.0.92 | 2 z 3 | 3 z 3 |
| Playwright 1.56.0 | 2 z 3 | 3 z 3 |
| Puppeteer 24.16.0 | 2 z 3 | 3 z 3 |
nodriver dopadá přesně tam, kde dopadají i velcí hráči. browser.get() následované přímo tab.get_content() je snímek po loadu: JavaScript vykreslí správně — to dokazuje třída B, protože v servírovaných bajtech nikde neexistuje — ale cokoli vložené až po loadu minout může. Přidáš-li tab.select("#delayed-injected"), získáš všechny tři. Stejná chyba, stejné řešení jako u Playwrightu a Puppeteeru. Stabilní ve třech opakováních i ve třech samostatných bězích celé sady, bez výpadků.
Prošel jsem i zpoždění injektáže, abych zjistil, kde výchozí čtení přestává stačit. Nodriver přestává třídu C vidět, jakmile se injektáž odehraje 100 ms nebo později po loadu — stejná hranice jako u obou stock kontrol. (Botasaurus je tady výjimka a zároveň skutečně zajímavý rozdíl mezi oběma anti-detect knihovnami: jeho get() implicitně čeká na kompletní načtení stránky, takže výchozí čtení zachytí i injektáž ve 300 ms. Stojí ho to asi 250 ms na jednu navigaci.)
Samotné čekání má jednu zvláštnost, se kterou je dobré počítat. V tomhle sweepu tab.select() u nodriveru neskákalo plynule podle zpoždění injektáže, ale po hrubých pollingových krocích:
| Zpoždění třídy C | 0 ms | 100 ms | 400 ms | 800 ms | 1500 ms |
|---|---|---|---|---|---|
nodriver select() | 124–152 ms | 1132–1141 | 1128–1129 | 2132–2177 | 2138–2150 |
Puppeteer waitForSelector | 113–129 ms | 203–216 | 512–516 | 911–919 | 1608–1611 |
V tomhle testu vedla injektáž po 100 ms k asi 1,1 s pro select(). Nainstalovaná smyčka provádí await self a po neúspěchu pak await self.sleep(0.5); změřený kombinovaný cyklus se tu pohyboval kolem jedné sekundy, ale await self není doložený jako univerzální fixní spánek. Každý testovaný opožděný uzel byl nalezen. Puppeteerův waiter tyto zpoždění sledoval přesněji. U mnoha po sobě jdoucích čekání se rozdíl může nasčítat, i když tahle recenze netestovala produkční stránku se třiceti selektory.
Další místo, kde se asynchronní a lehké pojetí potkává s realitou, je start prohlížeče. Spuštění browseru dostalo nodriver do stejné ligy jako Botasaurus a Puppeteer, ale výrazně za Playwright:
| Stack | Spuštění browseru napříč běhy |
|---|---|
| nodriver 0.50.3 | 910–1583 ms |
| Botasaurus 4.0.92 | 986–1151 ms |
| Puppeteer 24.16.0 | 969–1008 ms |
| Playwright 1.56.0 | 282–365 ms |
Jakmile běží, nodriver je z té čtveřice nejrychlejší při navigaci a čtení: 119–129 ms. Lehké řízení, obyčejný start.
Instalace a footprint: opravdu silná stránka
Tady si nodriver zaslouží označení „úsporný“, a je to ověřitelný fakt o instalaci, ne něco o scrappingu. Tohle řeší čisté pip install nodriver:
| Fakt o instalaci | nodriver 0.50.3 |
|---|---|
| Deklarované přímé runtime závislosti | tři — websockets, mss, deprecated |
| Vyřešená tranzitivní závislost | wrapt (přes deprecated) |
Změřená velikost site-packages celkem | asi 17,2 MB ve 6 dist-info adresářích, včetně prostředí pip |
| Podíl samotného nodriveru | 3,7 MB |
| numpy / lxml | ani jedno |
| Binárka browseru při instalaci | nic se nestahuje |
Na kategorii, která obvykle táhne celé renderovací prostředí, je to mimořádně lehký zásah.
Rozdíl mezi přímými a tranzitivními balíčky je důležitý pro údržbu. Metadata nodriveru si říkají o mss, websockets a deprecated; wrapt přichází proto, že ho vyžaduje deprecated. Šestý dist-info adresář v prostředí o velikosti 17,2 MB je pip, který už byl součástí virtuálního prostředí. Tvrzení „17,2 MB ve šesti distribucích“ tedy popisuje změřené prostředí, zatímco „čtyři přidané runtime balíčky“ popisuje výsledek instalace. Jsou to související, ale ne zaměnitelné počty.
Kontrast, díky kterému to číslo něco znamená: na stejném stroji váží sesterský framework Botasaurus 122,3 MB ve 44 balíčcích — zhruba sedmkrát větší footprint. To je rozdíl mezi úzce zaměřeným CDP driverem a frameworkem se vším v balení, a má to dvě strany. nodriver ti dává tenký, čitelný strom závislostí, který lze reálně auditovat; Botasaurus ti dá víc hned po instalaci, ale zaplatíš za to místem na disku i povrchem závislostí. V abstraktu není lepší ani horší — záleží na tom, jestli chceš driver nebo framework — ale pokud ti záleží na malém a dobře čitelném instalčním balíčku, nodriver je v tomhle směru nezvykle čistý.
Malý footprint na disku ale neznamená malý import. Na kopii pro Python 3.14 s opraveným bajtem trval import nodriver asi 158 ms (medián z nových subprocess importů, zhruba 151–199 ms). Neošetřený import na 3.14 selže, takže pro něj timing neuvádím. Všechny 57 CDP modulů se načítají eager způsobem a rezidentní paměť po importu byla 31,5–31,9 MB ještě předtím, než existoval jakýkoli proces Chromia. Spuštěný browser by přidal mnohem víc a do tohohle měření paměti nebyl započítán.
K těmhle číslům patří dvě důležité poznámky. Pocházejí z jednoho stroje — macOS arm64 — a velikost i timing importu byly měřeny na Pythonu 3.14 proti kopii s jedním bajtem opraveným, protože neopravovaný balíček se na tomhle interpreteru vůbec nenaimportuje. Na podporovaném Pythonu ten patch nepotřebuješ: na 3.12.13 jsem potvrdil čistý import bez úprav, a právě odtud pocházejí všechny browserové výsledky. A v runtime je stále potřeba skutečná binárka Chrome, Chromium, Edge nebo Brave — nodriver řídí existující browser, nedodává vlastní, takže náklad na instalaci leží mimo těch 17 MB a jak ukázala sekce o prozrazování, verze Chromia je přesně ta, kterou máš v počítači.
Licence je skutečné rozhodnutí o adopci

Většina recenzí bezplatného nástroje bere „je open source“ jako konec licenčního rozhovoru. U nodriveru je to naopak začátek, protože licence je AGPL-3.0 — potvrzená jak v LICENSE.txt v wheelu, tak v spdx_id v repozitáři. Je to silná síťová copyleft licence a je to podstatně jiný závazek než u sousedů.
Nejčastější alternativy, se kterými se nodriver porovnává, jsou permisivní, a právě proto je rozdíl dobře vidět:
| Nástroj | Licence | Když provozuješ upravenou kopii jako síťovou službu |
|---|---|---|
| nodriver | AGPL-3.0 | Sekce 13 může požadovat, aby provozovatel nabídl vzdáleným uživatelům odpovídající zdroj upravené verze |
| Playwright | Apache-2.0 | žádná ekvivalentní síťová copyleft klauzule |
| Puppeteer | Apache-2.0 | totéž |
| Botasaurus | MIT | totéž |
Rozsah je důležitý. Sekce 13 AGPL se týká upravené verze programu, která se používá pro vzdálenou síťovou interakci. Tahle recenze nerozhoduje, zda do rozsahu spadá i okolní servisní kód, ani neřeší hraniční případy interního použití nebo hranic firmy. Pokud hostingový produkt upravuje nodriver, zkontrolujte text licence i architekturu s právníkem. Tohle je technický signál pro adopci, ne právní rada.
Netvrdím, že AGPL je dobrá nebo špatná — copyleft je legitimní volba a spousta seriózních projektů ho používá. Jen upozorňuji, že „nodriver je zdarma a open source“ je pravda, ale neúplná. Povinnost je reálná, v této části ekosystému je jiná než permisivní standard, a patří do rozhodování, ne do zjednodušeného „zdarma“. (Na PyPI mimochodem vůbec není licence jako classifier; text AGPL je ve wheelu a SPDX ID je v repozitáři, takže na index balíčků se v tomhle nespoléhejte.)
Metadata s datem
Body v čase z repozitáře a PyPI, přímo z GitHub API a PyPI:
| Fakt | Hodnota k 14. červenci 2026 |
|---|---|
| Stars | 4,511 |
| Forky | 422 |
| Otevřené issue | 14 |
| Vznik | únor 2024 |
| Poslední push | květen 2026 |
| Nejnovější PyPI release | 0.50.3 |
| Wheel | čistý Python py3-none-any |
requires-python | >=3.9 |
| Klasifikátory Pythonu | 3.7–3.13 |
Signály údržby jsou smíšené: do repozitáře přibyl push v květnu 2026, zatímco poslední měřený balíček stále obsahoval problém s importem na Pythonu 3.14 a navrhovaná oprava ještě nebyla podle data výzkumu vydána. Počet stars ani otevřených issue sám o sobě neříká, jestli je tahle kadence pro tebe dostatečná.
Klady a zápory
Klady:
- Malá a čitelná instalace: tři deklarované závislosti plus tranzitivní
wrapt, asi 17,2 MB v šestidist-infoadresářích v měřeném prostředí (jeden jepip), žádné numpy/lxml a žádné stahování browseru při instalaci. - Opravdu CDP nativní: obsahuje vlastní 57modulový binding na DevTools Protocol a mluví protokolem přímo, bez chromedriver/Selenium binárky uprostřed.
- Široké možnosti vyhledávání prvků v
Tab(62 veřejných metod) s nativním XPath, CSS i textovým vyhledáváním — není nutné kvůli XPath přecházet na rawevaluate(). - Asynchronní od návrhu, čerstvý profil pro každý běh a čistý
Configpro běžné volby (headless, cesta k binárce, argumenty, jazyk, porty). - Vytáhne obsah ze stránky vytvořené JavaScriptem stejně dobře jako velcí hráči: 2 ze 3 tříd obsahu při výchozím čtení, 3 ze 3 s explicitním čekáním — stejně jako stock Playwright a Puppeteer na stejném fixture, stabilní ve třech bězích. Navigace a čtení byly nejrychlejší z celé čtveřice, 119–129 ms.
- Ve výchozím nastavení hlásí
navigator.webdriverjakofalse, zatímco stock kontroly hlásítrue, a přitom vlastnost nepatchuje — descriptor zůstává nativním getterem browseru. - Jasná architektonická identita jako CDP-native nástupce undetected-chromedriver.
Zápory:
- Na Pythonu 3.14 se po instalaci nenaimportuje — jeden neběžný UTF-8 bajt v
cdp/network.pyvyhodí při importuSyntaxError. Reprodukováno proti otevřenému issue #35, ve verzi 0.50.3 stále neopraveno. Připni si ≤3.13 (na 3.12.13 jsem ověřil čistý import) nebo soubor přegeneruj. - AGPL-3.0 je pro každého, kdo provozuje upravenou kopii jako síťovou službu, skutečný faktor při adopci — přísnější než Apache/MIT u alternativ.
- Malý diskový footprint neznamená malý import: asi 158 ms cold-start a asi 31,5 MB rezidentně ještě před existencí browseru, protože se všech 57 CDP modulů načítá hned.
tab.select()polluje s půlsekundovým back-offem, takže krátká čekání se zaokrouhlují nahoru — čekání 100 ms stojí asi 1,1 s, kde Puppeteerův waiter stojí asi 210 ms. Funguje to správně, ale při mnoha malých čekáních to narůstá.- Headless běhy pořád ve výchozím nastavení oznamují
HeadlessChromev user-agentu, přesně jako stock kontroly; nic ve výchozí konfiguraci neskrývá nejviditelnější samoidentifikační řetězec. - Stále potřebuje skutečnou binárku Chrome/Chromium/Edge/Brave v runtime; lehká pip instalace je jen polovina příběhu a verze Chromia, kterou máš v počítači, je ta, kterou prozradíš navenek.
- Účinnost proti jakémukoli anti-bot systému zde nebyla ověřena — celý stealth předpoklad zůstává záměrně netestovaný.
To, co jsem netestoval, a proto o tom nemohu tvrdit nic: paměť na úrovni jednotlivých tabů, latence CDP round-tripů, práce s profily, propustnost ve větším měřítku, Linux ani Windows, konkrétně Python 3.13 a — to hlavní — účinnost v reálném světě proti živé anti-bot ochraně. Všechna browserová data v téhle recenzi pocházejí jen z fixture na 127.0.0.1. Všechna čísla jsou z jednoho stroje (macOS arm64); browserová měření jsou na Pythonu 3.12.13 bez opravy a starší údaje o footprintu a import timing jsou na Pythonu 3.14 s jedním bajtem opraveným.
Také jsem nespouštěl matici upgrade browseru, takže kompatibilita s budoucími verzemi Chromia zůstává provozní kontrolou pro adoptera, ne výsledkem téhle recenze.
Pro koho je a kdo by měl jít jinam
nodriver dává smysl, pokud chceš lehký, asynchronní, CDP-nativní driver pro živé Chromium a nevadí ti spravovat browser, aktualizace a runtime. Malý strom závislostí je snadněji auditovatelný a design orientovaný na CDP sedí na řízení na úrovni protokolu. Vhodnost do kontejneru zůstává neověřená: tahle recenze netestovala Linux image, instalaci browseru, sdílené knihovny, sandboxing ani úklid procesů.
Dvě skupiny by se měly podívat jinam. Pokud jedeš na Pythonu 3.14 a nechceš si připínat starší interpreter nebo patchovat vendrovaný soubor, počkej na vydání opravy — import je dnes tvrdý stop. A pokud je pro tvůj způsob nasazení AGPL-3.0 problém — třeba hostovaná služba s neveřejnými úpravami — samotná licence je dostatečný důvod zvážit permisivní alternativu ještě před tím, než na tom začneš stavět. Ani jedno není výtka kódu; jsou to omezení, která je lepší znát teď než při compliance kontrole.
Taky ho přeskoč, pokud ve skutečnosti potřebuješ data, ne browser, který budeš ručně řídit. nodriver ti dává skriptovatelný tab a 62 metod; převést vykreslenou stránku na čisté strukturované záznamy je pořád tvůj kód. To je jiná práce a právě v tom místě přichází řízené API.
Alternativy a kde zapadá Thunderbit
Nejprve upřímné vymezení: nodriver je zdarma, pod AGPL a self-hosted. Ty provozuješ browser, spravuješ aktualizace, vlastníš runtime i všechny jeho chyby. Pro vývojáře, který chce přesně tuhle kontrolu, se s knihovnou, kterou už má, cenově nemůže měřit žádná managed služba.
V open source porovnávej podle úkolu, ne podle loga. Pokud zvažuješ ovladače reálného browseru, naše srovnání Playwright vs Puppeteer pokrývá dvě zřejmé těžké váhy, vedle kterých nodriver stojí. Scrapling je na ose stealthu nejbližší pythoní soused, pokud právě to je tvůj důvod ke zvažování. Pro výstup připravený pro LLM místo přímého řízení browseru Crawl4AI vykresluje stránky a vrací Markdown, a Scrapy zůstává referenčním frameworkem pro velké crawly bez browseru. Když porovnáváš víc z nich najednou, souhrn open-source scraperů staví kategorie vedle sebe.
Zveřejnění: tenhle článek publikuje Thunderbit. Thunderbit je řízená služba pro extrakci dat, takže porovnání je podle úkolu, ne podle architektury. nodriver ti dává vrstvu řízení browseru, kterou hostuješ a programuješ ty; Thunderbit řeší renderování a vrací obsah stránky nebo záznamy ve strukturovaném schématu jako službu. Nodriver použij, když je požadavek kontrola browseru na úrovni protokolu a self-hosting. Řízený extractor zvaž, když je důležitější výstupní záznam a provozní předání než vlastnictví browseru. Měnitelné detaily o endpointu, kreditech a limitech batchů patří na stránku s cenami, ne dovnitř benchmarku knihovny.
Kompenzace je v tom, kde leží práce: nodriver nechává správu browseru i údržbu runtime na tvé straně bez poplatku za každý request; managed API tu vrstvu přebírá a účtuje za volání.
Vyzkoušet Thunderbit pro extrakci webových dat
Verdikt
Použij nodriver, pokud chceš malý, asynchronní, CDP-native driver pro Chromium, ověřil sis ho na své verzi Pythonu (na 3.12.13 tu import proběhl čistě) a zkontroloval sis AGPL-3.0 s ohledem na způsob distribuce. Jeho runtime graf přidal čtyři balíčky, změřené prostředí zabralo asi 17 MB a knihovna dodává 57 CDP modulů, Tab se 62 metodami a nativním XPath. Na tomhle fixture vrátila defaultně 2 ze 3 tříd obsahu a s čekáním 3 ze 3, což odpovídá stock Playwrightu i Puppeteeru.
Je ale potřeba počítat s omezeními. Na Pythonu 3.14 se vůbec nenaimportuje, dokud neopravíš jeden neběžný UTF-8 bajt — zdokumentovaná, stále otevřená chyba, ne záhada, ale tvrdý stop v okamžiku, kdy na ni narazíš. Licence je AGPL-3.0, což je pro každého, kdo provozuje upravenou kopii jako službu, skutečné rozhodnutí, ne formalita. Malá instalace neznamená malý import, protože se všechny CDP moduly načítají hned, a select() s půlsekundovým pollingem prodlouží krátká čekání zhruba na sekundu. Na otázce defaultního prozrazování, na kterou jsem si skutečně mohl odpovědět, je obraz užší, než marketing naznačuje: od stock Puppeteeru se liší jedna boolovská hodnota, headless user-agent pořád říká HeadlessChrome a všechno ostatní, co jsem měřil, bylo u všech čtyř stacků stejné. A celý předpoklad anti-detekce — důvod, proč spousta lidí nodriver vůbec najde — je něco, co jsem záměrně netestoval. Inventarizoval jsem knihovnu a řídil ji proti stránce na vlastním stroji místo toho, abych ji stavěl proti živým ochranám, a radši ti to řeknu na rovinu, než abych ti předal tvrzení o obejití, za které nemůžu ručit. U otázek, na které jsem odpovědět mohl, je nodriver dobře postavený a neobvykle lehký driver se dvěma ostrými hranami — bariérou danou verzí Pythonu a copyleft licencí — které je dobré vidět předem.
Vyzkoušet Thunderbit pro extrakci webových dat Get Started Free
Často kladené otázky
Proč na Pythonu 3.14 selže import nodriver?
Protože cdp/network.py obsahuje neběžný bajt ± bez deklarace kódování zdrojáku. Python 3.14.2 soubor odmítne a přeruší transitive import; Python 3.12.13 naopak byteově identický soubor bez opravy naimportuje. Tahle recenze neizolovala přesnou změnu v interpreteru a netestovala Python 3.13. Upstream stopa je nodriver issue #35 a pull request #36. Použij verzi, kterou máš ověřenou, nebo soubor přegeneruj do UTF-8.
Co mi přinese „CDP-native, bez webdriveru“ a co stojí instalace?
nodriver obsahuje 57 modulů DevTools Protocol domén a mluví přes CDP po WebSocketu místo toho, aby volal chromedriver přes Selenium. Tab nabízí 62 metod, včetně nativního XPath. Metadata balíčku deklarují tři runtime požadavky (websockets, mss, deprecated); při jejich řešení přibude wrapt. Změřené prostředí zabralo asi 17,2 MB v šesti dist-info adresářích, včetně pip, bez numpy, lxml nebo stažené browser binárky. Dvě poznámky: opravená kopie se importovala asi za 158 ms, protože všech 57 CDP modulů se načítá eager způsobem, a browser z rodiny Chrome je stále potřeba zvlášť.
Záleží na licenci AGPL-3.0 pro můj projekt? Záleží na tom, jak to distribuujete. AGPL-3.0 je síťová copyleft licence: pokud provozujete upravenou verzi nodriveru jako službu pro jiné lidi, musíte jim nabídnout odpovídající zdrojové kódy té upravené verze. Pro osobní skript nebo interní nástroj, který nikdy nevystavíte ven, je to bez dopadu. Pro hostovaný komerční produkt postavený na upraveném nodriveru je to reálná otázka pro compliance — a je to přísnější než Apache-2.0 a MIT u srovnatelných nástrojů.
Co nodriver prozrazuje o sobě — a znamená to, že porazí Cloudflare?
První část jsem změřil, druhou ne, a ten rozdíl je důležitý. Na stránce, kterou jsem servíroval z 127.0.0.1, a při řízení stejného buildu Chromia jako u kontrol: navigator.webdriver vrací false, zatímco stock Playwright i stock Puppeteer hlásí true. Tato hodnota se nastavuje při startu browseru, ne patchováním vlastnosti — descriptor je pořád nativní getter Chromia. Kromě téhle boolovské hodnoty se skoro všechno shodovalo s kontrolami: stejný string platformy, pět pluginů, dvanáct jader, 16 GB hlášené paměti zařízení, stejná podoba window.chrome, žádný rozpor v Permissions API a žádné zbytky ve stylu cdc_ na document ani window. Headless běhy pořád v user-agentu hlásí HeadlessChrome/151.0.0.0, stejně jako obě kontroly — to za tebe maskované není. Nic z toho ale neříká, jestli to funguje proti skutečné anti-bot službě. Nikdy jsem nodriver neposlal na živý web, nikdy jsem neoslovil anti-bot službu a nikdy jsem neotestoval CAPTCHA; to bylo záměrně mimo rozsah. Tabulka prozrazování výše říká, co stack oznamuje. Neříká nic o tom, kdo poslouchá nebo co s tím udělá.
Zvládá nodriver správně JavaScriptem renderovaný obsah?
Ano, s obvyklou výhradou ohledně toho, kdy čteš. Na fixture se třemi třídami obsahu vrátilo výchozí browser.get() + tab.get_content() hodnotu 2 ze 3 — JavaScript spouští správně (sync-injektovaná třída v servírovaných bajtech nikde není a přesto se vrátila), ale čte v okamžiku load eventu, takže mine cokoli vložené až potom. Přidání tab.select("#delayed-injected") vrátilo 3 ze 3. Je to stejné jako u stock Playwrightu i stock Puppeteeru na stejné stránce. Počítej ale s jednou zvláštností: select() polluje s půlsekundovým back-offem, takže čekání 100 ms stojí asi 1,1 s.


