Saltar para o conteúdo
Comece grátis
Voltar ao blogue

Web scraping com Go: criar um scraper HTML fiável

Sorin-Gabriel MaricaÚltima atualização em 15 min read
Web scraping com Go: criar um scraper HTML fiável
Resumo: Para fazer web scraping com Go, usa net/http para obter HTML estático e o `goquery` para extrair campos com seletores CSS. Comece com uma página, valide cada registo e exporte em JSON. Adicione o Colly para agendar o rastreamento ou um navegador para JavaScript apenas quando o seu alvo assim o exigir. O exemplo completo abaixo inclui tempos limite, limites de resposta, verificações de estado e erros de análise explícitos.

Um scraper que imprime um título uma única vez é fácil de demonstrar. Um scraper que o avisa quando uma página muda, um pedido falha ou um campo desaparece é muito mais útil numa aplicação em Go. A diferença reside nos limites que estabelece em torno da obtenção, análise e exportação de dados.

Este guia apresenta o web scraping com Go utilizando uma livraria de exemplo. Irá criar um módulo, obter uma página do catálogo, extrair registos de livros e escrever um ficheiro JSON. O programa está completo, pelo que pode executá-lo antes de adaptar os seletores a outra fonte. Já deve estar familiarizado com funções, estruturas, retornos de erro e o trabalho básico na linha de comandos do Go.

A mesma abordagem aplica-se a muitos projetos de web scraping em Golang: definir primeiro os campos de saída, inspecionar o HTML de origem e tornar os dados em falta observáveis. Em seguida, abordaremos a paginação, o Colly, a renderização do navegador e os testes, para que possas ampliar a primeira versão sem perder o controlo do seu comportamento.

Mantenha o critério de sucesso inicial concreto: uma página solicitada produz um conjunto válido de registos de livros ou um erro que explique por que razão não foi possível. Isso dá-lhe algo útil para avaliar antes de adicionar mais páginas ou trabalhadores.

Web scraping com Go: escolha a ferramenta certa

Comece por separar a recuperação da página, a análise do HTML e a execução no navegador. São tarefas diferentes, mesmo quando uma biblioteca agrupa várias delas. Para uma página que já contenha os registos desejados na sua resposta HTML, bastam um cliente HTTP e um analisador.

Ferramenta

Responsabilidade

Um ponto de partida útil

net/http

Enviar pedidos e ler respostas

Quer ter controlo explícito sobre as solicitações e as falhas

goquery

Selecionar e ler elementos HTML

Precisa de seletores CSS sobre o conteúdo descarregado

Colly

Coordenar pedidos e callbacks

Precisa de regras de navegação e de agendamento de rastreamento

chromedp

Controlar um navegador através do CDP

A página requer a execução ou interação com JavaScript

O exemplo abaixo combina net/http com o goquery. Isto mantém as funções de obtenção e análise suficientemente pequenas para serem testadas separadamente. Uma alteração num seletor não requer uma chamada de rede para ser verificada, e uma falha HTTP não passa despercebida dentro do código de extração.

Utilize o Colly quando o trabalho de agendamento se tornar significativo. Recorra a um navegador após inspecionar a resposta e confirmar que os campos estão ausentes do HTML inicial. Executar um navegador não garante automaticamente que os valores extraídos estejam corretos.

Para a extração de dados da Web com Go, esta escolha costuma ser mais importante do que comparar a velocidade das linguagens. Escolha a ferramenta menos complicada que devolva o conteúdo de que o seu analisador realmente necessita e, em seguida, verifique os registos resultantes.

Configurar um módulo Go reproduzível

Crie um diretório fora de qualquer módulo Go existente e inicialize um pequeno projeto:

mkdir go-book-scraper
cd go-book-scraper

go mod init example.com/book-scraper
go get github.com/PuerkitoBio/goquery@v1.13.0

O exemplo foi testado com o Go 1.26.2 e o goquery 1.13.0. Esta versão fixada do goquery declara o Go 1.25.0 como a versão mínima nos metadados do módulo. Utilize uma cadeia de ferramentas compatível, em vez de copiar a informação de um tutorial antigo sobre a versão mais recente do Go.

Mantenha go.mod e go.sum com o projeto. O guia de gestão de dependências do Go explica como os requisitos dos módulos e as somas de verificação suportam compilações reproduzíveis. Atualize as dependências de forma deliberada e volte a executar os testes do analisador após uma atualização.

O nosso alvo é o «Books to Scrape», uma livraria fictícia destinada a exercícios de scraping. O seu catálogo contém fichas de produto com um título, preço e link. O exemplo pressupõe HTML em UTF-8 e processa uma página do catálogo por cada invocação. Esse âmbito deliberadamente reduzido torna o scraping da Web com Go mais fácil de inspecionar antes de adicionar navegação ou trabalhadores simultâneos.

Criar um scraper HTML para JSON testado

Guarde o seguinte programa completo como main.go. Este extrai o título completo de cada livro, o preço apresentado e o URL absoluto do produto. Mantém os preços como cadeias de caracteres de origem, incluindo o símbolo da moeda, em vez de introduzir a conversão numérica na primeira etapa de extração.

Os seletores são específicos deste site de prática. Cada article.product_pod é um contentor de registos, h3 a contém o link do livro e o atributo do título completo, e .price_color contém o preço apresentado. Leia os campos dentro de cada cartão para que o título de um livro não possa ser acidentalmente emparelhado com o preço de outro livro.

package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"github.com/PuerkitoBio/goquery"
)

const maxBody = 2 << 20 // A 2 MiB limit chosen for this tutorial.

type Book struct {
	Title string `json:"title"`
	Price string `json:"price"`
	URL   string `json:"url"`
}

func parseBooks(r io.Reader, base *url.URL) ([]Book, error) {
	doc, err := goquery.NewDocumentFromReader(r)
	if err != nil {
		return nil, fmt.Errorf("parse HTML: %w", err)
	}
	cards := doc.Find("article.product_pod")
	if cards.Length() == 0 {
		return nil, fmt.Errorf("no book cards found")
	}
	books := make([]Book, 0, cards.Length())
	for _, node := range cards.Nodes {
		card := goquery.NewDocumentFromNode(node)
		link := card.Find("h3 a").First()
		title, _ := link.Attr("title")
		href, _ := link.Attr("href")
		price := strings.TrimSpace(card.Find(".price_color").Text())
		title = strings.TrimSpace(title)
		href = strings.TrimSpace(href)
		if title == "" || href == "" || price == "" {
			return nil, fmt.Errorf("book card has a missing required field")
		}
		target, err := base.Parse(href)
		if err != nil || (target.Scheme != "http" && target.Scheme != "https") {
			return nil, fmt.Errorf("book card has an invalid link")
		}
		books = append(books, Book{Title: title, Price: price, URL: target.String()})
	}
	return books, nil
}

func fetchBooks(ctx context.Context, client *http.Client, target string) ([]Book, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", "GoBookTutorial/1.0")
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("fetch page: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("unexpected HTTP status: %s", resp.Status)
	}
	body, err := io.ReadAll(io.LimitReader(resp.Body, maxBody+1))
	if err != nil {
		return nil, fmt.Errorf("read body: %w", err)
	}
	if len(body) > maxBody {
		return nil, fmt.Errorf("response exceeds 2 MiB")
	}
	return parseBooks(bytes.NewReader(body), resp.Request.URL)
}

func run(w io.Writer, target string) error {
	client := &http.Client{Timeout: 15 * time.Second}
	defer client.CloseIdleConnections()
	books, err := fetchBooks(context.Background(), client, target)
	if err != nil {
		return err
	}
	encoder := json.NewEncoder(w)
	encoder.SetIndent("", "  ")
	return encoder.Encode(books)
}

func main() {
	target := "https://books.toscrape.com/"
	if len(os.Args) > 1 {
		target = os.Args[1]
	}
	if err := run(os.Stdout, target); err != nil {
		log.Fatal(err)
	}
}

Execute o programa e grave a sua saída JSON apenas após uma extração bem-sucedida:

go run . > books.tmp.json && mv books.tmp.json books.json

Na primeira página verificada, o primeiro registo era «A Light in the Attic», com um preço apresentado de «£51,77» e um URL absoluto do produto. Verifique esses três campos na sua saída antes de adaptar o exemplo. Um ficheiro JSON legível, por si só, não prova que os seletores capturaram os valores pretendidos.

O ficheiro de saída temporário evita substituir o seu último conjunto de dados bem-sucedido por um ficheiro vazio, caso o pedido seguinte falhe. A saída padrão contém o documento JSON; os erros são encaminhados para o erro padrão e produzem um estado de saída diferente de zero.

O cliente HTTP tem um tempo limite de 15 segundos. A função `fetch` também limita a resposta a 2 MiB, um limite escolhido para este tutorial e não uma recomendação universal para o tamanho das páginas. Ela lê um byte extra para que um documento de tamanho excessivo resulte num erro explícito, em vez de ser silenciosamente truncado em HTML.

O contexto da solicitação é passado para o cliente. Num programa maior, substitua context.Background() no ponto de chamada pelo contexto da tarefa, para que o cancelamento possa interromper o trabalho em curso. A documentação do Go HTTP também faz uma distinção importante: uma resposta como 404 pode chegar sem que haja um erro de transporte. É por isso que a verificação do estado é separada de err.

A análise (Parsing) devolve um erro quando a estrutura esperada do cartão ou um campo obrigatório desaparece. Este exemplo rejeita a página em vez de produzir um resultado parcialmente preenchido. Trata-se de uma escolha de política que pode alterar mais tarde, mas faça-o explicitamente: registe as linhas rejeitadas se o seu pipeline de produção aceitar resultados parciais.

Por fim, os links dos produtos são resolvidos com base no URL da resposta final. Isto é importante quando uma página redireciona ou utiliza links relativos. O código valida os esquemas de links antes de os exportar; não segue esses URLs de produtos.

Para a extração de dados da Web com Go, estes limites fornecem-lhe uma base de referência útil: erros de pedido, limites de tamanho de página, falhas de seletor e erros de saída permanecem visíveis. Os registos resultantes ainda necessitam de verificações específicas da aplicação antes de os tratar como um conjunto de dados concluído.

Adicionar paginação sem um rastreio ilimitado

O programa de uma página não rastreia toda a livraria. Para inspecionar a página seguinte do catálogo, passe a sua URL explicitamente:

go run . https://books.toscrape.com/catalogue/page-2.html

Ambas as páginas do catálogo devolveram 20 registos quando verificadas para este tutorial. Trata-se de um resultado observado no site de prática, não de uma garantia quanto a futuros layouts de página ou outros sites.

Para a paginação automática, torne a descoberta um passo separado da extração de registos. Analise o link da página seguinte a partir do mesmo documento descarregado, resolva-o em relação ao URL de resposta e decida se este pertence ao próximo lote de pedidos. Evite obter uma página duas vezes apenas porque a extração e a navegação são funções separadas.

Um ciclo de paginação delimitado deve fazer o seguinte:

  1. Começar com o URL do catálogo aprovado e um limite de páginas.
  2. Recuperar e validar a página atual.
  3. Extrair registos e inspecionar o link para a página seguinte.
  4. Resolva esse link e verifique se o esquema, o host e o caminho correspondem ao esperado.
  5. Parar se não houver um link para a página seguinte, se o endereço já tiver sido visitado ou se o limite de páginas for atingido.
  6. Aguarde de acordo com os limites operacionais da fonte antes de solicitar a página seguinte.

Verifique os destinos de redirecionamento, bem como os links descobertos, se um rastreio tiver de permanecer numa única origem. Uma verificação de «mesma origem» apenas no endereço inicial não limita o destino para onde um redirecionamento HTTP envia o cliente.

Acompanhe os URLs das páginas visitadas separadamente dos identificadores de registo. Uma página de catálogo repetida e um livro repetido são problemas de duplicados diferentes. Mantenha ambas as verificações compreensíveis, em vez de eliminar todos os parâmetros de consulta e assumir que os endereços se tornam equivalentes.

O nosso guia de rastreamento do GoSpider abrange um fluxo de trabalho de descoberta dedicado. Quando a extração de dados da Web com Go vai além dos endereços conhecidos, essa separação ajuda-o a determinar se os registos em falta resultaram de uma descoberta incompleta ou de uma extração incorreta.

Utilize o Colly quando precisar de agendar rastreamentos

O Colly torna-se útil quando tem de gerir você mesmo muitos callbacks de pedidos, regras de navegação e limites por domínio. O seu coletor coordena a recuperação e os manipuladores, tais como OnHTML, OnRequest, e OnError.

Para o módulo v2, utilize o caminho do módulo com versão:

go get github.com/gocolly/colly/v2@v2.3.0

Import github.com/gocolly/colly/v2 no seu código-fonte Go. O /v2 sufixo é importante; um exemplo mais antigo que importa o caminho sem versão não está a selecionar esta versão do módulo. Se adicionar o Colly ao projeto de demonstração mais tarde, mantenha as regras de registo do analisador consistentes, em vez de alterar o esquema de saída ao mesmo tempo.

Configure AllowedDomains para restringir os destinos e utilize um LimitRule para o domínio relevante. Verifique o erro devolvido por Limit. Defina um tempo limite para o pedido e registe OnError para que as falhas nas solicitações não desapareçam por trás de callbacks.

Se ativar Async, chame Wait antes de considerar a tarefa concluída. Proteja as fatias partilhadas e os gravadores de saída, ou passe os registos para um gravador através de um canal. Um callback simultâneo que acrescente dados a uma fatia partilhada continua a ser código simultâneo e necessita de uma estratégia de sincronização.

A documentação de referência do pacote do Colly descreve estes controlos. Note-se que MaxDepth os limites determinam a profundidade dos links; não se trata de um orçamento de páginas total. Uma página superficial pode conter links para muitos destinos.

Para a extração de dados da Web com Go, uma estrutura pode reduzir o código de agendamento, mas não escolhe os seletores corretos, não valida campos de negócio nem decide o grau de completude que um conjunto de dados deve ter. Mantenha essas responsabilidades explícitas.

Lide com JavaScript e pedidos falhados de forma deliberada

Inspecione a resposta recebida antes de alterar a sua cadeia de extração. Um campo em falta pode indicar uma alteração no seletor, uma página de resposta diferente ou conteúdo que o JavaScript ainda não tenha renderizado.

Sintoma

Primeira verificação

HTTP 200 sem cartões de produto

Inspecione o corpo da resposta e confirme se é a página esperada

Os dados visíveis no navegador estão ausentes do HTML da resposta

Verifique se o JavaScript ou uma interação os fornece

HTTP 429

Reduza a frequência dos pedidos e respeite Retry-After quando aplicável

HTTP 403

Analise a resposta e as condições de acesso

Cartões válidos com valores em falta

Verifique novamente os seletores de campos e a política de campos obrigatórios

Um navegador sem interface gráfica pode executar JavaScript e realizar interações. Em Go, o chromedp controla um navegador através do Protocolo Chrome DevTools. O seu comportamento predefinido inicia o Chrome sem interface gráfica; a execução do navegador continua a requerer um ambiente de navegador adequado.

Não substitua todas as solicitações HTTP por um navegador por predefinição. Primeiro, determine qual o conteúdo que requer renderização e, em seguida, aguarde um elemento relevante ou o estado da página, em vez de depender apenas de um tempo de espera arbitrário.

Da mesma forma, não trate todas as solicitações com falha como passíveis de repetição. Defina um limite de tentativas, distinga falhas temporárias de rejeições persistentes e respeite as instruções de rastreamento da fonte. Uma string de agente do utilizador identifica este cliente do tutorial; não é uma garantia de acesso.

A extração fiável de dados da Web com Go começa por diagnosticar em que fase ocorreu a falha. Alterar os seletores não resolve um tempo limite de transporte, e aumentar a concorrência não repara uma página que não contenha os dados solicitados.

Teste os registos antes de aumentar a concorrência

As funções fetch e parse têm entradas separadas, pelo que pode testá-las sem depender de um site em constante mudança. Alimente parseBooks um pequeno fixture HTML e um URL base. Verifique o atributo «title» completo, a string do preço original e o endereço do produto resolvido, comparando-os com os valores esperados explícitos.

Em seguida, altere o fixture. Remova um preço, renomeie a classe do cartão, omita o atributo «title» e forneça um link inválido. Cada variação deve produzir a falha que o seu pipeline espera. Inclua uma página que devolva um estado HTTP de sucesso, mas que não contenha cartões de produto.

Para testar o comportamento da rede, utilize o pacote `httptest` do Go para fornecer respostas controladas. Devolva os códigos de erro 404 e 429, atrase uma resposta até que o tempo limite do cliente expire e envie um corpo de resposta que exceda o limite de tamanho configurado. Estes casos revelam falhas que uma única solicitação real bem-sucedida não consegue detetar.

O exemplo que acompanha este texto foi verificado com fixtures locais, go vete pelo detetor de corrida. O conjunto de fixtures abrangeu a saída JSON bem-sucedida e os casos de erro acima referidos, com uma cobertura de instruções de 84,3%. Duas solicitações diretas ao catálogo de prática verificaram separadamente os seletores em produção. Trata-se de validação funcional, não de um teste de desempenho.

Antes de adicionar paralelismo, torne o caminho de saída seguro para vários trabalhadores. Reutilize um cliente HTTP configurado, limite o número de trabalhadores e mantenha um único responsável pela gravação de registos. Preserve os erros com contexto de página suficiente para os investigar, mantendo as credenciais fora dos registos.

Para a extração de dados da Web com Go, aumente um limite de cada vez e verifique o número de registos válidos, bem como o número de pedidos. Um maior número de pedidos concluídos só é útil quando estes produzem os registos de que a sua aplicação necessita.

Pontos-chave

  • Separe a recuperação da extração. Um cliente HTTP obtém uma resposta; o analisador transforma o seu HTML em registos. Manter estas funções independentes permite-lhe reproduzir uma falha do seletor a partir de um fixture guardado sem ter de efetuar outro pedido em tempo real.
  • Defina o comportamento em caso de falha juntamente com o seu esquema de saída. Decida se a ausência de um título, um preço em branco ou uma página inesperada deve rejeitar uma linha ou todo o resultado. O exemplo rejeita a página, o que torna imediatamente visível uma alteração no layout.
  • Defina os limites da navegação antes de a automatizar. Destinos permitidos, endereços visitados, contagem de páginas e ritmo de navegação resolvem problemas diferentes. Trate-os como controlos explícitos quando alargar o comando de uma página para um rastreio.
  • Adicione simultaneidade depois de poder confiar nos registos. Um limite de trabalhadores e um único responsável pela saída tornam a próxima versão mais fácil de compreender. Monitorize os registos aceites e as páginas rejeitadas juntamente com os pedidos de rede.

Perguntas frequentes

O `golang.org/x/net/html` faz parte da biblioteca padrão?

Não. O analisador HTML encontra-se no módulo separado golang.org/x/net . O pacote padrão do Go html do Go e este analisador têm caminhos de importação e responsabilidades diferentes. O goquery utiliza o analisador externo por baixo da sua API de seleção. Verifique os requisitos do módulo ao seguir um tutorial, em vez de assumir que todos os pacotes mantidos no projeto Go vêm incluídos no conjunto de ferramentas.

O goquery consegue analisar HTML guardado no disco?

Sim. Abra o ficheiro e passe o seu leitor para `goquery.NewDocumentFromReader`, verificando ambos os erros. Feche o ficheiro no seu código de chamada; o analisador não se encarrega de o fechar. Quando precisar de ligações absolutas, guarde o URL original da página separadamente, pois um nome de ficheiro local não fornece o contexto de resolução do URL do site.

Como lido com páginas que não estejam em UTF-8?

Converta a entrada antes de a analisar. O pacote golang.org/x/net/html/charset fornece NewReader, que devolve um leitor que converte HTML para UTF-8 utilizando a deteção de codificação e o tipo de conteúdo declarado. Trate os erros de configuração da conversão e respeite os limites de tamanho da resposta na sua camada de recuperação. Um fixture que contenha caracteres acentuados ajuda a detetar problemas de codificação que um teste apenas em ASCII não conseguiria identificar.

Devo exportar os preços como números JSON?

Escolha a representação adequada para o utilizador. Se precisar de fazer cálculos, defina explicitamente a moeda e a escala decimal; um número inteiro em unidades menores ou uma representação decimal podem ser mais adequados do que um número de ponto flutuante binário. Preserve o valor original apresentado como um campo separado quando a rastreabilidade for importante. Não remova a pontuação cegamente: os separadores de agrupamento e decimais dependem das convenções de formatação da fonte.

Conclusão

Uma primeira implementação útil de web scraping com Go tem um contrato restrito e observável. Solicita uma página conhecida, valida a resposta, extrai um formato de registo definido e reporta falhas. O exemplo da livraria dá-lhe esse ponto de partida com um programa executável e seletores verificados em relação ao catálogo de práticas.

O seu próximo passo deve seguir o problema com que se deparar efetivamente. Adicione paginação quando os registos se estenderem por páginas conhecidas do catálogo. Introduza o Colly quando a agendamento de pedidos e os callbacks se tornarem substanciais. Utilize a renderização do navegador quando o conteúdo necessário depender de JavaScript ou de interação. Em cada caso, mantenha o esquema de saída e as regras de validação suficientemente estáveis para comparar os resultados antes e depois da alteração.

Se a manutenção da recuperação de páginas se tornar um fardo operacional separado, avalie a API Scraper da WebScrapingAPI para essa parte do fluxo de trabalho. A sua aplicação Go pode manter a responsabilidade pela análise, validação de registos e armazenamento. Compare o conteúdo devolvido com os mesmos fixtures e expectativas de campos que utiliza nas solicitações diretas.

Comece por executar o exemplo, inspecionar o seu JSON e alterar deliberadamente um seletor para observar a falha. Compreender esse percurso de falha constitui uma base prática para alargar o scraper com confiança.

Sobre o autor

Sorin-Gabriel Marica, Desenvolvedor Full-Stack @ WebScrapingAPI

Sorin-Gabriel Marica

Desenvolvedor Full-Stack

Sorin Marica é engenheiro Full Stack e DevOps na WebScrapingAPI, onde desenvolve funcionalidades do produto e mantém a infraestrutura que garante o bom funcionamento da plataforma.

Web Scraping com AWS Lambda: Guia para Python e Java 2026
Guias

Web Scraping com AWS Lambda: Guia para Python e Java 2026

TL;DR: Resumo: A extração de dados da Web com o AWS Lambda funciona melhor quando cada invocação é curta, delimitada e pode ser repetida de forma independente. Comece com HTTP direto, AWS SAM e S3 e, só depois, adicione SQS, contentores, renderização no navegador, proxies ou uma camada de recuperação gerida, apenas quando a carga de trabalho demonstrar que precisa deles.

Suciu Dan32 min read
Ler artigo
Como utilizar o GoSpider: rastrear, limpar URLs e extrair dados
Guias

Como utilizar o GoSpider: rastrear, limpar URLs e extrair dados

TL;DR: Resumo: O GoSpider é um rastreador de linha de comandos destinado a descobrir URLs, não um extrator completo de dados estruturados. Este guia sobre como utilizar o GoSpider mostra como realizar um rastreio limitado, gerir resultados de forma organizada, fazer a transferência de dados do Colly para CSV e seguir um percurso de diagnóstico para respostas 403 ou páginas que exijam renderização em JavaScript.

Suciu Dan23 min read
Ler artigo
Como fazer o Scrape Redfin: Guia Python para Dados de Propriedade
Guias

Como fazer o Scrape Redfin: Guia Python para Dados de Propriedade

TL;DR: A Redfin expõe pontos de extremidade de API ocultos que retornam JSON estruturado para listagens de propriedades, tornando possível ignorar totalmente a análise HTML frágil. Este guia orienta-o na construção de um scraper Python que extrai dados de aluguer e venda, pesquisa por localização, monitoriza novas listagens através de sitemaps XML e exporta resultados limpos para CSV ou JSON.

Suciu Dan14 min read
Ler artigo

Comece a construir

Pronto para expandir a sua recolha de dados?

Junte-se a mais de 2.000 empresas que utilizam a WebScrapingAPI para extrair dados da Web à escala empresarial, sem quaisquer custos de infraestrutura.