Recenzja nodriver: mały sterownik CDP, który nie importuje się w Pythonie 3.14

Ostatnia aktualizacja: August 18, 2026
Recenzja nodriver: mały sterownik CDP, który nie importuje się w Pythonie 3.14
Podsumowanie AI
nodriver to biblioteka automatyzacji przeglądarki w Pythonie od ultrafunkamsterdam, autora undetected-chromedriver, i przedstawiana jako następca tamtego projektu. Jej zasadnicza różnica względem Selenium i chromedrivera jest architektoniczna: nodriver komunikuje się z Chromium przez Chrome DevTools Protocol (CDP), bez udziału binarki WebDriver, a jego API jest asynchroniczne. Playwright i Puppeteer należą do innej kategorii porównawczej. Te narzędzia również sterują przeglądarką przez protokół, a nie wywodzą się z WebDrivera; praktyczne różnice dotyczą projektu API, sposobu dystrybucji, dostarczania przeglądarki i polityki zgodności. Ta recenzja analizuje nodriver 0.50.3, zamiast testować go na żywych zabezpieczeniach.

recenzja nodriver, web scraping, open source, benchmark

nodriver to biblioteka do automatyzacji przeglądarki w Pythonie stworzona przez ultrafunkamsterdam, autora undetected-chromedriver, i przedstawiana jako następca tamtego projektu. Jej zasadnicza różnica względem Selenium i chromedrivera jest architektoniczna: nodriver komunikuje się z Chromium przez Chrome DevTools Protocol (CDP), bez udziału binarki WebDriver, a jego API jest asynchroniczne. Playwright i Puppeteer należą do innej kategorii porównawczej. Te narzędzia również sterują przeglądarką przez protokół, a nie wywodzą się z WebDrivera; praktyczne różnice dotyczą projektu API, sposobu dystrybucji, dostarczania przeglądarki i polityki zgodności.

Ta recenzja analizuje nodriver 0.50.3, zamiast testować go na żywych zabezpieczeniach. Sprawdziłem zainstalowany pakiet, importy, powierzchnię API, zajętość dysku i licencję, a przeglądarki uruchamiałem wyłącznie na stronach serwowanych z 127.0.0.1. Nie brało w tym udziału żadne prawdziwe źródło, usługa anty-bot ani CAPTCHA. Wyniki opisują zachowanie pakietu i domyślne ujawnianie informacji przez przeglądarkę; nie dowodzą skuteczności ukrywania automatyzacji.

W ramach tego zakresu najbardziej rzuciły się w oczy trzy rzeczy. Po pierwsze, w Pythonie 3.14 biblioteka w ogóle się nie importuje — jeden zbędny bajt wywraca cały pakiet, zanim zdążysz wywołać cokolwiek. Po drugie, jak na sterownik z tej kategorii jest zaskakująco mała: trzy zadeklarowane zależności plus jeden rozwiązany pakiet pośredni i około 17 MB w badanym środowisku. Po trzecie, na mojej własnej stronie jedyną właściwością, w której nodriver wyraźnie różni się od stockowego Playwrighta i stockowego Puppeteera, jest jedna flaga logiczna — a w trybie headless string user-agent nadal wszędzie zawiera HeadlessChrome. Reszta niespodzianek kryje się w licencji.

Problem z importem w Pythonie 3.14

Zacznijmy od problemu, na który trafisz jako pierwszy, bo pojawia się zanim uruchomi się jakikolwiek twój kod. W Pythonie 3.14 zwykłe import nodriver kończy się niepowodzeniem:

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

Plik, który powoduje błąd, to generowany automatycznie cdp/network.py (w nagłówku ma # DO NOT EDIT THIS FILE!). Zawiera jeden bajt spoza UTF-8, 0xb1, odpowiadający znakowi ± w komentarzu #: JSON (±Inf)., i nie ma deklaracji kodowania źródła. Moduł ładuje się przez nodriver/__init__ → cdp/__init__ → network, więc błąd parsowania zatrzymuje import. Skan pakietu nie wykazał żadnego innego pliku źródłowego spoza UTF-8.

Granica wersji wymaga precyzyjnego sformułowania. Python 3.14.2 odrzuca ten plik, natomiast Python 3.12.13 importuje go bez poprawek. Zainstalowane wersje network.py były w tych środowiskach identyczne bajtowo (SHA-256 ef755f41800d4efb593736f8b55b331bba68eec373ed62ece433e66e4b491cd6), więc różne artefakty źródłowe nie tłumaczą wyniku. Ta recenzja nie izolowała dokładnej zmiany w tokenizerze CPythona, która za to odpowiada, i nie testowała Pythona 3.13. Zawiera dwa zmierzone wyniki interpretera, zamiast twierdzić, że każda wcześniejsza wersja Pythona zachowuje się jak 3.12.

Z tych dwóch punktów pomiarowych nie wynika nic o Pythonie 3.13.

To odtworzenie, a nie odkrycie. Ten sam traceback z Pythona 3.14 jest opisany w issue #35 nodriver, a proponowana poprawka znajduje się w pull request #36. Wersja 0.50.3 nadal zawiera ten bajt. Metadane PyPI podają klasyfikatory tylko do Pythona 3.13 i nie deklarują obsługi 3.14.

Obejście jest równie małe jak sam błąd: użyj przetestowanej wersji interpretera albo ponownie zakoduj ten jeden plik do UTF-8, zgodnie z propozycją z pull requestu #36. Po takim zabiegu pakiet importuje się i daje się introspekcyjnie sprawdzić w 3.14 bez pojawienia się drugiej blokady. Czysty przebieg na 3.12.13 poniżej potwierdza jedną niepoprawianą ścieżkę; Python 3.13 nie był w tej recenzji testowany.

W Pythonie 3.12.13 import nodriver działa bez żadnej poprawki. Wszystkie późniejsze liczby dotyczące sterowania przeglądarką pochodzą z tej instalacji. Wyniki dotyczące rozmiaru i czasu importu odnoszą się do kopii z jednym ponownie zakodowanym bajtem na 3.14 i są tak opisane. Python 3.13 pozostaje tu nietestowany; jeden wynik z 3.12 nie obejmuje całego wcześniejszego zakresu.

Jeśli domyślnie pracujesz na nowoczesnych interpreterach — a wiele zespołów szybko przesiada się na nowe wydania Pythona — to jest to realna, choć banalnie naprawialna przeszkoda. Warto wiedzieć, że istnieje, żeby nie stracić popołudnia na SyntaxError w pliku, którego nigdy nie pisałeś.

Czym nodriver jest naprawdę, pod maską

Jednozdaniowy opis („CDP-native, no webdriver”) brzmi jak marketing, dopóki nie zobaczysz, co faktycznie siedzi w wheelu. nodriver zawiera własny komplet wiązań do DevTools Protocol: pakiet nodriver.cdp obejmuje 57 modułów domen protokołu — accessibility, dom, network, page, fetch, runtime, target, storage, input, emulation i kolejne. To właśnie ta liczba modułów stoi za deklaracją „direct CDP, no Selenium”. Zamiast uruchamiać binarkę chromedrivera, która mówi WebDriverem i tłumaczy polecenia, nodriver generuje obiekty Pythona dla domen CDP i sam komunikuje się protokołem przez WebSocket. cdp/network.py — plik z błędnym bajtem — jest jednym z tych 57 generowanych modułów, dlatego usterka siedzi w kodzie generowanym, a nie ręcznie edytowanym.

Na tej warstwie protokołu zbudowano wygodniejszy model obiektowy. Obiekt Tab udostępnia 62 publiczne metody, a zakres wyszukiwania elementów jest szerszy niż w wielu innych sterownikach: wyszukiwanie tekstu przez find() i find_all(), CSS przez select() i select_all() oraz natywne xpath(). XPath, CSS i wyszukiwanie tekstowe na jednym obiekcie to naprawdę wygodne rozwiązanie; inne biblioteki czasem zmuszają do zejścia do evaluate() przy XPath. Konstruktor Config udostępnia user_data_dir, headless, browser_executable_path, browser_args, sandbox, lang (domyślnie 'en-US'), host, port, expert oraz **kwargs. Podczas inwentaryzacji API wywołanie Config(headless=True) generowało 16 flag uruchomieniowych Chromium, w tym --no-first-run, --no-default-browser-check, --remote-allow-origins=* oraz --homepage=about:blank. Na tym etapie nie wywoływałem start(). Testy przeglądarki opisane niżej były osobnym przebiegiem.

System diagram: Direct CDP Control Path

Biblioteka rzeczywiście ma też powierzchnię API ukierunkowaną na unikanie wykrycia — metody, których istnienie potwierdziłem, ale których działania nie testowałem na żadnym serwisie. Zaznaczam to raz i idę dalej, bo wpływ tych metod na realną usługę jest dokładnie tym, czego nie chciałem sprawdzać. Neutralnie można powiedzieć tyle: nazewnictwo nodriver jest bardziej powściągliwe niż u niektórych konkurentów ze „stealth” w nazwie. Jego historia jest architektoniczna — CDP-native, świeży profil przy każdym uruchomieniu — a nie zbiorem metod w stylu detect_and_bypass. Traktuj to jako obserwację projektu API, nie jako twierdzenie o skuteczności.

Te liczby pochodzą z importu pakietu i introspekcji w Pythonie — inspect, przechodzenie po modułach, zliczanie atrybutów. Nie dotknięto żadnej strony. Jeśli chcesz to odtworzyć, wartości wynikają wprost z pakietu; to nie kwestia wrażeń.

Co sam o sobie ujawnia

Measured results chart: Browser disclosure fields in the tested stacks

Pytanie, na które da się odpowiedzieć bez wychodzenia poza bezpieczne warunki: co przeglądarka sterowana przez nodriver sama o sobie zdradza stronie, którą odwiedza? Zbudowałem stronę odczytującą oczywiste sygnały — navigator.webdriver, user-agent, platformę, języki, liczbę wtyczek i elementów sprzętowych, kształt window.chrome, odpowiedzi Permissions API, geometrię okna i ekranu — podałem ją z 127.0.0.1 i skierowałem na nią cztery stosy: nodriver, Botasaurus oraz stockowe Playwright i Puppeteer jako kontrolę. Wszystkie cztery sterowały tym samym buildem Chrome (Chrome for Testing 151.0.7922.10), więc jeśli coś się różni, to biblioteka, a nie przeglądarka. Tryb headless i headed, po trzy uruchomienia każdy. Każda wartość poniżej była taka sama we wszystkich trzech przebiegach.

StosTrybnavigator.webdriverToken user-agentnavigator.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"]

Najbardziej wyraźna różnica to jedna flaga logiczna. W tych domyślnych konfiguracjach uruchomienia nodriver zwraca navigator.webdriver jako false, podczas gdy stockowe Playwright i Puppeteer zwracają true, zarówno w trybie headless, jak i headed. Test pokazuje różnicę na poziomie konfiguracji; nie dowodzi, że wartość wynika wyłącznie z braku binarki WebDrivera.

We wszystkich czterech stosach właściwość nadal jest natywnym getterem przeglądarki na Navigator.prototype — function get webdriver() { [native code] } — nigdy własną właściwością instancji ani podmienioną funkcją. Wartość nie została przepisana przez JavaScript strony po załadowaniu. Test nie rozbierał na czynniki pierwsze konkretnej flagi uruchomieniowej ani ścieżki źródłowej odpowiedzialnej za odmienny wynik.

Drugi szczegół studzi marketing. W trybie headless user-agent nodriver nadal ogłasza HeadlessChrome/151.0.0.0 — identycznie jak stockowy Puppeteer i stockowy Playwright. W trybie headed zmienia się na Chrome/151.0.0.0, znów dokładnie tak samo. Jeśli zakładałeś, że biblioteka do ukrywania automatyzacji domyślnie maskuje najsłynniejszy identyfikator przeglądarki używanej do automatyzacji, to tak nie jest. Trzeba by to ustawić samemu.

Prawie wszystko inne było takie samo we wszystkich czterech stosach, i warto to powiedzieć wprost, bo zawęża obraz — każda z poniższych właściwości miała identyczną wartość na nodriver, Botasaurus, Playwright i Puppeteer:

WłaściwośćWartość identyczna na wszystkich czterech stosach
platformMacIntel
vendorGoogle Inc.
Wtyczkipięć
Typy MIMEdwa
pdfViewerEnabledtrue
Liczba logicznych rdzenidwanaście
Zgłaszana pamięć urządzenia16 GB
Punkty dotykuzero
window.chromeobecny, z app/csi/loadTimes i bez runtime
String renderera WebGLidentyczny we wszystkich czterech

Stary klasyk, w którym Permissions API i Notification.permission przeczą sobie nawzajem, nigdzie się nie pojawił — wszystkie cztery zwróciły zgodne default i prompt. Przeskanowałem też document i window pod kątem pozostałości w stylu cdc_, znanych ze starszych stosów WebDrivera: wszędzie pusto.

Jedno miejsce, w którym nodriver wygląda bardziej jak surowo zautomatyzowana przeglądarka niż kontrolowany środowiskowo egzemplarz, to geometria okna. W trybie headless nodriver zgłasza outerWidth/outerHeight jako 0×0 na ekranie 800×600; headless Playwright zgłasza 1280×720, bo sam ustawia viewport. Puppeteer znajduje się razem z nodriver przy 0×0. To różnica domyślnej konfiguracji, nie możliwości, i można ją zmienić.

Jeszcze jedna rzecz, którą warto znać przed wdrożeniem: nodriver nie dostarcza własnej przeglądarki, więc bez dodatkowych działań korzysta z tego Chrome’a, który masz już w systemie. U mnie oznaczało to automatyczne wykrycie /Applications/Google Chrome.app — Chrome 150.0.7871.187 — i to właśnie tę wersję ujawniał user-agent, a nie jakąś przypiętą. W twoim środowisku ujawniana wersja przeglądarki będzie po prostu tą, którą masz zainstalowaną.

Nazwijmy to uczciwie: to zapis tego, co ujawnia zautomatyzowany stos, kiedy nikt nie kazał mu się ukrywać — przydatny, jeśli patrzysz z perspektywy obrony, i przydatny, jeśli chcesz wiedzieć, co broadcastuje własne narzędzie. To nie jest miara tego, czy ma to znaczenie dla konkretnej usługi. Tego nie testowałem i żadna z powyższych pozycji nie powinna być odczytywana jako wynik skuteczności.

Czy naprawdę wyciąga treść ze strony?

Ogłaszanie się to jedno, a zwrócenie właściwego HTML-a to drugie. Uruchomiłem nodriver na tym samym trzyklasowym fixture, którego używa reszta tego repo benchmarkowego, więc wyniki są porównywalne z każdym innym testowanym narzędziem. Strona zawiera trzy elementy: A, statyczny link, którego znacznik jest literalnie obecny w serwowanych bajtach; B, węzeł budowany przez skrypt inline podczas parsowania, z markerem i URL składanymi z fragmentów, tak aby dopiero wykonanie JavaScriptu ujawniało ich treść; oraz C, węzeł wstrzykiwany 800 ms po zdarzeniu load, składany w ten sam sposób. Klasa C jest najbardziej „wroga” — odczyt wykonany w momencie load nie może jej zobaczyć.

StosOdczyt domyślnyZ jednoznacznym oczekiwaniem
nodriver 0.50.32 z 3 (A + B, bez C)3 z 3
Botasaurus 4.0.922 z 33 z 3
Playwright 1.56.02 z 33 z 3
Puppeteer 24.16.02 z 33 z 3

nodriver ląduje dokładnie tam, gdzie lądują cięższe narzędzia. browser.get() od razu po nim tab.get_content() daje migawkę z momentu load: JavaScript jest renderowany poprawnie — potwierdza to klasa B, bo nie ma jej w serwowanych bajtach — ale wszystko wstrzyknięte po load zostaje pominięte. Dodaj tab.select("#delayed-injected") i masz wszystkie trzy. Ten sam problem i to samo rozwiązanie co w Playwright i Puppeteer. Stabilne w trzech powtórzeniach i trzech osobnych przebiegach całego zestawu, bez żadnych flaków.

Przesunąłem opóźnienie wstrzyknięcia, żeby sprawdzić, kiedy domyślny odczyt przestaje działać. nodriver przestaje widzieć klasę C, gdy wstrzyknięcie następuje po 100 ms lub później — dokładnie taka sama granica jak w obu stockowych kontrolach. (Botasaurus jest tu odstępstwem i to jest prawdziwie ciekawa różnica między dwiema bibliotekami „anti-detect”: jego get() domyślnie blokuje się do pełnego załadowania strony, więc domyślny odczyt nadal łapie wstrzyknięcie przy 300 ms. Kosztuje to około 250 ms na nawigację.)

Samo oczekiwanie ma jeden niuans, który warto uwzględnić w budżecie czasu. W tym teście tab.select() nodrivera działało w grubych przedziałach pollingowych, zamiast śledzić opóźnienie wstrzyknięcia bardzo dokładnie:

Opóźnienie klasy C0 ms100 ms400 ms800 ms1500 ms
nodriver select()124–152 ms1132–11411128–11292132–21772138–2150
Puppeteer waitForSelector113–129 ms203–216512–516911–9191608–1611

W tym fixture wstrzyknięcie po 100 ms prowadziło do około 1,1 s dla select(). Zainstalowana pętla wykonuje await self, a potem await self.sleep(0.5) po nieudanej próbie; łączny cykl pomiarowy wypadł tu blisko jednej sekundy, ale nie należy przyjmować, że await self jest uniwersalnym stałym uśpieniem. Każdy testowany opóźniony węzeł został znaleziony. Czekanie w Puppeteerze śledziło te opóźnienia dokładniej. W wielu sekwencyjnych oczekiwaniach różnica mogłaby się kumulować, choć ta recenzja nie benchmarkowała produkcyjnej strony z trzydziestoma selektorami.

Uruchamianie to drugi obszar, w którym połączenie asynchroniczności i oszczędności zderza się z praktyką. Start przeglądarki stawiał nodrivera w tej samej okolicy co Botasaurus i Puppeteer, a wyraźnie za Playwrightem:

StosStart przeglądarki, w kolejnych uruchomieniach
nodriver 0.50.3910–1583 ms
Botasaurus 4.0.92986–1151 ms
Puppeteer 24.16.0969–1008 ms
Playwright 1.56.0282–365 ms

Po uruchomieniu przejście do strony i odczyt są najszybsze z czwórki: 119–129 ms. Lekki przy sterowniku, zwyczajny przy rozruchu.

Instalacja i zajętość: naprawdę dobra strona

Tutaj nodriver zasługuje na etykietę „skoncentrowany”, i jest to bezpieczny, weryfikowalny fakt instalacyjny, a nie cokolwiek o scrapowaniu. Tak wygląda czyste pip install nodriver po rozwiązaniu zależności:

Fakt instalacyjnynodriver 0.50.3
Zadeklarowane bezpośrednie zależności w runtimetrzy — websockets, mss, deprecated
Rozwiązana zależność pośredniawrapt (przez deprecated)
Zmierzony łączny rozmiar site-packagesokoło 17,2 MB w 6 katalogach dist-info, w tym pip ze środowiska
Udział samego nodrivera3,7 MB
numpy / lxmlbrak obu
Binarka przeglądarki podczas instalacjinic nie jest pobierane

Jak na kategorię, która zwykle ciągnie za sobą całe środowisko renderujące, to naprawdę lekki ślad.

Różnica między pakietami bezpośrednimi a pośrednimi ma znaczenie dla utrzymania. Metadane nodrivera proszą o mss, websockets i deprecated; wrapt pojawia się dlatego, że wymaga go deprecated. Szósty katalog dist-info w środowisku 17,2 MB to pip, który już tam był. Zatem „17,2 MB w sześciu dystrybucjach” opisuje środowisko pomiarowe, a „cztery dodane pakiety runtime” opisuje to, co zostało zainstalowane. To liczby powiązane, ale niezamienne.

Kontrast, który nadaje tej liczbie znaczenie: na tej samej maszynie framework Botasaurus waży 122,3 MB w 44 pakietach — czyli mniej więcej siedem razy więcej miejsca na dysku. To różnica między wyspecjalizowanym sterownikiem CDP a frameworkiem z pełnym wyposażeniem, i działa w obie strony. nodriver daje cienkie, czytelne drzewo zależności, które da się realnie przejrzeć; Botasaurus oferuje więcej „od razu po instalacji”, ale kosztuje to miejsce na dysku i większą powierzchnię zależności. Żadne z nich nie jest „lepsze” samo w sobie — zależy, czy potrzebujesz sterownika, czy frameworka — ale jeśli cenisz małą, łatwą do audytu instalację, nodriver jest wyjątkowo czysty jak na to, co robi.

Mały rozmiar na dysku nie oznacza małego importu. W poprawionej kopii na Pythonie 3.14 import nodriver trwało około 158 ms (mediana z nowych importów w subprocessach, mniej więcej 151–199 ms). Niepoprawiony import w 3.14 kończy się błędem, więc nie podaję dla niego czasu. 57 modułów domen CDP ładuje się od razu, a pamięć po imporcie wynosiła 31,5–31,9 MB zanim w ogóle powstał proces Chrome. Uruchomiona przeglądarka dodałaby znacznie więcej i nie została ujęta w tym pomiarze pamięci.

Dwie uwagi do tych liczb. Pochodzą z jednej maszyny — macOS arm64 — a wartości rozmiaru i czasu importu zostały zmierzone na Pythonie 3.14 na kopii z jednym poprawionym bajtem, ponieważ niepoprawiony pakiet w ogóle się na tym interpreterze nie importuje. Na wspieranym Pythonie poprawka nie jest potrzebna: potwierdziłem czysty, niezmieniany import na 3.12.13, i stamtąd pochodzą wszystkie pomiary przeglądarki. W runtime nadal potrzebny jest prawdziwy Chrome, Chromium, Edge albo Brave — nodriver steruje istniejącą przeglądarką, nie dostarcza własnej, więc koszt instalacji leży poza tymi 17 MB, a jak pokazała sekcja o ujawnianiu danych, wersja Chrome, którą masz w systemie, jest wersją widoczną na zewnątrz.

Licencja to prawdziwa decyzja o wdrożeniu

System diagram: The license is the real adoption decision

Większość recenzji darmowego narzędzia traktuje „jest open source” jako koniec rozmowy o licencji. W przypadku nodrivera to dopiero początek, bo licencja to AGPL-3.0 — potwierdzona zarówno w pliku LICENSE.txt w wheelu, jak i w polu spdx_id w repozytorium. To silna licencja copyleft dla sieci i jakościowo inny zobowiązanie niż to, czego oczekują sąsiednie projekty.

Najczęściej porównywani konkurenci nodrivera mają licencje permisywne, co czyni kontrast bardzo konkretnym:

NarzędzieLicencjaJeśli uruchamiasz zmodyfikowaną kopię jako usługę sieciową
nodriverAGPL-3.0Sekcja 13 może wymagać od operatora udostępnienia odpowiedniego kodu źródłowego zmodyfikowanej wersji użytkownikom zdalnym
PlaywrightApache-2.0brak odpowiednika klauzuli sieciowego copyleft
PuppeteerApache-2.0jak wyżej
BotasaurusMITjak wyżej

Zakres ma znaczenie. Sekcja 13 AGPL dotyczy zmodyfikowanej wersji programu objętego licencją, używanej do interakcji zdalnej przez sieć. Ta recenzja nie rozstrzyga, czy osobny kod otaczającej usługi staje się częścią objętego dzieła, ani nie analizuje przypadków granicznych związanych z użyciem wewnętrznym czy granicami korporacyjnymi. Jeśli produkt hostowany modyfikuje nodrivera, przejrzyj tekst licencji i architekturę z prawnikiem. To sygnał techniczny dotyczący wdrożenia, nie porada prawna.

Nie oceniam, czy AGPL jest dobra czy zła — copyleft to uczciwy wybór i wiele poważnych projektów z niego korzysta. Zwracam uwagę, że „nodriver jest darmowy i open source” jest prawdą, ale niepełną. Zobowiązanie jest realne, różni się od permisywnego standardu w tej części ekosystemu i powinno wejść do decyzji, zamiast zostać spłaszczone do słowa „free”. (Dla porządku: PyPI nie podaje tu żadnego klasyfikatora licencji; tekst AGPL znajduje się w wheelu, a identyfikator SPDX w repozytorium, więc nie polegaj na indeksie pakietów, że sam ci to pokaże.)

Metadane, w ujęciu datowanym

Dane repozytorium i pakietu z konkretnego momentu, prosto z GitHub API i PyPI:

FaktWartość na 14 lipca 2026
Gwiazdki4 511
Forki422
Otwarte issue14
Utworzonyluty 2024
Ostatni pushmaj 2026
Najnowsze wydanie PyPI0.50.3
Wheelczysto pythonowy py3-none-any
requires-python>=3.9
Klasyfikatory Pythona3.7–3.13

Obserwowalne sygnały utrzymania są mieszane: repozytorium otrzymało push w maju 2026, ale najnowszy zmierzony pakiet nadal zawierał problem z importem w Pythonie 3.14, a proponowana poprawka nie była jeszcze wydana w dniu badania. Same gwiazdki i liczba otwartych issue nie rozstrzygają, czy tempo rozwoju spełnia twój próg utrzymania.

Plusy i minusy

Plusy:

  • Mała, przejrzysta instalacja: trzy zadeklarowane zależności plus pośredni wrapt, około 17,2 MB w sześciu katalogach dist-info w badanym środowisku (jeden to pip), bez numpy/lxml i bez pobierania przeglądarki przy instalacji.
  • Naprawdę natywny CDP: własne 57-modułowe wiązania do DevTools Protocol i komunikacja bezpośrednio przez protokół, bez binarki chromedriver/Selenium.
  • Szeroka powierzchnia wyszukiwania elementów w Tab (62 metody publiczne), z pierwszorzędnym XPath, CSS i wyszukiwaniem tekstu — bez konieczności schodzenia do surowego evaluate() dla XPath.
  • Asynchroniczny z założenia, świeży profil przy każdym uruchomieniu i czytelny Config dla typowych ustawień (headless, ścieżka do binarki, argumenty, język, porty).
  • Z jakim skutkiem wyciąga treść z JavaScriptowej strony? Tak samo dobrze jak cięższe narzędzia: 2 z 3 klas treści przy odczycie domyślnym i 3 z 3 po explicit wait — identycznie jak stockowe Playwright i Puppeteer na tym samym fixture, stabilnie w trzech uruchomieniach. Sam odczyt strony był najszybszy z czwórki: 119–129 ms.
  • Zwraca navigator.webdriver jako false domyślnie tam, gdzie oba stockowe kontrolery zwracają true, bez patchowania samej właściwości — deskryptor pozostaje natywnym getterem przeglądarki.
  • Czytelna tożsamość architektoniczna jako CDP-native następca undetected-chromedriver.

Minusy:

  • Nie importuje się domyślnie w Pythonie 3.14 — jeden plik z bajtem spoza UTF-8 (cdp/network.py) rzuca SyntaxError przy imporcie. Potwierdzone w odniesieniu do otwartego issue #35, nadal niepoprawione w 0.50.3. Przypnij ≤3.13 (czysty import potwierdzony na 3.12.13) albo ponownie zakoduj plik.
  • AGPL-3.0 to realny czynnik przy wdrożeniu dla każdego, kto uruchamia zmodyfikowaną kopię jako usługę sieciową — bardziej restrykcyjny niż licencje Apache/MIT u podobnych narzędzi.
  • Mały rozmiar instalacji nie oznacza małego importu: około 158 ms cold startu i około 31,5 MB pamięci rezydentnej zanim pojawi się jakakolwiek przeglądarka, bo wszystkie 57 modułów CDP ładują się od razu.
  • tab.select() robi polling z półsekundowym krokiem, więc krótkie oczekiwania zaokrąglają się w górę — 100 ms oczekiwania kosztuje około 1,1 s, podczas gdy w Puppeteerze to około 210 ms. Zawsze poprawne, ale przy wielu małych waitach robi się to odczuwalne.
  • W trybie headless user-agent nadal domyślnie ujawnia HeadlessChrome, dokładnie jak w stockowych kontrolach; domyślna konfiguracja niczego tu nie maskuje.
  • Nadal potrzebuje prawdziwej binarki Chrome/Chromium/Edge/Brave w runtime; lekka instalacja pip to tylko połowa historii zależności, a wersja Chrome obecna w systemie jest wersją ujawnianą na zewnątrz.
  • Skuteczność wobec jakiegokolwiek systemu anty-bot nie została tu zweryfikowana — cała premisa stealth była z założenia nietestowana.

Czego nie testowałem, więc nie mogę o tym mówić: pamięci na kartę, latencji round-trip CDP, obsługi profili, przepustowości przy skali, Linuxa ani Windowsa, konkretnie Pythona 3.13 oraz — najważniejszego — realnej skuteczności przeciwko żywym usługom anty-bot. Wszystkie przeglądarki w tej recenzji komunikowały się wyłącznie z fixture na 127.0.0.1. Wszystkie liczby pochodzą z jednej maszyny (macOS arm64); dane o sterowaniu przeglądarką dotyczą Pythona 3.12.13 bez poprawek, a starsze pomiary rozmiaru i importu wykonano na Pythonie 3.14 na kopii z jednym poprawionym bajtem.

Nie robiłem też matrycy aktualizacji przeglądarek, więc zgodność z przyszłymi wydaniami Chrome pozostaje operacyjną kwestią po stronie wdrażającego, a nie wynikiem tej recenzji.

Dla kogo to jest, a kto powinien odpuścić

nodriver pasuje, jeśli chcesz lekkiego, asynchronicznego, natywnego dla CDP sterownika do żywego Chromium i nie przeszkadza ci samodzielne utrzymywanie przeglądarki, aktualizacji i runtime. Małe drzewo zależności jest łatwiejsze do audytu, a projekt CDP-first dobrze nadaje się do kontroli na poziomie protokołu. Przydatność w kontenerach pozostaje nietestowana: ta recenzja nie sprawdzała obrazów Linux, instalacji przeglądarki, bibliotek współdzielonych, sandboxingu ani sprzątania procesów.

Dwie grupy powinny szukać gdzie indziej. Jeśli używasz Pythona 3.14 i nie chcesz przypinać interpretera ani poprawiać vendoro­wanego pliku, poczekaj na wydanie fixu — problem z importem to dziś twardy stop. A jeśli AGPL-3.0 koliduje z tym, jak planujesz wdrożyć rozwiązanie — hostowana usługa z prywatnymi modyfikacjami — sama licencja wystarczy, by rozważyć permisywną alternatywę zanim zbudujesz coś na tym narzędziu. Nie jest to zarzut wobec kodu; to ograniczenia, które lepiej poznać teraz niż podczas przeglądu zgodności.

Pomiń go też wtedy, gdy naprawdę potrzebujesz danych, a nie przeglądarki sterowanej ręcznie. nodriver daje ci skryptowalny tab i 62 metody; zamiana wyrenderowanej strony na czyste, ustrukturyzowane rekordy nadal należy do twojego kodu. To inne zadanie i właśnie w tym miejscu pojawia się zarządzane API.

Alternatywy i miejsce Thunderbit

Najpierw uczciwe ustawienie ramy: nodriver jest darmowy, na AGPL i self-hosted. To ty uruchamiasz przeglądarkę, zarządzasz aktualizacjami, odpowiadasz za runtime i za każdy jego błąd. Dla dewelopera, który chce właśnie takiej kontroli, żadna usługa zarządzana nie konkuruje ceną z biblioteką, którą już masz.

W open source porównuj po zadaniu, a nie po logo. Jeśli bierzesz pod uwagę sterowniki do prawdziwej przeglądarki, nasze porównanie Playwrighta i Puppeteera opisuje dwóch oczywistych ciężkich graczy, obok których stoi nodriver. Scrapling jest najbliższym pythonowym odpowiednikiem w osi nastawionej na stealth, jeśli to jest powód twojego zainteresowania. Jeśli potrzebujesz wyniku gotowego pod LLM, a nie surowej kontroli przeglądarki, Crawl4AI renderuje strony i zwraca Markdown, a Scrapy pozostaje wzorcowym frameworkiem do dużych crawlów bez przeglądarki. Jeśli porównujesz kilka tych narzędzi naraz, przegląd open-source scraperów zestawia kategorie obok siebie.

Ujawnienie: ten artykuł publikuje Thunderbit. Thunderbit to zarządzana usługa ekstrakcji, więc porównanie jest oparte na zadaniu, a nie na architekturze. nodriver daje warstwę kontroli przeglądarki, którą hostujesz i programujesz sam; Thunderbit zajmuje się renderowaniem i zwraca zawartość strony albo rekordy w zadanej strukturze jako usługę. Użyj nodrivera, gdy wymagana jest kontrola przeglądarki na poziomie protokołu i self-hosting. Rozważ zarządzany ekstraktor, gdy ważniejszy jest wynik i przekazanie operacyjne niż posiadanie przeglądarki. Zmienne szczegóły dotyczące endpointów, kredytów i limitów batchy należą na stronę cennika, a nie do benchmarku biblioteki.

Różnica sprowadza się do tego, gdzie leży praca: nodriver zostawia zarządzanie przeglądarką i utrzymanie runtime po twojej stronie, bez opłaty serwisowej za każde wywołanie; zarządzane API przejmuje tę warstwę i rozlicza żądania.

Wypróbuj Thunderbit do ekstrakcji danych z internetu

Werdykt

Wybierz nodrivera, jeśli chcesz małego, asynchronicznego, natywnego dla CDP sterownika Chromium, potwierdziłeś jego działanie na swojej wersji Pythona (u mnie czysty import działał na 3.12.13) i przeanalizowałeś AGPL-3.0 pod kątem sposobu wdrożenia. Rozwiązany graf runtime dodał cztery pakiety, badane środowisko zajmowało około 17 MB, a biblioteka dostarcza 57 modułów domen CDP, powierzchnię Tab z 62 metodami i natywny XPath. Na tym fixture zwrócił 2 z 3 klas treści domyślnie i 3 z 3 po oczekiwaniu, tak samo jak stockowe Playwright i Puppeteer.

Trzeba jednak uczciwie ocenić zastrzeżenia. W Pythonie 3.14 w ogóle się nie importuje, dopóki nie poprawisz jednego bajtu spoza UTF-8 — to udokumentowany, nadal otwarty problem, nie zagadka, ale twarda blokada w dniu, w którym na niego trafisz. Licencja to AGPL-3.0, więc dla każdego, kto uruchamia zmodyfikowaną kopię jako usługę, to realna decyzja, a nie formalność. Mała instalacja nie kupuje małego importu, bo wszystkie moduły CDP ładują się od razu, a półsekundowy polling select() sprawia, że krótkie oczekiwania kosztują około sekundy każde. W kwestii domyślnego ujawniania danych, którą dało się realnie sprawdzić, obraz jest węższy, niż sugeruje marketing: od stockowego Puppeteera różni się tylko jedna flaga logiczna, headless user-agent nadal mówi HeadlessChrome, a każda inna mierzona przeze mnie właściwość była identyczna we wszystkich czterech stosach. A cała premisa anty-detekcji — powód, dla którego wiele osób w ogóle trafia na nodriver — to coś, czego świadomie nie testowałem. Zrobiłem inwentaryzację biblioteki i sterowałem nią na stronie na własnej maszynie, zamiast wystawiać ją przeciwko żywym zabezpieczeniom, i wolę powiedzieć to jasno, niż sugerować obejście, którego nie mogę obronić. W odpowiedzi na pytania, które dało się sprawdzić, nodriver jest dobrze zbudowanym, wyjątkowo lekkim sterownikiem z dwoma ostrymi kantami — barierą wersji Pythona i licencją copyleft — które dobrze jest widzieć z wyprzedzeniem.

Wypróbuj Thunderbit do ekstrakcji danych z internetu Get Started Free

FAQ

Dlaczego import nodriver nie działa w Pythonie 3.14? Bo cdp/network.py zawiera bajt ± niezakodowany w UTF-8, bez deklaracji kodowania źródła. Python 3.14.2 odrzuca ten plik i przerywa import pośredni; Python 3.12.13 importuje ten sam plik bez poprawek. Ta recenzja nie izolowała dokładnej zmiany w interpreterze i nie testowała Pythona 3.13. Ślad upstream to issue #35 nodriver oraz pull request #36. Użyj wersji, którą przetestowałeś, albo ponownie zakoduj plik do UTF-8.

Co daje „CDP-native, no webdriver” i ile kosztuje instalacja? nodriver zawiera 57 modułów domen DevTools Protocol i komunikuje się przez CDP po WebSocket, zamiast wywoływać chromedriver przez Selenium. Tab udostępnia 62 metody, w tym natywny XPath. Metadane pakietu deklarują trzy zależności runtime (websockets, mss, deprecated); ich rozwiązanie dodaje wrapt. Badane środowisko zajmowało około 17,2 MB w sześciu katalogach dist-info, w tym pip, bez numpy, lxml ani pobranej binarki przeglądarki. Dwie uwagi: poprawiona kopia importowała się w około 158 ms, bo wszystkie 57 modułów CDP ładują się od razu, a osobno nadal potrzebna jest przeglądarka z rodziny Chrome.

Czy licencja AGPL-3.0 ma znaczenie dla mojego projektu? To zależy od sposobu wdrożenia. AGPL-3.0 to licencja network-copyleft: jeśli uruchamiasz zmodyfikowaną wersję nodrivera jako usługę używaną przez innych, musisz udostępnić im źródła tej zmodyfikowanej wersji. Dla osobistego skryptu albo narzędzia wewnętrznego, którego nigdy nie wystawiasz na zewnątrz, to nie jest problem. Dla hostowanego produktu komercyjnego opartego na poprawionym nodriverze to poważne pytanie do zespołu odpowiedzialnego za zgodność — i bardziej restrykcyjne niż Apache-2.0 i MIT w porównywalnych narzędziach.

Co nodriver ujawnia o sobie — i czy to oznacza, że pokonuje Cloudflare? Pierwsza część jest zmierzona; druga nie, i ta różnica jest kluczowa. Na stronie, którą podałem z 127.0.0.1, sterując tym samym buildem Chrome co kontrolki: navigator.webdriver wraca jako false, podczas gdy stockowy Playwright i stockowy Puppeteer zwracają true. Ta wartość jest ustawiana przy starcie przeglądarki, a nie przez patchowanie właściwości — deskryptor nadal jest natywnym getterem Chrome. Poza tą jedną flagą prawie wszystko zgadzało się z kontrolami: ten sam string platformy, pięć wtyczek, dwanaście rdzeni, 16 GB zgłaszanej pamięci urządzenia, ta sama forma window.chrome, brak sprzeczności Permissions API i brak śladów w stylu cdc_ na document i window. W trybie headless user-agent nadal zgłasza HeadlessChrome/151.0.0.0, tak samo jak obie kontrolki — tego nie maskuje się za ciebie. Nic z tego nie mówi jednak, czy zadziała to przeciwko prawdziwej usłudze anty-bot. Nigdy nie kierowałem nodrivera na żywą stronę, nie kontaktowałem się z usługą anty-bot i nie dotykałem CAPTCHA; celowo było to poza zakresem. Tabela ujawniania powyżej mówi tylko, co deklaruje stos. Nie mówi nic o tym, kto to obserwuje i co z tym robi.

Czy nodriver poprawnie obsługuje treści renderowane przez JavaScript? Tak, z typowym zastrzeżeniem dotyczącym momentu odczytu. W fixture z trzema klasami treści domyślne browser.get() + tab.get_content() zwróciło 2 z 3 — JavaScript wykonuje poprawnie (synchronizowana klasa wstrzyknięta przez skrypt nie występuje w serwowanych bajtach, a mimo to wróciła), ale odczyt wykonywany jest w momencie load, więc pomija wszystko, co zostało wstrzyknięte później. Dodanie tab.select("#delayed-injected") zwróciło 3 z 3. Na tej samej stronie było to identyczne jak w stockowym Playwright i stockowym Puppeteer. Warto uwzględnić jeden niuans: select() robi polling z półsekundowym back-offem, więc czekanie 100 ms kosztuje około 1,1 s.

Ke
Ke
CTO w Thunderbit | Starszy data scientist i ekspert ML Dzięki prawie dziesięciu latom doświadczenia w uczeniu maszynowym i data science, Ke Shen jest absolwentem Columbia University i byłym starszym data scientistą w Walmart Labs. Dysponując dogłębną, uznaną przez branżowych ekspertów wiedzą w zakresie Python, R, Java i statystyki, dzieli się sprawdzonymi w boju spostrzeżeniami na temat wdrażania złożonych algorytmów AI — od teorii po architekturę gotową do produkcji.
Spis treści
Thunderbit · Agent AI do danych z internetu

Wyciągaj dane z dowolnej strony w 1 klik

Zaufało nam ponad 250 000 użytkowników
dostępny darmowy plan
Od strony do arkusza kalkulacyjnego
Opisz, czego potrzebujesz — Agent AI Thunderbit zbierze to i wyeksportuje do Excela, Google Sheets, Airtable lub Notion. Start jest darmowy.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week