O nosso ponto de vista: somos a Geonode e vendemos proxies, e a automatização do navegador é a atividade que mais largura de banda consome quando realizada através de um proxy. Um navegador sem interface gráfica carrega antecipadamente todas as imagens, tipos de letra, scripts e vídeos, pelo que executar o chromedp com tráfego medido custa aproximadamente uma ordem de grandeza a mais do que as solicitações HTTP simples para as mesmas páginas. Há uma secção sobre como reduzir isso, e a técnica nela descrita irá poupar-lhe mais do que se optasse por um fornecedor mais barato. A configuração do proxy em si tem apenas três linhas e apresenta uma única armadilha genuína, que também é abordada.
O que é o chromedp
O projeto descreve-se como «uma forma mais rápida e simples de controlar navegadores compatíveis com o Protocolo Chrome DevTools em Go, sem dependências externas».
Essa última frase é o seu principal argumento de venda. O Selenium necessita de um ficheiro binário de controlador compatível com a versão do seu navegador; o Playwright inclui o seu próprio ambiente de execução. O chromedp comunica diretamente com o Protocolo DevTools através de um WebSocket, pelo que basta um binário em Go e uma instalação do Chrome para completar a implementação.
Está licenciado sob a licença MIT e é mantido ativamente — a versão 0.15.1 foi lançada em abril de 2026, com commits até julho, registados em setembro de 2026.
A instalação é simples:
go get -u github.com/chromedp/chromedp
As ligações de protocolo geradas encontram-se num pacote complementar, github.com/chromedp/cdproto, ao qual se recorre quando é necessário algo que a API de alto nível não abrange.
O Modelo de Contexto
A parte que deve ser compreendida em primeiro lugar, pois tudo o resto decorre dela.
O chromedp utiliza context.Context para duas tarefas em simultâneo: o cancelamento, como o Go faz sempre, e o manuseamento dos identificadores do navegador e dos separadores. Essa dupla finalidade é a razão pela qual a configuração tem o aspeto que tem.
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
var title string
err := chromedp.Run(ctx,
chromedp.Navigate("https://example.com"),
chromedp.Text("h1", &title, chromedp.NodeVisible),
)
O primeiro NewContext aloca um navegador. Os contextos subsequentes derivados deste criam novas abas no mesmo navegador, o que permite executar várias páginas sem ter de suportar repetidamente os custos de arranque do navegador:
browserCtx, cancelBrowser := chromedp.NewContext(context.Background())
defer cancelBrowser()
tabCtx, cancelTab := chromedp.NewContext(browserCtx)
defer cancelTab()
O cancelamento encerra tudo. Cancelar um contexto de separador fecha o separador; cancelar o contexto do navegador fecha o navegador. O defer cancel() não é uma formalidade opcional — omitir este parâmetro provoca uma fuga de um processo do Chrome.
Os tempos de espera são definidos da forma habitual em Go:
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
Vale a pena conhecer antecipadamente dois erros mencionados na própria FAQ do projeto.
«A execução de uma ação sem um Run resulta num "contexto inválido"». A FAQ explica que «por predefinição, um contexto chromedp não tem um executor; no entanto, é possível especificar um manualmente, se necessário». As ações não se executam por si próprias — são valores que Run executa.
«Estou a ver erros do tipo “contexto cancelado”.» A secção de perguntas frequentes atribui isto à perda de ligação: «quando a ligação ao navegador é perdida, chromedp cancela o contexto, o que pode resultar neste erro. Isto ocorre, por exemplo, se o navegador for fechado manualmente, ou se o processo do navegador tiver sido encerrado ou terminado de outra forma.» Assim, um erro context canceled significa frequentemente que o Chrome deixou de funcionar, em vez de ter sido acionado o seu tempo limite — vale a pena distinguir isto antes de aumentar o tempo limite.
Ações: o comando «
Run
» recebe uma sequência de ações e executa-as por ordem. As ações mais comuns cobrem a maior parte do trabalho.
err := chromedp.Run(ctx,
chromedp.Navigate("https://example.com/search"),
chromedp.WaitVisible(`input[name="q"]`),
chromedp.SendKeys(`input[name="q"]`, "golang"),
chromedp.Click(`button[type="submit"]`, chromedp.NodeVisible),
chromedp.WaitVisible(`.results`),
chromedp.Text(`.results`, &results, chromedp.NodeVisible),
)
Para extrair dados de vários elementos, utiliza-se Nodes
ou Evaluate
:
var links []string
err := chromedp.Run(ctx,
chromedp.Navigate(url),
chromedp.Evaluate(`[...document.querySelectorAll('a')].map(a => a.href)`, &links),
)
Evaluate
executa JavaScript na página e desmarcha o resultado para um valor Go, o que é frequentemente o caminho mais curto para qualquer tarefa que envolva vários elementos ao mesmo tempo. O valor deve ser serializável em JSON.
Para ações que devolvem vários valores, as Perguntas Frequentes (FAQ) fornecem o wrapper:
chromedp.Run(ctx, chromedp.ActionFunc(func(ctx context.Context) error {
_, err := domain.SomeAction().Do(ctx)
return err
}))
ActionFunc
é também a forma de recorrer a chamadas «cdproto
» para tudo o que a API de alto nível não abrange — definir cookies, interceptar pedidos de rede, emular um dispositivo. Essa via de escape está disponível para todo o Protocolo DevTools, que é uma área bastante vasta.
Esperar da forma correta
A diferença entre um scraper fiável e um instável, e o erro é sempre o mesmo.
Não adormeça. O chromedp.Sleep(3*time.Second)
existe, é tentador e ou é demasiado curto — causando falhas intermitentes num dia mais calmo — ou demasiado longo, desperdiçando tempo em cada execução. Normalmente, ambas as situações ocorrem, em máquinas diferentes.
Espera pelo que te interessa:
chromedp.WaitVisible(`.results`, chromedp.ByQuery)
chromedp.WaitNotVisible(`.spinner`)
chromedp.WaitReady(`#content`)
WaitVisible
espera que o elemento exista e esteja visível; WaitReady
espera que ele exista no DOM. Para conteúdo carregado após uma interação, «visível» é normalmente a condição certa.
Para uma condição que nenhum seletor expressa, faz uma verificação na página:
chromedp.Poll(`document.querySelectorAll('.item').length >= 20`, nil)
Esta é a resposta para «esperar até que a lista termine de carregar», algo que nenhuma espera baseada num elemento consegue expressar.
Associe sempre a espera a um limite de tempo de contexto. Um ``WaitVisible num seletor que nunca irá corresponder bloqueia até que o contexto expire e, sem um limite de tempo, isso é para sempre.
Execução em modo headless e no Docker
O Chrome é executado em modo headless por predefinição. A secção de perguntas frequentes responde à primeira dúvida que as pessoas têm: «Por predefinição, o Chrome é executado em modo headless. Consulte DefaultExecAllocatorOptions
e veja um exemplo de como substituir as opções predefinidas.»
Para ver como funciona durante o desenvolvimento:
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.Flag("headless", false),
)
allocCtx, cancelAlloc := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancelAlloc()
ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()
NewExecAllocator
é onde se definem os parâmetros de linha de comandos do Chrome, e constitui a camada acima do contexto do navegador.
Para contentores, a recomendação do projeto é específica: «A forma mais simples é executar o programa Go que utiliza o chromedp dentro da imagem chromedp/headless-shell
. Essa imagem contém headless-shell
, uma versão mais pequena e sem interface gráfica do Chrome, que chromedp
consegue localizar de imediato.»
Vale a pena seguir esta recomendação. Compilar manualmente um Chrome funcional num contentor implica procurar bibliotecas partilhadas e pacotes de tipos de letra em falta, e o resultado é maior do que a imagem criada especificamente para o efeito.
Um comportamento específico do Linux mencionado nas Perguntas Frequentes, que surpreende quem executa o Chrome separadamente: «No Linux, chromedp
está configurado para evitar a fuga de recursos, encerrando à força quaisquer processos filhos do Chrome que tenham sido iniciados. Se precisar de iniciar uma instância do Chrome de longa duração, inicie o Chrome manualmente e ligue-se utilizando RemoteAllocator
.»
RemoteAllocator
liga-se a um navegador já em execução através do seu ponto de extremidade WebSocket, o que é o padrão para um conjunto partilhado de navegadores ou para um navegador a ser executado num contentor separado.
Utilizar um proxy
Três linhas e uma armadilha.
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.ProxyServer("http://proxy.example.com:9000"),
)
allocCtx, cancelAlloc := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancelAlloc()
ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()
ProxyServer
define o sinalizador «--proxy-server
» do Chrome.
O problema é a autenticação. O sinalizador «--proxy-server
» do Chrome não aceita credenciais — um URL que contenha um nome de utilizador e uma palavra-passe não será autenticado. Em vez disso, o Chrome responde a um desafio do proxy exibindo uma caixa de diálogo, e um navegador sem interface gráfica não tem ninguém para a preencher.
Existem duas formas de contornar isto, por ordem de preferência.
Utilizar uma lista de IPs autorizados. Se o seu endereço de origem for estável, registe-o junto do seu fornecedor e elimine completamente as credenciais. Esta é a solução mais simples e elimina o problema em vez de o contornar.
Lidar com o evento de autenticação. O chromedp pode responder ao pedido de autenticação do DevTools Protocol através de ``fetch.Enable`
com ``handleAuthRequests
`, fornecendo as credenciais programaticamente. Isto implica mais código e é a opção a seguir quando o seu endereço não é fixo.
Em seguida, verifique se funcionou, porque um proxy mal configurado num navegador não dá qualquer aviso:
var ip string
err := chromedp.Run(ctx,
chromedp.Navigate("https://api.ipify.org"),
chromedp.Text("body", &ip, chromedp.NodeVisible),
)
Execute-o com e sem a opção de proxy. Se o endereço não mudar, o Chrome não o está a utilizar — e nada lhe terá dado a entender isso. Para trabalhos com segmentação geográfica, vá mais longe e confirme se o conteúdo regionalmente distinto difere realmente, uma vez que o endereço é a parte fácil. Este é o padrão de falha silenciosa que descrevemos em por que é importante testar proxies.
Ajusta a tua localização ao teu país de saída já que estás nisso. Uma saída alemã com um cabeçalho de idioma en-US
e um fuso horário de Londres é uma combinação que nenhum visitante real produz, e muitos sites utilizam a localização independentemente do endereço:
chromedp.Flag("lang", "de-DE"),
Redução da largura de banda
Esta é a secção que permite poupar mais dinheiro e aplica-se a qualquer automação de navegadores.
Uma página com 200 KB de HTML pode chegar aos 4 MB depois de todas as imagens, tipos de letra, scripts de rastreamento e pré-carregamento de vídeos terem sido obtidos. A taxas residenciais de 0,79 $/GB — os nossos números, verificados em relação à página de preços em setembro de 2026 — essa diferença representa todo o orçamento.
Bloqueie os tipos de recursos de que não necessita. Utilizando fetch.Enable
e um ouvinte de pedidos em pausa, interrompa os pedidos de imagens, multimédia e tipos de letra:
chromedp.ListenTarget(ctx, func(ev interface{}) {
if e, ok := ev.(*fetch.EventRequestPaused); ok {
go func() {
c := chromedp.FromContext(ctx)
execCtx := cdp.WithExecutor(ctx, c.Target)
switch e.ResourceType {
case network.ResourceTypeImage, network.ResourceTypeMedia, network.ResourceTypeFont:
_ = fetch.FailRequest(e.RequestID, network.ErrorReasonBlockedByClient).Do(execCtx)
default:
_ = fetch.ContinueRequest(e.RequestID).Do(execCtx)
}
}()
}
})
Isto reduz sistematicamente a maior parte do tráfego total e, como efeito secundário, acelera as suas execuções.
Reutilize o navegador, crie separadores. O arranque do navegador é dispendioso; um separador é barato. Para uma execução que abranja muitas páginas, aloque recursos uma única vez e crie contextos de separador a partir daí.
E pergunte-se se precisa mesmo de um navegador. Se o conteúdo estiver presente no HTML inicial, uma simples solicitação HTTP custa uma fração do preço e é executada muito mais rapidamente. Verifique o código-fonte da página antes de recorrer à automação — o reflexo de renderizar tudo é a principal fonte de custos desnecessários nesta área.
Depuração quando nada funciona
A automação do navegador falha de forma inexplicável — um seletor que nunca corresponde e uma página que nunca carrega resultam no mesmo tempo de espera esgotado. Uma sequência fixa resolve a maior parte dos problemas.
Ligue o navegador e observe. O diagnóstico mais rápido de todos, e aquele que as pessoas deixam para o fim:
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.Flag("headless", false),
)
Metade das vezes, a resposta é visível imediatamente — um banner de cookies a cobrir o botão, um redirecionamento para uma página de início de sessão, um ecrã de autenticação ou um layout diferente daquele com que testou.
Capture a página quando uma execução falhar. Num ambiente «headless», isto substitui a observação:
var buf []byte
_ = chromedp.Run(ctx, chromedp.FullScreenshot(&buf, 90))
_ = os.WriteFile("failure.png", buf, 0644)
Combine-o com o HTML, uma vez que uma captura de ecrã mostra o que foi renderizado e o código-fonte mostra o que chegou:
var html string
_ = chromedp.Run(ctx, chromedp.OuterHTML("html", &html, chromedp.ByQuery))
Verifique se o seletor é resolvido antes de assumir que se trata de um problema de tempo:
var count int
_ = chromedp.Run(ctx, chromedp.Evaluate(`document.querySelectorAll('.item').length`, &count))
O valor zero indica um problema no seletor, e por mais que se espere, isso não o resolve.
Ative o registo do navegador quando suspeitar que a própria página está a apresentar erros:
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.Flag("enable-logging", true),
chromedp.Flag("v", "1"),
)
Esteja atento às mensagens da consola e aos pedidos falhados, que frequentemente explicam por que razão uma página é renderizada vazia:
chromedp.ListenTarget(ctx, func(ev interface{}) {
switch e := ev.(type) {
case *runtime.EventConsoleAPICalled:
log.Printf("console.%s", e.Type)
case *network.EventLoadingFailed:
log.Printf("failed: %s %s", e.Type, e.ErrorText)
}
})
Uma página cujas chamadas à API estão todas a falhar parece idêntica a uma página cujos seletores foram alterados, e apenas os eventos de rede as distinguem.
**E utilize o chromedp-proxy
como último recurso.** Este ferramenta situa-se entre o seu programa e o navegador e regista o tráfego do Protocolo DevTools em ambas as direções. Quando o comportamento não faz qualquer sentido, observar a troca real de protocolos geralmente explica-o numa única leitura.
Quando o chromedp é a escolha certa
Utilize-o quando já estiver a programar em Go e quiser distribuir um único binário estático sem driver, ou quando precisar de acesso direto ao Protocolo DevTools para algo que as ferramentas de nível superior não disponibilizam.
Considere o Playwright quando pretender compatibilidade entre navegadores, espera automática integrada em cada ação, rastreio e capturas de ecrã em caso de falha, ou um conjunto mais vasto de documentação e exemplos. Existe uma versão em Go, mas o ecossistema centra-se no JavaScript e no Python.
Considere o HTTP simples quando o conteúdo estiver na resposta inicial. É mais rápido, mais barato, mais simples e uma parte maior da Web serve HTML útil do que o discurso sugere.
A própria lista de recursos da FAQ é um bom guia sobre o que fazer a seguir: o repositório examples para ações complexas e capturas de ecrã de página inteira, a referência cdproto para a API do protocolo gerado e chromedp-proxy — um proxy de registo CDP — para ver exatamente o que o seu programa e o navegador estão a comunicar entre si, que é a ferramenta de depuração de último recurso e uma ferramenta genuinamente boa.
Perguntas frequentes
O que é o chromedp?
Um pacote em Go que controla navegadores que utilizam o Protocolo Chrome DevTools, sem dependências externas. Ao contrário do Selenium, não necessita de um binário de controlador e, ao contrário do Playwright, não inclui um ambiente de execução — um binário em Go e uma instalação do Chrome constituem toda a implementação.
Por que recebo a mensagem «contexto inválido» no chromedp?
Porque executou uma ação sem um «Run». A secção de perguntas frequentes explica que, por predefinição, um contexto do chromedp não tem executor. As ações são valores que chromedp.Run executa; chamar uma diretamente não tem nada que a execute.
O que significa «contexto cancelado» no chromedp?
Normalmente, significa que a ligação ao navegador foi perdida. A secção de perguntas frequentes atribui isso ao facto de o navegador ter sido fechado manualmente ou de o processo ter sido encerrado. Vale a pena distinguir esta situação de um tempo limite esgotado, uma vez que a solução é diferente — um Chrome que tenha falhado não é resolvido esperando mais tempo.
Como posso executar o chromedp com um navegador visível?
O Chrome é executado em modo «headless» por predefinição. Acrescente chromedp.Flag("headless", false) a DefaultExecAllocatorOptions e passe-os para NewExecAllocator; em seguida, obtenha o seu contexto a partir desse alocador.
Como utilizo um proxy com o chromedp?
Adicione chromedp.ProxyServer("http://host:port") às opções do alocador. As credenciais na URL não funcionarão, porque o sinalizador --proxy-server do Chrome não as aceita — utilize uma lista de IPs autorizados se o seu endereço for estável ou trate do pedido de autenticação através do DevTools Protocol.
Como é que se executa o chromedp no Docker?
Execute o seu programa Go dentro da imagem chromedp/headless-shell, o que o projeto recomenda explicitamente. Esta contém uma versão mais pequena do Chrome sem interface gráfica que o chromedp deteta sem necessidade de configuração, evitando assim ter de montar manualmente um ambiente de navegador funcional.
Como posso aguardar por um elemento no chromedp?
Utilize WaitVisible, WaitReady ou WaitNotVisible com um seletor, ou Poll com uma expressão JavaScript para condições que nenhum seletor expresse. Evite Sleep — ou é demasiado curto e instável, ou demasiado longo e dispendioso, normalmente ambos, dependendo da máquina.
Como posso reduzir a largura de banda ao utilizar o chromedp?
Bloqueie imagens, tipos de letra e ficheiros multimédia ativando a interceção de pedidos e rejeitando esses tipos de recursos, o que normalmente elimina a maior parte do tráfego. Reutilize um único navegador e crie separadores em vez de os alocar repetidamente. Verifique também se o conteúdo se encontra no HTML inicial; nesse caso, ignore completamente o navegador.
Conclusão
A curva de aprendizagem do chromedp resume-se quase inteiramente ao modelo de contexto. Assim que compreender que um contexto abrange o navegador ou o separador, que o cancelamento de um deles o fecha e que as ações não têm efeito até que um «Run» as execute, o resto da API torna-se simples.
Os hábitos que importam são os mesmos que em qualquer automação de navegador. Aguarde pelas condições em vez de entrar em modo de espera, limite cada espera com um tempo limite de contexto e reutilize um único navegador em várias abas, em vez de suportar custos de arranque repetidamente.
Há dois pontos específicos do Go que vale a pena recordar. Utilize defer cancel() em cada contexto, ou irá causar fugas de processos do Chrome — e no Linux, o chromedp encerra à força os processos filhos do Chrome que iniciou, pelo que um navegador que fique a funcionar durante muito tempo precisa de ser iniciado separadamente e acedido através de RemoteAllocator.
E se estiver a utilizar um proxy com limite de tráfego, bloqueie os tipos de recursos de que não necessita antes de fazer qualquer outra coisa. Uma página renderizada consome uma ordem de grandeza a mais do que o HTML que contém, e a maior parte desse consumo deve-se a imagens que nunca iria ver.
