Análise do nodriver: um driver CDP minúsculo que não importa no Python 3.14

Atualizado em August 18, 2026
Análise do nodriver: um driver CDP minúsculo que não importa no Python 3.14
Resumo com IA
nodriver é uma biblioteca Python de automação de navegador da ultrafunkamsterdam, o mesmo autor do undetected-chromedriver, e se apresenta como a sucessora desse projeto. A ruptura arquitetural dele é justamente com Selenium e chromedriver: o nodriver conversa com o Chromium por meio do Chrome DevTools Protocol (CDP), sem nenhum binário de WebDriver no caminho, e sua API é assíncrona. Playwright e Puppeteer formam outra categoria de comparação. Eles também são controladores de navegador orientados por protocolo, e não descendentes do WebDriver; as diferenças úteis estão no desenho da API, no empacotamento, no fornecimento do navegador e na política de compatibilidade. Esta análise cataloga o nodriver 0.50.3, em vez de testá-lo contra defesas reais.

nodriver é uma biblioteca Python de automação de navegador da ultrafunkamsterdam, o mesmo autor do undetected-chromedriver, e se apresenta como a sucessora desse projeto. A ruptura arquitetural dele é justamente com Selenium e chromedriver: o nodriver conversa com o Chromium por meio do Chrome DevTools Protocol (CDP), sem nenhum binário de WebDriver no caminho, e sua API é assíncrona. Playwright e Puppeteer formam outra categoria de comparação. Eles também são controladores de navegador orientados por protocolo, e não descendentes do WebDriver; as diferenças úteis estão no desenho da API, no empacotamento, no fornecimento do navegador e na política de compatibilidade.

Esta análise cataloga o nodriver 0.50.3, em vez de testá-lo contra defesas reais. Medi o pacote instalado, imports, superfície de API, espaço em disco e licença, e só então iniciei navegadores em páginas servidas em 127.0.0.1. Nenhum alvo real, serviço anti-bot ou CAPTCHA foi envolvido. Os resultados descrevem o comportamento do pacote e a exposição padrão do navegador; eles não comprovam eficácia contra detecção.

Três coisas chamaram atenção dentro desse limite. Primeiro, no Python 3.14 a biblioteca simplesmente não importa — um único byte fora de UTF-8 derruba o pacote inteiro antes que você consiga chamar qualquer coisa. Segundo, para um driver dessa categoria, ele é surpreendentemente pequeno: três dependências diretas declaradas mais um pacote transitivo resolvido, e cerca de 17 MB no ambiente medido. Terceiro, na minha própria página, a única propriedade em que o nodriver difere visivelmente do Playwright padrão e do Puppeteer padrão é um booleano — e a string de user-agent em modo headless ainda diz HeadlessChrome nos três casos. O restante das surpresas está na licença.

O bloqueio de import no Python 3.14

Comece pelo problema que vai te atingir primeiro, porque acontece antes de qualquer linha do seu código rodar. No Python 3.14, um simples import nodriver falha imediatamente:

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

O arquivo que falha é o cdp/network.py gerado automaticamente (o cabeçalho diz # DO NOT EDIT THIS FILE!). Ele contém um único byte fora de UTF-8, 0xb1, referente ao ± no comentário #: JSON (±Inf)., e não tem declaração de encoding de origem. O módulo é carregado por nodriver/__init__cdp/__init__network, então a falha de parsing interrompe o import. Uma varredura no pacote não encontrou outro arquivo-fonte fora de UTF-8.

O limite de versão precisa ser descrito com cuidado. O Python 3.14.2 rejeita o arquivo, enquanto o Python 3.12.13 o importa sem patch. Os arquivos network.py instalados são byte a byte idênticos nesses ambientes (SHA-256 ef755f41800d4efb593736f8b55b331bba68eec373ed62ece433e66e4b491cd6), então artefatos-fonte diferentes não explicam o resultado. Esta análise não isolou a mudança exata no tokenizer do CPython responsável, e o Python 3.13 não foi testado. Portanto, ela relata os dois resultados observados, sem afirmar que toda versão anterior se comporta como o 3.12.

Dessa comparação não se conclui nada sobre o Python 3.13.

Isso é uma reprodução, não uma descoberta inédita. O mesmo traceback do Python 3.14 já aparece na issue #35 do nodriver, com uma correção proposta no pull request #36. A versão 0.50.3 ainda contém o byte problemático. Os metadados do PyPI listam classificadores até Python 3.13 e não afirmam suporte a 3.14.

A solução alternativa é pequena como o bug: use uma versão de interpretador testada ou regrave aquele arquivo em UTF-8, como propõe o pull request #36. Depois da regravação, o pacote importa e é inspecionado no 3.14 sem aparecer um segundo bloqueio. A execução limpa no 3.12.13 abaixo confirma um caminho sem patch; o Python 3.13 não foi testado nesta análise.

No Python 3.12.13, import nodriver funciona sem patch. Todos os números de condução de navegador citados mais adiante vêm dessa instalação. Os dados de footprint e tempo de import usam a cópia com um byte reencodificado no 3.14 e estão identificados como tal. O Python 3.13 continua sem teste aqui; um resultado no 3.12 não cobre toda a faixa anterior.

Se você trabalha com interpretadores modernos por padrão — e muita gente adota uma versão nova do Python rapidamente — isso é uma barreira real, ainda que trivial de corrigir. É bom saber que ela existe para não perder uma tarde com um SyntaxError em um arquivo que você nunca escreveu.

O que o nodriver realmente é, por dentro

A descrição de uma linha ("CDP nativo, sem webdriver") soa marketing até você olhar o que vem no wheel. O nodriver traz seu próprio conjunto completo de bindings do DevTools Protocol: o pacote nodriver.cdp contém 57 módulos de domínios do protocolo — accessibility, dom, network, page, fetch, runtime, target, storage, input, emulation e por aí vai. Esse número de módulos é o mecanismo por trás da afirmação “CDP direto, sem Selenium”. Em vez de chamar um executável chromedriver que fala WebDriver e traduz as chamadas para você, o nodriver gera objetos Python para os domínios CDP e conversa com o protocolo diretamente via WebSocket. O cdp/network.py — o arquivo com o byte problemático — é um desses 57 módulos gerados automaticamente, por isso a falha está em código gerado que ninguém edita manualmente.

Em cima dessa camada de protocolo existe um modelo de objetos mais amigável. O objeto Tab expõe 62 métodos públicos, e a superfície de busca é mais ampla do que a maioria dos drivers costuma oferecer: correspondência de texto via find() e find_all(), CSS via select() e select_all(), e um ponto de entrada nativo xpath(). Ter XPath, CSS e busca textual no mesmo objeto é realmente conveniente; outras bibliotecas às vezes exigem cair em evaluate() para XPath. O construtor Config expõe user_data_dir, headless, browser_executable_path, browser_args, sandbox, lang (padrão 'en-US'), host, port, expert e **kwargs. Durante esse inventário da API, construir Config(headless=True) gerou 16 flags de inicialização do Chromium, incluindo --no-first-run, --no-default-browser-check, --remote-allow-origins=* e --homepage=about:blank. Não chamei start() nessa etapa de introspecção. Os testes de navegador abaixo foram uma execução separada.

System diagram: Direct CDP Control Path

A biblioteca de fato expõe uma superfície de API voltada a anti-detecção — métodos cuja existência eu confirmei, mas cujo comportamento não exercitei contra nenhum alvo. Estou mencionando isso uma vez e seguindo em frente, porque o efeito disso em qualquer serviço real é exatamente o que eu escolhi não testar. De forma neutra: a nomenclatura do nodriver é mais contida do que a de alguns concorrentes com branding stealth. A narrativa dele é arquitetural — CDP nativo, perfil novo a cada execução — e não uma parede de nomes do tipo detect_and_bypass. Leia isso como observação de design de API, não como afirmação de resultado.

Esses números vieram da importação do pacote e do uso de introspecção do próprio Python — inspect, varredura de módulos, contagem de atributos. Nenhum site foi acessado. Se quiser repetir, os números saem diretamente do pacote; não são impressão subjetiva.

O que ele anuncia sobre si mesmo

Measured results chart: Browser disclosure fields in the tested stacks

Aqui vai uma pergunta que você pode responder sem chegar perto de defesa real alguma: quando o nodriver controla um navegador, o que esse navegador revela por conta própria à página que está visitando? Eu criei uma página que lê os itens óbvios — navigator.webdriver, user-agent, plataforma, idiomas, contagem de plugins e de hardware, a forma de window.chrome, o que a Permissions API informa, e a geometria de janela e tela —, servi isso em 127.0.0.1 e apontei quatro stacks para ela: nodriver, Botasaurus, e como controles o Playwright padrão e o Puppeteer padrão. As quatro usaram a mesma versão do Chrome (Chrome for Testing 151.0.7922.10), então qualquer diferença é da biblioteca, não do navegador. Headless e headed, três execuções cada. Todos os valores abaixo se mantiveram nas três rodadas.

StackModonavigator.webdriverToken no 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"]

A diferença mais clara é esse único booleano. Nessas configurações padrão de inicialização, o nodriver reporta navigator.webdriver como false, enquanto o Playwright e o Puppeteer padrão reportam true, tanto em headless quanto em headed. O teste identifica uma diferença no nível da configuração; ele não prova que esse valor decorre apenas da ausência de um binário WebDriver.

Em todas as quatro stacks, a propriedade continua sendo o getter nativo do navegador em Navigator.prototypefunction get webdriver() { [native code] } — e nunca uma propriedade própria da instância nem uma função substituída. A propriedade não foi reescrita por JavaScript da página depois do carregamento. O teste não enumerou qual argumento de inicialização ou caminho de origem é responsável pela diferença no valor.

O segundo detalhe é o que relativiza o marketing. Em modo headless, o user-agent do nodriver ainda anuncia HeadlessChrome/151.0.0.0 — idêntico ao do Puppeteer padrão, idêntico ao do Playwright padrão. Em modo headed, ele vira Chrome/151.0.0.0, de novo de forma idêntica. Se você imaginou que uma biblioteca anti-detecção mascara por padrão a string mais famosa de identificação da automação de navegador, não mascara. Isso teria de ser configurado por você.

Quase todo o resto foi igual entre as quatro stacks, e vale dizer isso explicitamente porque restringe a história — todas as propriedades abaixo tiveram o mesmo valor no nodriver, Botasaurus, Playwright e Puppeteer:

PropriedadeValor, idêntico nas quatro stacks
platformMacIntel
vendorGoogle Inc.
Pluginscinco
Tipos MIMEdois
pdfViewerEnabledtrue
Núcleos lógicosdoze
Memória do dispositivo reportada16 GB
Pontos de toquezero
window.chromepresente, com app/csi/loadTimes e sem runtime
String do renderizador WebGLidêntica nas quatro

O velho caso em que a Permissions API e Notification.permission se contradizem não apareceu em lugar nenhum — as quatro stacks reportaram default e prompt em concordância. Também varri document e window em busca dos resíduos no estilo cdc_ que eram conhecidos em stacks WebDriver antigas: vazio em todas as quatro.

O único ponto em que o nodriver parece mais com um navegador automatizado cru do que com um controlado é a geometria da janela. Em modo headless, o nodriver reporta outerWidth/outerHeight de 0×0 em uma tela 800×600; o Playwright headless reporta 1280×720 porque define um viewport para você. O Puppeteer fica com o nodriver em 0×0. Isso é uma diferença de configuração padrão, não de capacidade, e você pode alterar.

Uma última coisa útil antes de colocar em produção: o nodriver não vem com navegador embutido; por padrão, ele usa qualquer Chrome que já esteja instalado na máquina. No meu caso, ele detectou automaticamente /Applications/Google Chrome.app — Chrome 150.0.7871.187 — e o user-agent exposto foi essa versão, não uma fixada. A versão do navegador anunciada pela sua frota é simplesmente a que já estiver instalada nela.

Chame isso pelo que é e pelo que não é. É um registro do que uma stack automatizada revela quando ninguém pediu para ela se esconder — útil para quem está do lado da defesa, útil para quem quer saber o que sua própria ferramenta divulga. Não é uma medida de quanto disso importa para qualquer serviço específico. Eu não testei isso, e nenhuma linha acima deve ser lida como implicando um resultado.

Ele realmente consegue extrair conteúdo de uma página?

Anunciar-se é uma coisa; devolver o HTML certo é o trabalho. Rodei o nodriver contra o mesmo fixture de três classes de conteúdo que o restante deste repositório de benchmark usa, então os números batem com todas as outras ferramentas medidas aqui. A página contém três coisas: A, um link estático cujo marcador é um literal nos bytes servidos; B, um nó construído por um script inline durante o parse, com marcador e URL montados a partir de fragmentos, de modo que só executando o JavaScript eles aparecem; e C, um nó injetado 800 ms depois do evento load, montado da mesma forma. A classe C é a adversária — uma leitura feita no load não consegue vê-la.

StackLeitura padrãoCom espera explícita
nodriver 0.50.32 de 3 (A + B, perde C)3 de 3
Botasaurus 4.0.922 de 33 de 3
Playwright 1.56.02 de 33 de 3
Puppeteer 24.16.02 de 33 de 3

O nodriver fica exatamente onde os pesos-pesados ficam. browser.get() seguido diretamente de tab.get_content() é um snapshot no momento do load: ele processa JavaScript corretamente — a classe B prova isso, já que ela não existe em bytes servidos e mesmo assim aparece —, mas perde qualquer coisa injetada depois do load. Adicione tab.select("#delayed-injected") e você obtém as três. O mesmo ponto fraco, a mesma correção, como no Playwright e no Puppeteer. Resultado estável em três repetições e três execuções separadas da suíte inteira, sem flutuações.

Varri o atraso de injeção para descobrir onde a leitura padrão desiste. O nodriver deixa de ver a classe C quando a injeção acontece 100 ms ou mais após o load — o mesmo limite dos dois controles padrão. (Botasaurus é o outlier aqui, e é a diferença realmente interessante entre as duas bibliotecas anti-detecção: o get() dele bloqueia até o carregamento completo por padrão, então a leitura padrão ainda captura a injeção em 300 ms. Ele paga cerca de 250 ms por navegação por isso.)

A própria espera tem uma peculiaridade que vale considerar no orçamento. Nesse teste, o tab.select() do nodriver caiu em blocos grosseiros de polling, em vez de acompanhar de perto o atraso de injeção:

Atraso da classe C0 ms100 ms400 ms800 ms1500 ms
nodriver select()124–152 ms1132–11411128–11292132–21772138–2150
Puppeteer waitForSelector113–129 ms203–216512–516911–9191608–1611

Nesse fixture, uma injeção de 100 ms levou a cerca de 1,1 s para select(). O loop instalado faz await self e depois await self.sleep(0.5) após uma falha; o ciclo combinado medido ficou perto de um segundo aqui, mas await self não está estabelecido como um sono fixo universal de duração. Todos os nós atrasados testados foram encontrados. O waiter do Puppeteer acompanhou esses atrasos com mais precisão. Muitas esperas sequenciais poderiam amplificar a diferença, embora esta análise não tenha medido uma página de produção com trinta seletores.

A inicialização é o outro ponto em que o discurso de assíncrono e leve encosta na realidade. Subir o navegador colocou o nodriver na mesma faixa do Botasaurus e do Puppeteer, e bem atrás do Playwright:

StackInicialização do navegador, em todas as execuções
nodriver 0.50.3910–1583 ms
Botasaurus 4.0.92986–1151 ms
Puppeteer 24.16.0969–1008 ms
Playwright 1.56.0282–365 ms

Depois de iniciado, o caminho navegar-e-ler do nodriver é o mais rápido dos quatro, com 119–129 ms. Leve no volante, comum na ignição.

Instalação e footprint: a parte realmente boa

É aqui que o nodriver faz jus ao rótulo de “focado”, e isso é um fato verificável de instalação, sem relação com scraping em si. Este é o resultado de um pip install nodriver limpo:

Fato de instalaçãonodriver 0.50.3
Dependências runtime diretas declaradastrêswebsockets, mss, deprecated
Dependência transitiva resolvidawrapt (via deprecated)
Total medido em site-packagescerca de 17,2 MB em 6 diretórios dist-info, incluindo o pip do ambiente
Parte do nodriver nisso3,7 MB
numpy / lxmlnenhum dos dois
Binário de navegador no momento da instalaçãonenhum baixado

Para uma categoria que frequentemente arrasta uma pilha de renderização, isso é bem enxuto.

A distinção entre pacotes diretos e transitivos importa para manutenção. Os metadados do nodriver pedem mss, websockets e deprecated; wrapt chega porque deprecated o exige. O sexto diretório dist-info no ambiente de 17,2 MB é o pip, que já fazia parte daquele virtualenv. Então “17,2 MB em seis distribuições” descreve o ambiente medido, enquanto “quatro pacotes runtime adicionados” descreve o que a instalação resolveu. São contagens relacionadas, mas não intercambiáveis.

O contraste que faz esse número significar algo: na mesma máquina, o framework irmão Botasaurus pesa 122,3 MB em 44 pacotes — cerca de sete vezes o footprint em disco. Essa é a diferença entre um driver CDP enxuto e um framework completo, e o efeito vai nos dois sentidos. O nodriver entrega uma árvore de dependências fina e legível, que você de fato consegue auditar; o Botasaurus entrega mais coisas prontas e cobra espaço em disco e superfície de dependências por isso. Nenhum dos dois é “melhor” em abstrato — depende se você quer um driver ou um framework —, mas se você valoriza uma instalação pequena e inspecionável, o nodriver é incomumente limpo para o que faz.

O footprint pequeno não implica import pequeno. Na cópia com o byte corrigido no Python 3.14, import nodriver levou cerca de 158 ms (mediana entre imports novos em subprocessos, algo como 151–199 ms). O import 3.14 sem patch falha, então não há tempo reportado para ele. Os 57 módulos de domínio do CDP são carregados de forma eager, e a memória residente após o import foi de 31,5–31,9 MB antes de existir qualquer processo do Chrome. Um navegador iniciado acrescentaria muito mais e não entrou nessa medida de memória.

Duas ressalvas para esses números. Eles vêm de uma máquina — macOS arm64 — e os dados de footprint e tempo de import foram coletados no Python 3.14 usando a cópia com um byte reencodificado, porque o pacote sem patch não importa nesse interpretador. Em um Python suportado, você não precisa do patch: confirmei um import limpo sem patch no 3.12.13, que é de onde vêm todas as medições de navegador. E ainda é necessário um binário real de Chrome, Chromium, Edge ou Brave em tempo de execução — o nodriver controla um navegador existente, não traz um junto, então esse custo de instalação fica fora dos 17 MB; e, como a seção de divulgação mostrou, a versão de Chrome que sua máquina tiver instalada é a versão que o tráfego vai anunciar.

A licença é a verdadeira decisão de adoção

System diagram: The license is the real adoption decision

A maioria das análises de uma ferramenta grátis trata “é open source” como o fim da conversa sobre licenciamento. No caso do nodriver, isso é só o começo, porque a licença é AGPL-3.0 — confirmado tanto em LICENSE.txt do wheel quanto no spdx_id do repositório. Trata-se de uma licença de copyleft em rede forte, e isso é uma obrigação materialmente diferente do que os vizinhos exigem.

Os pares com os quais a maioria compara o nodriver são permissivos, o que torna o contraste concreto:

FerramentaLicençaSe você executar uma cópia modificada como serviço de rede
nodriverAGPL-3.0A seção 13 pode exigir que o operador ofereça o código-fonte correspondente da versão modificada aos usuários remotos
PlaywrightApache-2.0sem cláusula equivalente de copyleft em rede
PuppeteerApache-2.0idem
BotasaurusMITidem

O escopo importa. A seção 13 da AGPL trata de uma versão modificada do programa coberto usada para interação remota via rede. Esta análise não decide se o código de um serviço separado ao redor faz parte da obra coberta, nem resolve casos-limite de uso interno ou de fronteira corporativa. Se um produto hospedado modificar o nodriver, revise o texto da licença e a arquitetura com assessoria jurídica. Isto é um sinal técnico de adoção, não aconselhamento legal.

Não estou opinando se a AGPL é boa ou ruim — copyleft é uma escolha legítima e muitos projetos sérios a usam. O ponto é que “nodriver é livre e open source” é verdade, mas incompleto. A obrigação existe de fato, é diferente do padrão permissivo desse ecossistema, e precisa entrar na decisão em vez de ser achatada em “gratuito”. (O PyPI, curiosamente, não traz nenhum classificador de licença; o texto da AGPL está no wheel e o id SPDX está no repositório, então não conte com o índice do pacote para destacar isso.)

Metadados, em um ponto do tempo

Números do repositório e do pacote em uma data específica, direto da API do GitHub e do PyPI:

FatoValor em 14 de julho de 2026
Estrelas4.511
Forks422
Issues abertas14
Criado emfevereiro de 2024
Último pushmaio de 2026
Última release no PyPI0.50.3
Wheelpuro Python py3-none-any
requires-python>=3.9
Classificadores de Python3.7–3.13

Os sinais observáveis de manutenção são mistos: o repositório recebeu push em maio de 2026, enquanto o pacote medido mais recente ainda continha o problema de import no Python 3.14 e a correção proposta ainda não havia sido lançada na data da pesquisa. Estrelas e contagem de issues abertas não resolvem se esse ritmo atende ao seu critério de manutenção.

Prós e contras

Prós:

  • Instalação pequena e legível: três dependências declaradas mais wrapt transitivo, cerca de 17,2 MB em seis diretórios dist-info no ambiente medido (um deles é o pip), sem numpy/lxml e sem baixar navegador na instalação.
  • Verdadeiramente CDP nativo: empacota seus próprios 57 bindings de DevTools Protocol e fala o protocolo diretamente, sem binário chromedriver/Selenium no meio.
  • Superfície ampla de busca de elementos no Tab (62 métodos públicos), com XPath, CSS e busca textual de primeira classe — sem precisar cair em evaluate() para XPath.
  • Assíncrono por desenho, perfil novo a cada execução e uma superfície de Config limpa para os controles mais comuns (headless, caminho do executável, args, lang, portas).
  • Extrai conteúdo de uma página construída em JavaScript tão bem quanto os pesos-pesados: 2 de 3 classes de conteúdo em leitura padrão, 3 de 3 com espera explícita — idêntico ao Playwright e ao Puppeteer padrão no mesmo fixture, estável em três execuções. Navegar e ler foi o mais rápido dos quatro, com 119–129 ms.
  • Reporta navigator.webdriver como false por padrão, onde os dois controles padrão reportam true, sem patchar a propriedade — o descritor continua sendo o getter nativo do navegador.
  • Identidade arquitetural clara como sucessor CDP nativo do undetected-chromedriver.

Contras:

  • Não importa no Python 3.14 por padrão — um arquivo com um único byte fora de UTF-8 (cdp/network.py) lança SyntaxError no import. Reproduzido na issue #35, ainda sem correção na 0.50.3. Fixe em ≤3.13 (confirmado limpo no 3.12.13) ou regrave o arquivo.
  • AGPL-3.0 é uma consideração real para quem executa uma cópia modificada como serviço de rede — mais restritiva que as licenças Apache/MIT dos pares.
  • O footprint pequeno em disco não significa import pequeno: cerca de 158 ms de cold start e ~31,5 MB de memória residente antes de qualquer navegador existir, porque todos os 57 módulos CDP são carregados antecipadamente.
  • tab.select() faz polling com back-off de meio segundo, então esperas curtas arredondam para cima — uma espera de 100 ms custa cerca de 1,1 s, onde o waiter do Puppeteer custa ~210 ms. Acerta sempre, mas soma em muitas esperas pequenas.
  • Em modo headless, o user-agent continua anunciando HeadlessChrome por padrão, exatamente como nos controles padrão; nada na configuração padrão oculta a string de autoidentificação mais óbvia.
  • Ainda depende de um binário real de Chrome/Chromium/Edge/Brave em runtime; a instalação leve via pip cobre só metade da história de dependências, e a versão de Chrome que sua máquina tiver é a que o tráfego divulga.
  • A eficácia contra qualquer sistema anti-bot não foi verificada aqui — toda a premissa stealth ficou sem teste por design.

O que eu não testei, e portanto não posso afirmar: memória por aba, latência de ida e volta do CDP, tratamento de perfis, throughput em escala, Linux ou Windows, Python 3.13 especificamente e — o principal — eficácia real contra anti-bot em qualquer serviço vivo. Todos os navegadores desta análise só conversaram com um fixture em 127.0.0.1. Todos os números vêm de uma máquina (macOS arm64); as medidas de condução de navegador são do Python 3.12.13 sem patch, e os dados antigos de footprint e tempo de import são do Python 3.14 na cópia com um byte corrigido.

Também não rodei uma matriz de upgrade de navegador, então a compatibilidade com futuras versões do Chrome continua sendo um cheque operacional do adotante, não um resultado desta análise.

Para quem serve, e quem deveria pular

O nodriver faz sentido se você quer um driver leve, assíncrono e CDP nativo para um Chromium real, e está confortável em gerenciar o navegador, as atualizações e o runtime. A árvore de dependências pequena é mais fácil de auditar, e o design first-class em CDP combina com controle em nível de protocolo. A adequação a contêineres permanece sem teste: esta análise não exerceu imagens Linux, instalação de navegador, bibliotecas compartilhadas, sandboxing nem limpeza de processos.

Dois grupos deveriam olhar para outra coisa. Se você está no Python 3.14 e não quer fixar a versão do interpretador nem corrigir um arquivo vendorizado, espere a correção sair — o bloqueio de import hoje é um stop hard. E se a AGPL-3.0 for um problema para a forma como você pretende publicar — um serviço hospedado com modificações privadas — a licença por si só já é motivo suficiente para considerar uma alternativa permissiva antes de construir em cima dele. Nenhum desses pontos é uma crítica ao código; ambos são restrições que é melhor conhecer agora do que numa revisão de conformidade.

Também vale pular se o que você realmente precisa é de dados, e não de um navegador operado manualmente. O nodriver te entrega uma aba programável e 62 métodos; transformar uma página renderizada em registros limpos e estruturados ainda é código seu. Isso é outro trabalho, e é justamente aí que uma API gerenciada entra.

Alternativas, e onde o Thunderbit se encaixa

Primeiro, o enquadramento honesto: o nodriver é gratuito, AGPL e auto-hospedado. Você roda o navegador, gerencia as atualizações e assume o runtime e cada falha nele. Para um desenvolvedor que quer exatamente esse controle, nenhum serviço gerenciado compete em preço com uma biblioteca que você já possui.

Dentro do open source, compare por tarefa, não por logotipo. Se você está avaliando drivers de navegador real, nossa comparação entre Playwright e Puppeteer cobre os dois pesos-pesados óbvios ao lado do nodriver em termos de formato. Scrapling é o vizinho Python mais próximo no eixo orientado a stealth, se esse for o motivo da sua busca. Para saída pronta para LLM em vez de controle bruto do navegador, o Crawl4AI renderiza páginas e devolve Markdown, e o Scrapy continua sendo o framework de referência para grandes crawls sem navegador. Se você estiver comparando vários desses ao mesmo tempo, o panorama de scrapers open source coloca as categorias lado a lado.

Divulgação: este artigo é publicado pela Thunderbit. Thunderbit é um serviço gerenciado de extração, então a comparação é baseada na tarefa, e não na arquitetura. O nodriver oferece uma camada de controle de navegador que você hospeda e programa; a Thunderbit lida com a renderização e devolve o conteúdo da página ou registros em esquema como serviço. Use nodriver quando controle do navegador em nível de protocolo e auto-hospedagem forem requisitos. Considere um extrator gerenciado quando o registro de saída e a transferência operacional importarem mais do que possuir o navegador. Detalhes mutáveis de endpoint, créditos e limites por lote pertencem à página de preços, não dentro de um benchmark de biblioteca.

A troca está em onde o trabalho fica: o nodriver mantém o gerenciamento do navegador e a manutenção do runtime do seu lado, sem taxa de serviço por requisição; uma API gerenciada assume essa camada e cobra pelas chamadas.

Experimente a Thunderbit para Extração de Dados da Web

Veredito

Use o nodriver se você quer um driver Chromium pequeno, assíncrono e CDP nativo, já confirmou que ele funciona na sua versão de Python (o 3.12.13 importou sem problemas aqui) e revisou a AGPL-3.0 para o modo como vai distribuir. O grafo de runtime resolvido adicionou quatro pacotes, o ambiente medido ocupou cerca de 17 MB, e a biblioteca traz 57 módulos de domínio CDP com uma superfície Tab de 62 métodos e XPath nativo. Neste fixture, ele retornou 2 de 3 classes de conteúdo por padrão e 3 de 3 com espera, igualando Playwright e Puppeteer padrão.

Mas dimensione as ressalvas com honestidade. No Python 3.14 ele não importa de jeito nenhum até você corrigir um byte fora de UTF-8 — um problema documentado, ainda em aberto, não um mistério, mas um bloqueio duro no dia em que você o encontra. A licença é AGPL-3.0, o que é uma decisão real para qualquer pessoa executando uma cópia modificada como serviço, não mera formalidade. A instalação pequena não compra um import pequeno, porque todos aqueles módulos CDP são carregados de início, e o polling de meio segundo de select() faz esperas curtas custarem cerca de um segundo cada. Na questão de exposição padrão que eu realmente consegui responder, o quadro é mais estreito do que o marketing sugere: um único booleano difere do Puppeteer padrão, o user-agent headless ainda diz HeadlessChrome, e todas as outras propriedades que medi foram idênticas entre as quatro stacks. E toda a premissa de anti-detecção — o motivo de muita gente encontrar o nodriver em primeiro lugar — foi algo que eu deliberadamente não testei. Eu inventariei a biblioteca e a conduzi contra uma página na minha própria máquina, em vez de colocá-la contra defesas reais, e prefiro deixar isso claro do que entregar uma alegação de bypass que eu não posso sustentar. Nas questões que eu pude responder, o nodriver é um driver bem construído, incomumente enxuto, com duas arestas bem afiadas — uma barreira de versão do Python e uma licença copyleft — que vale enxergar chegando.

Experimente a Thunderbit para Extração de Dados da Web Get Started Free

FAQs

Por que import nodriver falha no Python 3.14? Porque cdp/network.py contém um byte ± fora de UTF-8, sem declaração de encoding de origem. O Python 3.14.2 rejeita o arquivo e interrompe o import transitivo; o Python 3.12.13 importa o mesmo arquivo byte a byte sem patch. Esta análise não isolou a mudança exata do interpretador e não testou o Python 3.13. O caminho upstream é a issue #35 do nodriver e o pull request #36. Use uma versão que você tenha testado ou regrave o arquivo para UTF-8.

O que “CDP nativo, sem webdriver” entrega, e quanto custa instalar? O nodriver empacota 57 módulos de domínio do DevTools Protocol e fala CDP via WebSocket, em vez de invocar chromedriver pelo Selenium. O Tab expõe 62 métodos, inclusive XPath nativo. Os metadados do pacote declaram três requisitos runtime (websockets, mss, deprecated); a resolução deles adiciona wrapt. O ambiente medido usou cerca de 17,2 MB em seis diretórios dist-info, incluindo o pip, sem numpy, lxml nem binário de navegador baixado. Duas ressalvas: o import da cópia corrigida levou cerca de 158 ms porque todos os 57 módulos CDP são carregados de forma eager, e ainda é preciso um navegador da família Chrome separadamente.

A licença AGPL-3.0 importa para o meu projeto? Depende de como você distribui. AGPL-3.0 é uma licença de copyleft em rede: se você executar uma versão modificada do nodriver como serviço usado por outras pessoas, você é obrigado a oferecer a elas o código-fonte modificado. Para um script pessoal ou uma ferramenta interna nunca exposta, isso normalmente não é problema. Para um produto comercial hospedado e construído sobre um nodriver alterado, é uma questão real para tratar com quem cuida de conformidade — e é mais restritiva que as licenças Apache-2.0 e MIT das ferramentas comparáveis.

O que o nodriver expõe sobre si mesmo — e isso significa que ele vence o Cloudflare? A primeira parte foi medida; a segunda não, e a diferença importa. Numa página que eu servi de 127.0.0.1, controlando a mesma versão do Chrome usada pelos controles: navigator.webdriver volta false, enquanto o Playwright padrão e o Puppeteer padrão retornam true. Esse valor é definido no momento da inicialização do navegador, não por patch na propriedade — o descritor continua sendo o getter nativo do Chrome. Além desse booleano, quase tudo bateu com os controles: mesma string de plataforma, cinco plugins, doze núcleos, 16 GB de memória reportada, mesma forma de window.chrome, sem contradição da Permissions API e sem resíduos no estilo cdc_ em document ou window. Em headless, o user-agent ainda anuncia HeadlessChrome/151.0.0.0, igual aos controles — isso não é mascarado para você. O que nada disso informa é se algum desses sinais funciona contra um serviço anti-bot real. Eu não apontei o nodriver para um site vivo, não contatei nenhum serviço anti-bot e não toquei em CAPTCHA; isso ficou fora de escopo por decisão. A tabela de divulgação acima diz o que a stack anuncia. Ela não diz quem está ouvindo, nem o que fazem com isso.

O nodriver lida corretamente com conteúdo renderizado por JavaScript? Sim, com a ressalva usual sobre quando você lê. Em um fixture com três classes de conteúdo, um browser.get() + tab.get_content() padrão retornou 2 de 3 — ele executa JavaScript corretamente (a classe injetada por script síncrono não existe nos bytes servidos e mesmo assim voltou), mas lê no evento load, então perde o que for injetado depois disso. Ao adicionar tab.select("#delayed-injected"), o resultado foi 3 de 3. É idêntico ao Playwright padrão e ao Puppeteer padrão na mesma página. Considere uma peculiaridade no orçamento: select() faz polling com back-off de meio segundo, então uma espera de 100 ms custa cerca de 1,1 segundo.

Ke
Ke
CTO na Thunderbit | Cientista de Dados Sênior e Especialista em ML Com quase uma década de experiência em machine learning e data science, Ke Shen é ex-aluno da Columbia University e foi Cientista de Dados Sênior na Walmart Labs. Com profunda experiência, reconhecida pelos pares, em Python, R, Java e Estatística, ele compartilha insights testados em batalha sobre como levar algoritmos complexos de IA da teoria à arquitetura pronta para produção.
Índice
Thunderbit · Agente de dados web com IA

Extraia dados de qualquer página em 1 clique

Confiado por mais de 250.000 usuários
plano grátis disponível
Da página web para a planilha
Descreva o que você precisa — o Agente de IA da Thunderbit extrai e exporta para Excel, Google Sheets, Airtable ou Notion. Comece grátis.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week