Tutoriais

Web scraping com Python e BeautifulSoup: guia prático em 5 passos

Aprenda a extrair dados de sites com Python, requests e BeautifulSoup — do setup ao CSV, com tratamento de erros, paginação e os cuidados legais que ninguém explica.

Fernando Andrade

Fernando Andrade

14 de agosto de 2026· 10 min de leitura

Web scraping com Python e BeautifulSoup: guia prático em 5 passos

Nem todo site oferece uma API. Quando o dado que você precisa está visível na tela mas não existe endpoint público para buscá-lo, web scraping vira a alternativa: um programa abre a página, lê o HTML e extrai só o que interessa. Em Python, a combinação mais direta para isso são três bibliotecas — requests para baixar a página, BeautifulSoup para navegar no HTML e pandas para salvar o resultado. Este guia percorre os cinco passos do fluxo completo, com código que roda, os erros que aparecem na prática e os limites legais que você precisa respeitar antes de apertar o play.

Antes de começar

Você vai precisar de Python 3.9 ou superior instalado e de familiaridade básica com HTML — o suficiente para reconhecer uma tag, um atributo class e um id. Não é necessário saber JavaScript nem CSS a fundo, mas saber abrir o inspetor do navegador ajuda muito, e é justamente o que faremos no passo 2.

Trabalhe sempre dentro de um ambiente virtual (python -m venv .venv). Isso evita que as dependências deste projeto conflitem com outros projetos na sua máquina.

Passo 1: instale as bibliotecas

São três pacotes. O requests faz as requisições HTTP, o beautifulsoup4 interpreta o HTML e o pandas cuida da parte final, de organizar e exportar os dados.

bash
python -m venv .venv
source .venv/bin/activate   # no Windows: .venv\Scripts\activate

pip install requests beautifulsoup4 pandas

Repare que o nome do pacote é beautifulsoup4, com o número, mas na hora de importar no código ele vira bs4. É uma das confusões mais comuns de quem está começando.

Passo 2: leia o HTML antes de escrever qualquer código

Esse passo não tem código, e é o que mais economiza tempo. Abra a página no navegador, clique com o botão direito sobre o dado que você quer extrair e escolha "Inspecionar". O inspetor vai destacar exatamente a tag que contém aquele conteúdo.

O que você procura é um padrão: normalmente cada item de uma listagem está dentro de um mesmo tipo de container, com a mesma classe CSS. Se cada produto de um catálogo está em uma <div class="produto-card">, essa classe é a sua âncora — é por ela que o BeautifulSoup vai encontrar todos os itens de uma vez. Anote a tag e a classe antes de continuar.

Se o conteúdo aparece no navegador mas não no HTML retornado pelo requests, a página provavelmente é renderizada por JavaScript. Nesse caso requests e BeautifulSoup não bastam — você precisaria de uma ferramenta que execute JavaScript, como Playwright ou Selenium.

Passo 3: faça a requisição com tratamento de erro

Uma requisição sem timeout e sem verificação de status é a receita para um script que trava ou que processa silenciosamente uma página de erro achando que é a página certa.

python
import requests
from bs4 import BeautifulSoup

URL = "https://exemplo.com/produtos"
HEADERS = {
    "User-Agent": "Mozilla/5.0 (compatible; MeuScraper/1.0; +https://meusite.com/bot)"
}

try:
    resposta = requests.get(URL, headers=HEADERS, timeout=10)
    resposta.raise_for_status()
except requests.exceptions.Timeout:
    print("A requisicao demorou demais.")
    raise
except requests.exceptions.HTTPError as erro:
    print(f"Servidor respondeu com erro: {erro}")
    raise

soup = BeautifulSoup(resposta.text, "html.parser")

Três detalhes importam aqui. O timeout=10 impede que o script fique pendurado indefinidamente se o servidor não responder. O raise_for_status() transforma respostas 4xx e 5xx em exceção, em vez de deixar você processar uma página de erro como se fosse conteúdo válido. E o User-Agent identifica quem está fazendo a requisição — muitos servidores rejeitam o agente padrão do requests, e um identificador honesto, com forma de contato, é considerado boa prática.

Passo 4: extraia os dados

O BeautifulSoup tem dois métodos centrais: find() devolve a primeira ocorrência que casar com o critério, e find_all() devolve uma lista com todas. Como class é palavra reservada em Python, o parâmetro se chama class_, com underscore no final.

python
produtos = []

for card in soup.find_all("div", class_="produto-card"):
    titulo = card.find("h2", class_="titulo")
    preco = card.find("span", class_="preco")
    link = card.find("a", href=True)

    produtos.append({
        "titulo": titulo.get_text(strip=True) if titulo else None,
        "preco": preco.get_text(strip=True) if preco else None,
        "url": link["href"] if link else None,
    })

print(f"{len(produtos)} produtos encontrados")

A verificação if titulo else None não é preciosismo. Quando um único card da listagem vem sem preço — porque o produto está esgotado, por exemplo — find() devolve None, e chamar .get_text() em None derruba o script inteiro no meio da execução. Verificar antes de acessar é o que separa um scraper que roda uma vez de um que roda todo dia.

O get_text(strip=True) remove espaços e quebras de linha que o HTML costuma deixar em volta do texto. Sem ele você acaba com valores cheios de \n e espaçamento irregular no CSV final.

Passo 5: salve em CSV

Com a lista de dicionários pronta, o pandas resolve a exportação em duas linhas.

python
import pandas as pd

df = pd.DataFrame(produtos)
df.to_csv("produtos.csv", index=False, encoding="utf-8-sig")

print(df.head())

O index=False evita uma coluna extra com a numeração das linhas, que quase nunca é útil. Já o encoding="utf-8-sig" é o detalhe que resolve um problema clássico no Brasil: sem ele, o Excel abre o arquivo e mostra acentuação quebrada, com "informação" virando algo ilegível. O sufixo sig adiciona um marcador no início do arquivo que faz o Excel reconhecer o UTF-8 corretamente.

Coletando várias páginas

Listagens costumam ser paginadas. O padrão é montar a URL com o número da página e percorrer em loop — sempre com uma pausa entre as requisições.

python
import time

todos = []

for pagina in range(1, 11):
    url = f"https://exemplo.com/produtos?page={pagina}"
    resposta = requests.get(url, headers=HEADERS, timeout=10)

    if resposta.status_code != 200:
        print(f"Parando na pagina {pagina}: status {resposta.status_code}")
        break

    soup = BeautifulSoup(resposta.text, "html.parser")
    cards = soup.find_all("div", class_="produto-card")

    if not cards:
        print(f"Pagina {pagina} vazia, encerrando.")
        break

    todos.extend(cards)
    time.sleep(2)   # respeite o servidor

O time.sleep(2) entre requisições não é opcional na prática. Disparar centenas de requisições em sequência sem pausa sobrecarrega o servidor do outro lado, e é a forma mais rápida de ter seu IP bloqueado — além de ser, simplesmente, má educação técnica. Dois segundos é um ponto de partida razoável; sites menores merecem intervalos maiores.

As duas condições de parada também importam: verificar o status e verificar se a página voltou vazia evita que o loop continue rodando contra páginas inexistentes depois que a listagem acabou.

Erros comuns

  • AttributeError: 'NoneType' object has no attribute 'get_text' — o find() não encontrou o elemento. Verifique se a classe mudou ou se o item específico não tem aquele campo.
  • Status 403 ou 429 — o servidor está recusando ou limitando suas requisições. Aumente o intervalo entre elas e revise o User-Agent.
  • O HTML baixado não contém o conteúdo que você vê no navegador — a página usa JavaScript para renderizar. requests não executa JavaScript.
  • Acentuação quebrada no CSV — use encoding="utf-8-sig" na exportação.
  • O scraper funcionava e parou do nada — sites mudam de layout. Scrapers são frágeis por natureza e precisam de manutenção.

Scraping não é ilegal por si só, mas isso não significa que qualquer coleta seja permitida. Três pontos merecem atenção antes de rodar um scraper contra um site que não é seu.

O primeiro é o arquivo robots.txt, disponível na raiz de praticamente qualquer site (exemplo.com/robots.txt). Ele indica quais caminhos o site pede que robôs não acessem. Tecnicamente é uma convenção, não um mecanismo de bloqueio — mas ignorá-lo deliberadamente enfraquece bastante qualquer argumento de boa-fé.

O segundo são os Termos de Uso do site. Muitos proíbem expressamente coleta automatizada. Descumprir os termos é uma questão contratual, e plataformas grandes costumam levar isso a sério — redes sociais, em particular, historicamente agem contra scraping em massa de seus dados.

O terceiro ponto é o mais sensível no Brasil: se os dados coletados incluem informação sobre pessoas identificáveis — nome, e-mail, telefone, CPF —, você entra no escopo da LGPD (Lei 13.709/2018). Isso vale mesmo que o dado esteja publicamente visível: dado público não é dado livre. Coletar exige base legal, finalidade definida e cuidado no armazenamento. Em caso de dúvida, consulte um profissional da área jurídica — este texto é informativo e não substitui orientação legal.

Uma regra prática que evita a maior parte dos problemas: colete apenas dados agregados ou não pessoais, respeite o robots.txt, mantenha um intervalo generoso entre requisições e identifique seu bot com uma forma de contato. Se o volume que você precisa é grande a ponto de preocupar o servidor do outro lado, o caminho certo provavelmente não é scraping.

Quando não usar scraping

Se o site oferece uma API pública, use a API. Ela é mais estável — não quebra quando mudam o CSS —, costuma ser mais rápida, devolve dados já estruturados e deixa explícito que aquele uso é autorizado. Scraping é a solução para quando não existe alternativa, não a primeira opção.

Vale também considerar o custo de manutenção. Um scraper é código acoplado ao layout de um site que você não controla e que pode mudar sem aviso. Se o dado é crítico para o seu produto, depender de scraping significa aceitar que ele pode parar de funcionar em qualquer terça-feira. Para muitos casos isso é aceitável; para outros, vale procurar a fonte oficial ou negociar acesso direto.

Perguntas frequentes

Não existe lei que proíba scraping em si, mas a atividade esbarra em outras normas dependendo do que é coletado e como. Se os dados incluem informação de pessoas identificáveis, a LGPD se aplica mesmo que o dado esteja público. Além disso, os Termos de Uso do site podem proibir coleta automatizada, o que é uma questão contratual. Avalie caso a caso e busque orientação jurídica quando o uso for comercial ou envolver dados pessoais.

#Python#BeautifulSoup#Web Scraping#Pandas#Automação

Receba o melhor da tecnologia direto no seu e-mail

Uma newsletter semanal e objetiva sobre IA, programação e cloud. Sem spam.