Os frameworks de automação de navegador aceitam um proxy como opção de inicialização, mas proxies autenticados exigem tratamento especial porque o Chromium não aceita user:pass@ em uma URL de proxy: o Playwright aceita credenciais como campos separados (e pode definir um proxy diferente por contexto do navegador), o Puppeteer precisa de page.authenticate() e o Selenium precisa de uma extensão ou de um encaminhador local. Este guia fornece código funcional para cada um e, em seguida, aborda a parte que importa mais do que a sintaxe: um navegador headless atrás de um IP residencial ainda é identificável, a menos que o restante de sua história corresponda.

O fato que molda tudo: o Chromium ignora credenciais de proxy na URL

Ferramentas de linha de comando e bibliotecas Python aceitam facilmente http://user:pass@host:port. A flag --proxy-server do Chromium aceita apenas host:port (com um esquema opcional); as credenciais nele são silenciosamente descartadas. Quando o proxy responde com 407, o navegador mostra um diálogo de autenticação — que nenhum script de automação está observando. Portanto, cada framework tem sua própria maneira de fornecer o nome de usuário e a senha, e é aí que a maioria dos problemas de “proxy funciona no curl, mas não no meu script de navegador” vem. O Firefox é mais tolerante em alguns caminhos, mas os mecanismos específicos do framework abaixo funcionam em todos os mecanismos.

Playwright

O Playwright tem suporte de proxy de primeira classe com campos de credenciais separados e — o recurso que o torna a melhor opção para trabalhos com múltiplas identidades — o proxy pode ser definido por contexto do navegador, então um único processo de navegador pode hospedar várias sessões isoladas, cada uma saindo de um IP diferente.

Python

from playwright.sync_api import sync_playwright

PROXY = {"server": "http://proxy.example.com:8080",
         "username": "user", "password": "pass"}

with sync_playwright() as p:
    # opção A: um proxy para todo o navegador
    browser = p.chromium.launch(proxy=PROXY)
    page = browser.new_page()
    page.goto("https://api.ipify.org?format=json")
    print(page.text_content("body"))
    browser.close()

    # opção B: proxy diferente por contexto (um navegador, muitas identidades)
    browser = p.chromium.launch(proxy={"server": "per-context"})   # espaço reservado; proxies reais vêm dos contextos
    ctx_de = browser.new_context(proxy={"server": "http://gate.provider.com:7777",
                                        "username": "customer-country-de-session-a1", "password": "pass"},
                                 locale="de-DE", timezone_id="Europe/Berlin")
    ctx_us = browser.new_context(proxy={"server": "http://gate.provider.com:7777",
                                        "username": "customer-country-us-session-b2", "password": "pass"},
                                 locale="en-US", timezone_id="America/New_York")
    for ctx in (ctx_de, ctx_us):
        pg = ctx.new_page(); pg.goto("https://api.ipify.org?format=json"); print(pg.text_content("body"))
    browser.close()

Dois detalhes. Proxies por contexto no Chromium exigem que o navegador seja iniciado com uma opção de proxy (qualquer valor — o espaço reservado documentado é {"server": "per-context"}); sem isso, os contextos não podem substituir. E observe locale e timezone_id definidos junto com o proxy: um IP alemão com um fuso horário americano é uma contradição que os sistemas anti-bot ponderam, como explicado em como funcionam os sistemas anti-bot. Proxies SOCKS5 são suportados ("server": "socks5://host:1080"), mas o Chromium não envia credenciais para proxies SOCKS, então use autenticação por lista de permissão de IP com SOCKS.

Node.js

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ proxy: { server: 'per-context' } });
  const context = await browser.newContext({
    proxy: { server: 'http://gate.provider.com:7777',
             username: 'customer-country-de-session-a1', password: 'pass' },
    locale: 'de-DE', timezoneId: 'Europe/Berlin',
  });
  const page = await context.newPage();
  await page.goto('https://api.ipify.org?format=json');
  console.log(await page.textContent('body'));
  await browser.close();
})();

O campo bypass do Playwright ("bypass": "localhost,*.internal") exclui hosts do proxy, e launch_persistent_context() aceita a mesma opção proxy se você precisar de um diretório de perfil persistente.

Puppeteer

O Puppeteer passa o proxy como uma flag do Chromium e autentica por página:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    args: ['--proxy-server=http://proxy.example.com:8080'],
  });
  const page = await browser.newPage();
  await page.authenticate({ username: 'user', password: 'pass' });   // responde ao 407 para esta página
  await page.goto('https://api.ipify.org?format=json');
  console.log(await page.evaluate(() => document.body.innerText));
  await browser.close();
})();

page.authenticate() deve ser chamado antes da navegação e em todas as páginas (abas) que usarão o proxy. O proxy é para todo o navegador — o Puppeteer não tem configuração de proxy por contexto como o Playwright. Para várias identidades, você tem três opções: iniciar vários processos de navegador (um proxy cada; pesado, mas simples), usar --proxy-server apontando para um proxy de comutação local que roteia por algum marcador, ou mudar para o Playwright. Também é útil: --proxy-bypass-list=<-loopback> se você precisar que o localhost também seja proxyado, e puppeteer-extra com seu plugin de proxy, que lida com autenticação e pode rotacionar por página.

Selenium

O Selenium define o proxy por meio das opções do navegador. Proxies não autenticados (com lista de permissão de IP) são uma linha; os autenticados são o caso complicado.

Chrome, autenticação por lista de permissão

from selenium import webdriver

opts = webdriver.ChromeOptions()
opts.add_argument("--proxy-server=http://proxy.example.com:8080")
driver = webdriver.Chrome(options=opts)
driver.get("https://api.ipify.org?format=json")
print(driver.find_element("tag name", "body").text)
driver.quit()

Chrome, nome de usuário/senha

O protocolo WebDriver do Selenium não tem gancho para responder ao 407 do proxy. As soluções alternativas padrão:

  1. Selenium Wire (Python): uma extensão do Selenium que roteia o tráfego por meio de seu próprio proxy local e suporta upstreams autenticados:
    from seleniumwire import webdriver   # pip install selenium-wire
    opts = {"proxy": {"http": "http://user:pass@proxy.example.com:8080",
                      "https": "http://user:pass@proxy.example.com:8080"}}
    driver = webdriver.Chrome(seleniumwire_options=opts)
    Observe que o Selenium Wire não é mais mantido ativamente; ainda funciona com o Chrome atual para muitos, mas trate-o como uma solução temporária.
  2. Uma extensão Chrome gerada que define o proxy via chrome.proxy e responde a onAuthRequired com as credenciais — um padrão bem conhecido; a extensão é um manifesto mais algumas linhas de script em segundo plano, carregada com opts.add_extension(...). Não funciona no modo headless com Chrome mais antigo (extensões não eram suportadas lá); funciona no headless=new atual.
  3. Um proxy de encaminhamento local em 127.0.0.1 que contém as credenciais e encaminha para o upstream; aponte o Chrome para ele sem autenticação. Independente de framework e robusto.
  4. Autenticação por lista de permissão do provedor, se sua máquina tiver um IP estático — contorna o problema completamente.

Firefox

opts = webdriver.FirefoxOptions()
opts.set_preference("network.proxy.type", 1)
opts.set_preference("network.proxy.http", "proxy.example.com");  opts.set_preference("network.proxy.http_port", 8080)
opts.set_preference("network.proxy.ssl",  "proxy.example.com");  opts.set_preference("network.proxy.ssl_port", 8080)
opts.set_preference("network.proxy.socks_remote_dns", True)     # DNS remoto se usar SOCKS
driver = webdriver.Firefox(options=opts)

O Firefox ainda exibirá um diálogo de autenticação para proxies com nome de usuário/senha; as abordagens de encaminhador local ou lista de permissão se aplicam.

O que o proxy não resolve em um navegador headless

Um navegador real permite superar a impressão digital de TLS e HTTP/2 — o handshake é genuinamente do Chrome (veja impressão digital TLS). Isso não, por si só, permite superar a camada JavaScript:

  • navigator.webdriver é true sob automação; os sites verificam isso primeiro.
  • O modo headless tem diferenças de renderização e API (plugins ausentes, strings específicas do renderizador WebGL, dimensões de janela) que os scripts de impressão digital testam. O headless mais novo do Chrome (headless=new) é muito mais próximo do headful, mas não idêntico.
  • WebRTC ainda pode expor o IP real da máquina, a menos que seja desabilitado ou falsificado; um proxy não o toca. Teste — Como testar um proxy.
  • Fuso horário, localidade e geolocalização vêm da máquina, a menos que você os defina (Playwright: timezone_id, locale, geolocation); uma incompatibilidade com o país do proxy é um sinal clássico.
  • Comportamento: navegações instantâneas, sem movimento do mouse, tempo idêntico a cada execução.

Plugins de stealth (puppeteer-extra-plugin-stealth, playwright-stealth) corrigem os artefatos mais óbvios; os navegadores antidetect vão além, fornecendo uma impressão digital coerente por perfil e expondo uma porta de depuração à qual seu script Playwright/Puppeteer se conecta — que é a resposta de produção para trabalho com contas. Veja o que é um navegador antidetect e, para escolher o tipo de IP por trás de cada perfil, nosso ranking de proxies para antidetect.

Escolhendo o framework para trabalho com proxy

PlaywrightPuppeteerSelenium
Proxy autenticadoNativo (campos username/password)page.authenticate() por páginaSolução alternativa necessária (Wire, extensão, encaminhador local)
Proxy diferente por sessãoPor contexto, um navegadorUm navegador por proxyUm driver por proxy
SOCKS5Sim (sem credenciais → lista de permissão)Via flag (sem credenciais)Via opções/preferências
Localidade / fuso horário por sessãoOpções de contexto nativasAPIs de emulação / flagsPreferências / CDP
Melhor paraRaspagem e testes com múltiplas identidadesAutomação Chrome centrada em NodeSuites legadas, grades de navegadores cruzados

Perguntas frequentes

Como uso um proxy autenticado no Playwright?

Passe proxy={"server": "http://host:port", "username": "…", "password": "…"} para launch() ou, por contexto, para new_context() (inicie com proxy={"server": "per-context"} primeiro).

Como uso um proxy com autenticação no Puppeteer?

Inicie com args: ['--proxy-server=host:port'] e chame await page.authenticate({username, password}) antes de page.goto() em cada página.

Por que o Chrome ignora user:pass@ em --proxy-server?

A flag do Chromium aceita apenas scheme://host:port; as credenciais não são analisadas. Ele responde ao 407 do proxy com um diálogo interativo, então a automação deve fornecer as credenciais por meio do mecanismo do framework.

Posso usar um proxy diferente para cada aba?

Não por aba. O Playwright faz isso por contexto do navegador (cada contexto é uma sessão isolada); Puppeteer e Selenium precisam de uma instância de navegador por proxy, ou um proxy de comutação local.

Um proxy torna o Chrome headless indetectável?

Não. Ele corrige a camada de IP e, sendo um navegador real, a camada TLS. Flags de automação, artefatos headless, WebRTC, incompatibilidade de fuso horário e comportamento são sinais separados que precisam de tratamento separado.

O proxy deve ser residencial ou datacenter para automação de navegador?

O que a política de IP do alvo exigir — o navegador não muda esse cálculo. Datacenter onde for tolerado; residencial ou móvel em sites protegidos; estático (ISP) para perfis logados persistentes. Veja a comparação de tipos de proxy.