Autenticação de Proxy Nodriver: As Três Soluções Que Funcionam
O principal resultado para autenticação de proxy nodriver é uma discussão no GitHub, não um guia. nodriver não tem suporte nativo para user:pass — aqui estão as três soluções que funcionam, e o atalho de uma linha que a maioria das pessoas perde.
Pesquise autenticação de proxy nodriver e os dez principais resultados são uma discussão no GitHub, um repositório de demonstração, alguns tópicos no Stack Overflow sobre uma biblioteca diferente e uma postagem no Reddit — nenhum guia real. A razão é simples: nodriver, o sucessor assíncrono do CDP para undetected-chromedriver (esse projeto tem 12,8k estrelas no GitHub e 1,3k forks), não tem uma maneira nativa de passar user:pass para um proxy. O Chrome ignora credenciais incorporadas em um argumento de linha de comando, e nodriver não resolve isso. Este guia é a página que aquela discussão deveria ter se tornado: o que funciona, o que não funciona, e as três soluções que colocam um proxy autenticado em funcionamento.
Proxy simples funciona; proxy autenticado não
Um proxy não autenticado é uma linha de código. Passe o endereço através de browser_args e cada solicitação sai do IP do proxy:
import nodriver as uc
async def main():
browser = await uc.start(
browser_args=["--proxy-server=gate.quantumproxies.io:PORT"],
)
page = await browser.get("https://httpbin.org/ip")
print(await page.get_content()) # shows the proxy exit IP
uc.loop().run_until_complete(main())
Agora adicione credenciais — --proxy-server=http://USER:PASS@host:port — e ele quebra. O Chromium remove o segmento USER:PASS@ porque o formato do argumento não tem espaço para credenciais, então o proxy responde com 407 Proxy Authentication Required e o Chrome exibe um diálogo de login nativo que vive fora do DOM. nodriver não pode ver ou preenchê-lo. Esse 407 é a mesma barreira abordada em nosso guia para corrigir erros 407: o proxy está rejeitando você, não o navegador. Portanto, toda solução real precisa responder ao desafio de outra forma.
Solução 1: Lista branca de IPs — sem credenciais, sem diálogo
Este é o atalho que os tópicos do GitHub nunca mencionam, e é o mais simples de longe. Se o seu scraper roda de uma máquina com um IP público estável, registre esse IP no painel do seu provedor e elimine completamente as credenciais — o gateway autentica você pelo endereço de origem. O código nodriver permanece como o snippet simples --proxy-server acima, sem código de autenticação. Todo plano da QuantumProxies suporta lista branca de IPs junto com user:pass em seus proxies residenciais, então este é o caminho recomendado sempre que seu IP de saída for fixo. Seu único limite é topológico: ele autentica uma máquina, não um script, então runners em nuvem efêmeros, contêineres atrás de NAT e caixas de CI com IPs variáveis precisam de uma das duas próximas soluções.

Solução 2: responder ao desafio com um manipulador CDP Fetch
nodriver fala diretamente o Chrome DevTools Protocol, então você pode interceptar o desafio de autenticação em processo — sem necessidade de arquivo de extensão. Habilite o domínio Fetch com handle_auth_requests=True, depois responda a cada evento AuthRequired com continue_with_auth. Dois detalhes, ambos da resposta na discussão #1798, são a diferença entre funcionar e travar:
- Registre os manipuladores antes de habilitar o domínio. A chamada interna
enabledo nodriver sobrescreve seu registro de manipulador, então se você os adicionar depois, nenhum evento será disparado — a razão número um pela qual as pessoas relatam 'não faz nada'. - Dispare e esqueça as respostas. Aguardar a resposta dentro do manipulador bloqueia o loop de eventos e trava todo o navegador. Envolva cada envio em
asyncio.create_taskpara que ele execute sem bloquear.
import asyncio
import nodriver as uc
PROXY = "gate.quantumproxies.io:PORT" # host:port for --proxy-server
USER, PASS = "USER", "PASS"
class Scraper:
def __init__(self):
uc.loop().run_until_complete(self.run())
async def run(self):
browser = await uc.start(browser_args=[f"--proxy-server={PROXY}"])
self.tab = await browser.get("draft:,") # blank tab first
# 1) handlers BEFORE enabling the Fetch domain
self.tab.add_handler(uc.cdp.fetch.RequestPaused, self.on_request)
self.tab.add_handler(uc.cdp.fetch.AuthRequired, self.on_auth)
# 2) only now turn on interception with auth handling
await self.tab.send(uc.cdp.fetch.enable(handle_auth_requests=True))
page = await browser.get("https://httpbin.org/ip")
await asyncio.sleep(3)
print(await page.get_content())
async def on_auth(self, event):
# fire-and-forget: awaiting here deadlocks the loop
asyncio.create_task(self.tab.send(uc.cdp.fetch.continue_with_auth(
request_id=event.request_id,
auth_challenge_response=uc.cdp.fetch.AuthChallengeResponse(
response="ProvideCredentials", username=USER, password=PASS,
),
)))
async def on_request(self, event):
asyncio.create_task(self.tab.send(
uc.cdp.fetch.continue_request(request_id=event.request_id)))
if __name__ == "__main__":
Scraper()
Uma advertência surgiu no mesmo tópico: um usuário descobriu que isso funcionava em páginas HTTP simples, mas falhava em HTTPS, e o culpado era um proxy de baixa qualidade, não o código — trocar por uma melhor saída resolveu. Essa é a lição recorrente do scraping stealth: o manipulador responde ao desafio, mas a reputação do IP decide se o site permite sua entrada.
Solução 3: uma extensão Chrome gerada
O outro padrão da comunidade constrói uma pequena extensão Chrome na inicialização que define o proxy e responde ao desafio de credenciais através de chrome.webRequest.onAuthRequired — o mesmo truque que funciona no Selenium e Puppeteer. Você escreve um pequeno manifesto mais um trabalhador de fundo em um diretório temporário e o carrega via --load-extension:
import nodriver as uc
async def main():
# ext_dir holds a Manifest V3 extension: manifest.json + worker.js that
# calls chrome.proxy.settings.set(...) and returns authCredentials from
# chrome.webRequest.onAuthRequired. Generate it once, then load it:
browser = await uc.start(browser_args=[
"--load-extension=" + ext_dir,
"--headless=new", # extensions only load in the NEW headless mode
])
page = await browser.get("https://httpbin.org/ip")
print(await page.get_content())
uc.loop().run_until_complete(main())
O manifesto completo e o trabalhador são idênticos aos arquivos Manifest V3 em nosso guia de autenticação de proxy do Selenium — copie-os literalmente, apenas a chamada de lançamento muda. Dois problemas se repetem em todos os lugares: extensões carregam apenas sob --headless=new (plain --headless falha), e um diretório descompactado é mais confiável em versões do Chrome do que um zip compactado. A extensão lida com qualquer tipo de proxy, o que a torna o recurso quando a rota CDP está lutando contra você.
A quarta opção: um relay local
Se você preferir não mexer no nodriver, execute um pequeno relay local que mantém as credenciais e apresenta um endpoint sem autenticação em 127.0.0.1. nodriver então aponta para o endereço de loopback com o argumento simples e nunca vê um desafio. Esta é a rota mais limpa para SOCKS5, onde o Chromium recusa proxies autenticados completamente (rastreado como bug do Chromium 40829748). Cobrimos o relay mínimo, as ferramentas prontas e quando é exagero em o guia de relay de proxy.
Uma nota sobre SOCKS5
SOCKS5 não autenticado funciona através do argumento — --proxy-server=socks5://host:port — mas SOCKS5 autenticado não, e nenhum manipulador CDP salva você porque o Chromium nunca lançou suporte para nome de usuário/senha SOCKS5. As respostas práticas são as mesmas três: liste o IP, execute um relay, ou use o endpoint HTTP do provedor. Todo plano da QuantumProxies expõe tanto proxies HTTP quanto SOCKS5 no mesmo gateway, então mudar para o endpoint HTTP é muitas vezes a solução SOCKS5 mais rápida de todas. Para navegadores anti-detect como uma família, o mapa de proxies autenticados compara nodriver, zendriver e o resto lado a lado.

Perguntas frequentes
O nodriver suporta proxies autenticados?
Não nativamente. Você pode passar um proxy não autenticado através de browser_args=["--proxy-server=host:port"], mas user:pass nesse argumento é removido pelo Chromium. Para autenticar, você deve listar seu IP com o provedor, responder ao desafio com um manipulador CDP Fetch.AuthRequired, gerar uma extensão de proxy-auth Chrome, ou executar um relay local que mantém as credenciais.
Por que meu manipulador de autenticação nodriver não recebe eventos?
Quase sempre porque você habilitou o domínio Fetch antes de registrar os manipuladores. A chamada interna enable do nodriver sobrescreve o registro, então os eventos nunca chegam ao seu callback. Adicione os manipuladores RequestPaused e AuthRequired primeiro, depois chame fetch.enable(handle_auth_requests=True). Também envolva suas respostas em asyncio.create_task para que aguardá-las não bloqueie o loop.
O nodriver pode usar um proxy SOCKS5 com nome de usuário e senha?
Não. O Chromium não suporta SOCKS5 autenticado (bug do Chromium 40829748), e o nodriver herda essa limitação. SOCKS5 não autenticado funciona via --proxy-server=socks5://host:port. Para SOCKS5 autenticado, liste seu IP, execute um relay local que adicione as credenciais, ou mude para o endpoint HTTP do provedor, que lida com autenticação Basic de forma limpa.
nodriver ou zendriver para proxies autenticados?
Ambos compartilham a mesma lacuna e as mesmas soluções, porque zendriver é um fork comunitário do nodriver. zendriver tem um rastreador de problemas mais ativo onde a questão da autenticação é abertamente discutida, mas os métodos que funcionam são idênticos. Se você está no fork, a configuração específica do fork espelha tudo aqui — o manipulador CDP e a lista branca se comportam da mesma forma.
O resumo honesto: nodriver não fará autenticação de proxy por você, e isso é aceitável uma vez que você conhece o mapa. Liste quando seu IP for estável, recorra ao manipulador CDP ou extensão quando não for, e mantenha um relay na manga para SOCKS5. O código acima responde ao desafio — mas uma saída residencial limpa é o que realmente faz você passar pela porta.