Scraping com nodriver: Guia de Proxies Residenciais e CDP Assíncrono

Aprenda a usar nodriver, o sucessor assíncrono do undetected-chromedriver, com proxies residenciais ProxyHat para contornar Cloudflare e Imperva. Implementação prática em Python.

Scraping With nodriver: A Practical Guide to Async CDP + Residential Proxies

Se você está migrando do undetected-chromedriver para o nodriver, já percebeu que a arquitetura mudou radicalmente: não há Selenium, não há WebDriver e toda a comunicação acontece via Chrome DevTools Protocol (CDP) sobre websockets. Este guia mostra como fazer scraping com nodriver de forma idiomática — integrando rotação de proxies residenciais como uma camada de configuração nativa, não um hack.

Aviso legal: Este artigo aborda automação autorizada em dados públicos. O acesso não autorizado a sistemas pode violar a Computer Fraud and Abuse Act (CFAA) nos EUA e o GDPR na UE. Respeite robots.txt, Termos de Serviço e limites de taxa. Consulte advogado antes de operar em escala.

O que é scraping com nodriver e por que importa

Scraping com nodriver é a prática de automação de navegador usando a biblioteca Python nodriver, criada pelo mesmo autor do undetected-chromedriver, ultrafunkamsterdam. Ao contrário do Selenium, que usa o protocolo W3C WebDriver, o nodriver fala diretamente com o Chrome via CDP — o mesmo protocolo usado pelas DevTools do navegador. Isso elimina o atributo navigator.webdriver=true e várias outras assinaturas que sistemas como Cloudflare, Imperva e DataDome detectam.

Para engenheiros de scraping que precisam extrair dados de páginas protegidas, o nodriver async oferece três vantagens concretas:

  • Zero WebDriver: o Chrome não é iniciado em modo automation, então navigator.webdriver retorna false por padrão.
  • Assíncrono nativo: toda a API é async/await, permitindo centenas de abas concorrentes em um único processo.
  • Controle fino de CDP: você pode interceptar rede, modificar headers e injetar scripts sem extensões.

No entanto, mesmo o navegador mais stealth pode ser bloqueado se o IP de saída for um datacenter conhecido. É aqui que o nodriver proxy entra: combinar a invisibilidade do CDP com IPs residenciais reais é a estratégia mais eficaz contra detecção em 2026.

Arquitetura do nodriver: por que remover WebDriver ajuda contra anti-bot

O Selenium WebDriver funciona iniciando o Chrome com a flag --remote-debugging-port e conectando um driver que controla o navegador via HTTP. O problema é que o Chrome, quando detecta automação via WebDriver, define navigator.webdriver=true, adiciona flags como --enable-automation e expõe variáveis CDP que sistemas anti-bot fingerprintam.

O nodriver segue um caminho diferente: ele usa uc.start() para lançar o Chrome com argumentos personalizados e conecta diretamente via websockets ao endpoint de debugging. Como não há driver intermediário, o navegador se comporta quase identicamente a uma sessão manual. A documentação oficial do Chrome DevTools Protocol confirma que CDP é a interface de debugging nativa — não um protocolo de automação.

Assinaturas eliminadas pelo nodriver

AssinaturaSelenium/UCnodriver
navigator.webdrivertrue (requer patch)false nativo
--enable-automationPresenteAusente
Variável cdc_ no DOMPresenteAusente
Runtime.evaluate CDP detectávelSimSim, mas sem padrão de driver

Ainda assim, o nodriver não é invencível. Soluções como Cloudflare Turnstile ainda podem detectar padrões de comportamento, TLS fingerprinting e — crucialmente — reputação de IP. Um IP de datacenter com score baixo em serviços como IPQualityScore pode disparar um desafio mesmo com navegador perfeito.

API idiomática do nodriver: Browser, Tab e hooks de eventos

A API do nodriver é orientada a objetos assíncronos. O ponto de entrada é uc.start(), que retorna um objeto Browser. Cada aba é um Tab que você obtém e manipula com await. Não há WebDriverWait — em vez disso, você usa seletores e hooks de eventos.

Exemplo básico: iniciar e navegar

import nodriver as uc
import asyncio

async def main():
    browser = await uc.start(
        headless=False,
        browser_args=["--disable-blink-features=AutomationControlled"]
    )
    tab = await browser.get("https://example.com")
    await tab.select("h1")  # aguarda elemento aparecer
    heading = await tab.find("h1")
    print(await heading.get_attribute("textContent"))
    browser.stop()

asyncio.run(main())

O método tab.select() aguarda até que o seletor CSS esteja presente no DOM — é o equivalente idiomático ao WebDriverWait, mas nativo e não bloqueante. Para interagir, você encadeia await tab.find(), await element.click() e await element.send_keys().

Hooks de eventos em vez de polling

Em vez de fazer polling de mudanças de página, o nodriver expõe browser.add_handler() para eventos CDP como Network.responseReceived ou Page.frameNavigated. Isso permite interceptar respostas JSON sem precisar parsear HTML:

async def on_response(event):
    if "api/products" in event.request.url:
        body = await event.response_body
        print(f"Capturado: {len(body)} bytes")

browser.add_handler(uc.cdp.network.ResponseReceived, on_response)
await tab.get("https://loja-protegida.com/catalogo")

Esse padrão é fundamental para scraping de APIs ocultas: muitos sites carregam dados via XHR/Fetch e expõem JSON estruturado que você pode capturar diretamente, sem renderizar todo o DOM.

Configurando nodriver proxy via browser_args

O nodriver não tem um parâmetro proxy= nativo. Em vez disso, você passa --proxy-server=http://gate.proxyhat.com:8080 em browser_args. O problema: o Chrome via CDP não exibe diálogo de autenticação de proxy, então credenciais devem ser passadas via URL ou injetadas via CDP.

A forma mais robusta é usar autenticação na URL do proxy (formato http://user:pass@host:port) em conjunto com --proxy-server. No ProxyHat, o usuário incorpora flags de geo e sessão:

ParâmetroFormato no usernameExemplo
Paísuser-country-USResidencial EUA
Cidadeuser-country-DE-city-berlinBerlim, Alemanha
Sessão stickyuser-session-abc123IP fixo por ~30 min

Por que proxies residenciais são essenciais para headful stealth

Um navegador nodriver sem proxy sai com o IP do seu servidor ou VPS. IPs de datacenter (AWS, DigitalOcean, OVH) têm alta probabilidade de estar em listas de bloqueio de anti-bot. Proxies residenciais usam IPs de ISPs reais — Comcast, AT&T, Deutsche Telekom — que aparecem como tráfego doméstico legítimo.

Em testes de mercado, taxas de sucesso em sites com Cloudflare Turnstile podem saltar de ~15% com datacenter para ~85%+ com residencial. A diferença é especialmente dramática em headless: sem IP residencial, o navegador stealth é quase irrelevante. Veja nossa página de localizações de proxy para cobertura geográfica.

Exemplo executável: nodriver + ProxyHat residencial

Abaixo está um exemplo completo que lança o nodriver através de um endpoint residencial dos EUA com sessão fixa, navega em uma página protegida e captura JSON via handler de rede. O proxy URL é construído a partir de credenciais ProxyHat:

import nodriver as uc
import asyncio
import urllib.parse

# Credenciais ProxyHat
PROXYHAT_USER = "user-country-US-session-abc123"
PROXYHAT_PASS = "sua_senha_aqui"
PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = "8080"

# Monta a URL de proxy com autenticação
proxy_url = f"http://{urllib.parse.quote(PROXYHAT_USER)}:{urllib.parse.quote(PROXYHAT_PASS)}@{PROXY_HOST}:{PROXY_PORT}"

async def scrape_protegido():
    browser = await uc.start(
        headless=True,
        browser_args=[
            f"--proxy-server={proxy_url}",
            "--disable-blink-features=AutomationControlled",
            "--no-sandbox",
            "--disable-dev-shm-usage",
        ],
    )

    captured = []

    async def on_response(event):
        url = event.request.url if hasattr(event, "request") else ""
        if "/api/data" in url:
            try:
                body = await event.response_body
                captured.append(body)
            except Exception:
                pass

    browser.add_handler(uc.cdp.network.ResponseReceived, on_response)

    tab = await browser.get("https://site-protegido.com/pagina")
    await tab.select(".content-ready", timeout=20)

    # Extrai conteúdo renderizado
    content = await tab.evaluate("document.body.innerText")
    print(f"Conteúdo: {content[:500]}")
    print(f"Respostas de API capturadas: {len(captured)}")

    browser.stop()
    return captured

asyncio.run(scrape_protegido())

Notas importantes:

  • O --proxy-server aceita a URL com credenciais embutidas, mas alguns builds do Chrome exigem injeção de Fetch.authRequired via CDP. Se você ver erro 407, adicione um handler AuthRequired.
  • A flag session-abc123 mantém o mesmo IP por aproximadamente 30 minutos, ideal para fluxos multi-página que exigem cookies consistentes.
  • Para rotação por requisição, omita session- e cada nova conexão receberá um IP diferente do pool residencial.

Alternativa SOCKS5

Para casos que exigem menor latência ou tunelamento UDP, use o endpoint SOCKS5 na porta 1080:

socks5_proxy = f"socks5://{PROXYHAT_USER}:{PROXYHAT_PASS}@gate.proxyhat.com:1080"
browser = await uc.start(
    browser_args=[f"--proxy-server={socks5_proxy}"],
)

Escalando nodriver: abas concorrentes, fleet em Docker e shutdown gracioso

Abas concorrentes em um único browser

Como o nodriver é assíncrono, você pode abrir múltiplas abas no mesmo Browser e aguardar todas com asyncio.gather. Isso é mais eficiente em memória que múltiplos processos Chrome:

async def scrape_url(browser, url):
    tab = await browser.get(url)
    await tab.select("body")
    text = await tab.evaluate("document.title")
    await tab.close()
    return text

async def batch_scrape(urls):
    browser = await uc.start(headless=True, browser_args=[f"--proxy-server={proxy_url}"])
    tasks = [scrape_url(browser, u) for u in urls]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    browser.stop()
    return results

Em prática, 10–20 abas concorrentes por processo Chrome é um limite seguro. Acima disso, o consumo de CPU e RAM aumenta rápido — cada aba renderiza JavaScript e mantém um contexto V8 separado.

Fleet headless em Docker

Para escalar horizontalmente, containerize cada instância nodriver com seu próprio proxy. Um padrão comum é um docker-compose com N réplicas, cada uma com uma sessão proxy diferente:

# Dockerfile
FROM python:3.12-slim
RUN apt-get update && apt-get install -y chromium
RUN pip install nodriver
COPY scraper.py .
CMD ["python", "scraper.py"]

No scraper.py, injete o identificador de sessão via variável de ambiente para que cada contêiner use um IP diferente:

import os
session_id = os.environ.get("SESSION_ID", "default")
proxy_user = f"user-country-US-session-{session_id}"
proxy_url = f"http://{proxy_user}:{PROXYHAT_PASS}@gate.proxyhat.com:8080"

Com Kubernetes ou Docker Swarm, você pode orquestrar 50+ pods, cada um com 10 abas — 500 sessões concorrentes, cada uma com IP residencial único. A página de preços do ProxyHat detalha planos por tráfego e concorrência.

Shutdown gracioso

O nodriver não fecha processos Chrome órfãos automaticamente em crashes. Use try/finally e signal handlers:

import signal

async def run_with_cleanup():
    browser = await uc.start(browser_args=[f"--proxy-server={proxy_url}"])
    def cleanup(*args):
        browser.stop()
        sys.exit(0)
    signal.signal(signal.SIGINT, cleanup)
    signal.signal(signal.SIGTERM, cleanup)
    try:
        await do_work(browser)
    finally:
        browser.stop()

Quando NÃO usar um navegador completo

O nodriver é poderoso, mas caro: cada instância Chrome consome 200–500 MB de RAM e exige CPU para renderizar. Se a página-alvo não exige JavaScript para entregar dados, você pode obter 10x mais throughput com HTTP puro + curl_cffi, que imita TLS fingerprint do Chrome a nível de biblioteca:

from curl_cffi import requests

r = requests.get(
    "https://api-publica.com/data",
    impersonate="chrome120",
    proxies={"https": "http://user-country-US:pass@gate.proxyhat.com:8080"}
)
print(r.json())

Use navegador completo quando:

  • A página exige execução de JS para renderizar conteúdo (SPA com React/Vue).
  • Há desafios CAPTCHA interativos que exigem comportamento humano.
  • Você precisa capturar respostas de API injetadas via XHR pós-carregamento.

Use HTTP + curl_cffi quando:

  • O endpoint retorna JSON diretamente e não valida headers de navegador.
  • Você precisa de 1000+ requisições por segundo.
  • O custo de infraestrutura é o fator decisivo.

Para casos de uso de web scraping em escala e rastreamento de SERP, combine ambas as abordagens: HTTP para volume, navegador para páginas problemáticas.

Erros comuns e edge cases

1. Esquecer de fechar abas

Abas não fechadas acumulam memória. Sempre chame await tab.close() após processar cada URL, senão o Chrome cresce até OOM em poucas horas.

2. Usar datacenter proxy com nodriver

Como mencionado, o navegador stealth não compensa IP de datacenter. Se você vir desafios Cloudflare consistentes, troque para residencial antes de ajustar mais flags do Chrome.

3. Timeout muito curto em select()

O tab.select() padrão pode aguardar apenas 10 segundos. Páginas pesadas com Cloudflare podem levar 15–25s para liberar conteúdo. Use timeout=30.

4. Não tratar AuthRequired do proxy

Alguns builds do Chrome ignoram credenciais na URL do proxy. Se você receber 407, registre um handler CDP Fetch.authRequired que injeta authChallengeResponse com suas credenciais. A documentação em docs.proxyhat.com cobre esse padrão.

Key Takeaways

  • nodriver elimina WebDriver: CDP direto remove navigator.webdriver e outras assinaturas, mas IP ainda é o fator #1 de detecção.
  • Proxy vai em browser_args: use --proxy-server=http://user-country-US-session-abc123:pass@gate.proxyhat.com:8080 — sem código de rotação nativo.
  • Residencial > datacenter: taxas de sucesso saltam de ~15% para ~85%+ em sites com Turnstile.
  • Abas concorrentes: 10–20 por processo é seguro; use Docker + sessões únicas para escalar a 500+.
  • HTTP puro quando possível: curl_cffi + ProxyHat dá 10x mais throughput se JS não for necessário.

Conclusão e próximos passos

O nodriver representa a evolução natural do scraping de navegador em Python: assíncrono, sem WebDriver, com controle fino de CDP. Mas sem proxies residenciais, toda a stealth do navegador é desperdiçada. A combinação nodriver + ProxyHat residencial oferece o melhor dos dois mundos — invisibilidade de navegador e reputação de IP real.

Para começar, crie uma conta no dashboard ProxyHat, pegue suas credenciais e adapte o exemplo acima. Se você está migrando do undetected-chromedriver, a mudança de API é pequena e o ganho de estabilidade é significativo.

Pronto para começar?

Acesse mais de 50M de IPs residenciais em mais de 148 países com filtragem por IA.

Ver preçosProxies residenciais
← Voltar ao Blog