Nem toda informação que você precisa está disponível em uma API. Preços de produtos, dados públicos de portais governamentais, notícias, cotações, resultados de pesquisas — muitas vezes a única forma de obtê-los programaticamente é extraindo diretamente do HTML das páginas web. Web scraping é essa técnica, e Python tem ferramentas excelentes para isso: BeautifulSoup para páginas estáticas e Selenium para páginas que exigem JavaScript.
Considerações Éticas e Legais
Antes de qualquer código, algumas regras importantes:
- Sempre verifique o arquivo
robots.txtdo site — ele indica o que pode ou não ser coletado - Respeite os termos de serviço do site
- Não sobrecarregue servidores — adicione pausas entre requisições
- Prefira APIs oficiais quando disponíveis
- Dados pessoais coletados estão sujeitos à LGPD e ao GDPR
import requests
# Verificando robots.txt
resposta = requests.get("https://exemplo.com/robots.txt")
print(resposta.text)
requests + BeautifulSoup: Páginas Estáticas
pip install requests beautifulsoup4 lxml
import requests
from bs4 import BeautifulSoup
def obter_soup(url: str, headers: dict = None) -> BeautifulSoup:
"""Faz requisição e retorna objeto BeautifulSoup."""
headers_padrao = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
)
}
headers = {**headers_padrao, **(headers or {})}
resposta = requests.get(url, headers=headers, timeout=15)
resposta.raise_for_status()
return BeautifulSoup(resposta.text, "lxml")
# Exemplo com página de notícias
soup = obter_soup("https://news.ycombinator.com")
# Inspecionando a estrutura
print(soup.title.text) # título da página
print(soup.find("h1").text) # primeiro h1
print(soup.find("div", class_="content")) # div com classe
Seletores CSS e Navegação no DOM
from bs4 import BeautifulSoup
html = """
<html>
<body>
<div class="container">
<h1 id="titulo">Sistema Escolar</h1>
<ul class="lista-alunos">
<li class="aluno aprovado" data-id="1">
<span class="nome">Ana Silva</span>
<span class="nota">9.5</span>
</li>
<li class="aluno reprovado" data-id="2">
<span class="nome">Bruno Costa</span>
<span class="nota">4.0</span>
</li>
<li class="aluno aprovado" data-id="3">
<span class="nome">Carla Souza</span>
<span class="nota">8.5</span>
</li>
</ul>
<a href="/relatorio" class="btn">Ver Relatório</a>
<a href="/exportar" class="btn btn-primary">Exportar</a>
</div>
</body>
</html>
"""
soup = BeautifulSoup(html, "lxml")
# find() — primeiro elemento
titulo = soup.find("h1")
print(titulo.text) # Sistema Escolar
print(titulo["id"]) # titulo
# find_all() — todos os elementos
alunos = soup.find_all("li", class_="aluno")
print(len(alunos)) # 3
# Apenas aprovados
aprovados = soup.find_all("li", class_="aprovado")
# select() — seletores CSS completos
nomes = soup.select(".lista-alunos .nome")
notas = soup.select(".lista-alunos .nota")
for nome, nota in zip(nomes, notas):
print(f"{nome.text:15} — {nota.text}")
# select_one() — primeiro resultado
primeiro = soup.select_one(".aluno[data-id='1'] .nome")
print(primeiro.text) # Ana Silva
# Atributos
links = soup.find_all("a", class_="btn")
for link in links:
print(f"{link.text:20} → {link['href']}")
# Navegação
container = soup.find("div", class_="container")
print(container.find("h1").text) # filho direto
lista = container.find("ul")
primeiro_li = lista.find("li") # primeiro filho
proximo_li = primeiro_li.find_next_sibling("li") # irmão seguinte
print(proximo_li.find("span", class_="nome").text)
Exemplo Prático: Cotações de Moedas
import requests
from bs4 import BeautifulSoup
from dataclasses import dataclass
from typing import List
import time
@dataclass
class Cotacao:
moeda: str
codigo: str
compra: float
venda: float
variacao: float
def coletar_cotacoes() -> List[Cotacao]:
"""Coleta cotações do Banco Central do Brasil."""
url = "https://www.bcb.gov.br/estabilidadefinanceira/fechamentodolar"
soup = obter_soup(url)
cotacoes = []
tabela = soup.find("table")
if not tabela:
return cotacoes
linhas = tabela.find_all("tr")[1:] # pula o cabeçalho
for linha in linhas:
colunas = linha.find_all("td")
if len(colunas) < 4:
continue
try:
cotacao = Cotacao(
moeda = colunas[0].text.strip(),
codigo = colunas[1].text.strip(),
compra = float(colunas[2].text.strip().replace(",", ".")),
venda = float(colunas[3].text.strip().replace(",", ".")),
variacao = 0.0
)
cotacoes.append(cotacao)
except (ValueError, IndexError):
continue
return cotacoes
Scraping com Paginação
import requests
from bs4 import BeautifulSoup
from dataclasses import dataclass
from typing import List, Optional
import time
@dataclass
class Livro:
titulo: str
preco: float
disponivel: bool
avaliacao: int
url: str
def extrair_livros_pagina(soup: BeautifulSoup, base_url: str) -> List[Livro]:
"""Extrai todos os livros de uma página."""
livros = []
estrelas = {"One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5}
for artigo in soup.select("article.product_pod"):
titulo = artigo.find("h3").find("a")["title"]
preco_texto = artigo.find("p", class_="price_color").text
preco = float(preco_texto.replace("£", "").replace("Â", "").strip())
disponivel = "In stock" in artigo.find("p", class_="availability").text
avaliacao_cls = artigo.find("p", class_="star-rating")["class"][1]
avaliacao = estrelas.get(avaliacao_cls, 0)
link = artigo.find("h3").find("a")["href"]
livros.append(Livro(
titulo=titulo, preco=preco,
disponivel=disponivel, avaliacao=avaliacao,
url=f"{base_url}/{link}"
))
return livros
def coletar_todos_livros(max_paginas: int = 5) -> List[Livro]:
"""Percorre múltiplas páginas coletando livros."""
BASE = "https://books.toscrape.com/catalogue"
todos = []
pagina = 1
while pagina <= max_paginas:
if pagina == 1:
url = "https://books.toscrape.com/catalogue/page-1.html"
else:
url = f"{BASE}/page-{pagina}.html"
print(f"Coletando página {pagina}...")
try:
soup = obter_soup(url)
except Exception as e:
print(f" Erro na página {pagina}: {e}")
break
livros = extrair_livros_pagina(soup, BASE)
if not livros:
break
todos.extend(livros)
print(f" {len(livros)} livros coletados.")
# Verifica se há próxima página
proximo = soup.find("li", class_="next")
if not proximo:
break
pagina += 1
time.sleep(1) # pausa educada entre requisições
return todos
livros = coletar_todos_livros(max_paginas=3)
print(f"\nTotal coletado: {len(livros)} livros")
# Análise dos dados coletados
caros = sorted(livros, key=lambda l: l.preco, reverse=True)[:5]
bem_avaliados = [l for l in livros if l.avaliacao == 5]
print(f"\nTop 5 mais caros:")
for l in caros:
print(f" £{l.preco:.2f} — {l.titulo[:40]}")
print(f"\nCom 5 estrelas: {len(bem_avaliados)}")
Selenium: Páginas com JavaScript
Para páginas que carregam conteúdo dinamicamente via JavaScript — SPAs, resultados de busca, páginas com login:
pip install selenium webdriver-manager
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
import time
def criar_driver(headless: bool = True) -> webdriver.Chrome:
"""Cria e configura o driver do Chrome."""
opcoes = Options()
if headless:
opcoes.add_argument("--headless") # sem interface gráfica
opcoes.add_argument("--no-sandbox")
opcoes.add_argument("--disable-dev-shm-usage")
opcoes.add_argument("--disable-gpu")
opcoes.add_argument("--window-size=1920,1080")
opcoes.add_argument(
"user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36"
)
servico = Service(ChromeDriverManager().install())
return webdriver.Chrome(service=servico, options=opcoes)
def aguardar_elemento(driver, by, seletor, timeout=10):
"""Aguarda elemento aparecer na página."""
return WebDriverWait(driver, timeout).until(
EC.presence_of_element_located((by, seletor))
)
def aguardar_clicavel(driver, by, seletor, timeout=10):
"""Aguarda elemento ficar clicável."""
return WebDriverWait(driver, timeout).until(
EC.element_to_be_clickable((by, seletor))
)
Selenium: Interações com Formulários
def pesquisar_github(termo: str) -> list:
"""Pesquisa repositórios no GitHub."""
driver = criar_driver(headless=True)
resultados = []
try:
driver.get("https://github.com/search")
# Localiza e preenche campo de busca
campo = aguardar_elemento(driver, By.NAME, "q")
campo.clear()
campo.send_keys(termo)
campo.send_keys(Keys.RETURN)
time.sleep(2) # aguarda resultados carregarem
# Extrai resultados
itens = driver.find_elements(By.CSS_SELECTOR, "[data-testid='results-list'] > div")
for item in itens[:10]:
try:
nome = item.find_element(By.CSS_SELECTOR, "h3 a").text
link = item.find_element(By.CSS_SELECTOR, "h3 a").get_attribute("href")
try:
desc = item.find_element(By.CSS_SELECTOR, "p").text
except:
desc = ""
resultados.append({"nome": nome, "url": link, "descricao": desc})
except Exception:
continue
finally:
driver.quit()
return resultados
# Uso
repos = pesquisar_github("fastapi python")
for repo in repos[:5]:
print(f" {repo['nome']}")
if repo['descricao']:
print(f" {repo['descricao'][:60]}")
Selenium: Automação de Login
def automatizar_login(url_login: str, email: str, senha: str) -> webdriver.Chrome:
"""Realiza login em uma página e retorna o driver autenticado."""
driver = criar_driver(headless=False) # visível para debug
try:
driver.get(url_login)
# Campo de e-mail
campo_email = aguardar_elemento(driver, By.ID, "email")
campo_email.send_keys(email)
# Campo de senha
campo_senha = driver.find_element(By.ID, "password")
campo_senha.send_keys(senha)
# Botão de login
btn_login = aguardar_clicavel(driver, By.CSS_SELECTOR, "button[type='submit']")
btn_login.click()
# Aguarda redirecionamento
WebDriverWait(driver, 10).until(
EC.url_changes(url_login)
)
print(f"Login realizado. URL atual: {driver.current_url}")
return driver
except Exception as e:
driver.quit()
raise RuntimeError(f"Falha no login: {e}")
Selenium: Screenshots e Execução de JavaScript
def capturar_pagina(url: str, caminho_saida: str):
"""Captura screenshot de página completa."""
driver = criar_driver(headless=True)
try:
driver.get(url)
time.sleep(2) # aguarda carregar
# Redimensiona para capturar página inteira
altura = driver.execute_script(
"return document.body.scrollHeight"
)
driver.set_window_size(1920, altura)
driver.save_screenshot(caminho_saida)
print(f"Screenshot salvo: {caminho_saida}")
finally:
driver.quit()
def extrair_com_js(url: str) -> dict:
"""Extrai dados executando JavaScript na página."""
driver = criar_driver()
try:
driver.get(url)
time.sleep(2)
# Executa JS e retorna resultado
titulo = driver.execute_script("return document.title")
links = driver.execute_script("""
return Array.from(document.querySelectorAll('a'))
.map(a => ({texto: a.textContent.trim(), href: a.href}))
.filter(a => a.href.startsWith('https'))
.slice(0, 10);
""")
return {"titulo": titulo, "links": links}
finally:
driver.quit()
Scrapy: Para Projetos Maiores
Para scraping em escala — dezenas de sites, milhões de páginas:
pip install scrapy
scrapy startproject escola_scraper
# escola_scraper/spiders/livros.py
import scrapy
class LivrosSpider(scrapy.Spider):
name = "livros"
start_urls = ["https://books.toscrape.com"]
custom_settings = {
"DOWNLOAD_DELAY": 1,
"CONCURRENT_REQUESTS": 4,
"ROBOTSTXT_OBEY": True,
"FEEDS": {
"livros.json": {"format": "json", "encoding": "utf8"},
}
}
def parse(self, response):
# Extrai livros da página atual
for livro in response.css("article.product_pod"):
yield {
"titulo": livro.css("h3 a::attr(title)").get(),
"preco": livro.css("p.price_color::text").get(),
"avaliacao": livro.css("p.star-rating::attr(class)").get(),
}
# Segue para próxima página
proxima = response.css("li.next a::attr(href)").get()
if proxima:
yield response.follow(proxima, self.parse)
scrapy crawl livros
Exemplo Completo: Monitor de Preços
import requests
from bs4 import BeautifulSoup
from dataclasses import dataclass
from pathlib import Path
import json
import time
from datetime import datetime
@dataclass
class Produto:
nome: str
preco: float
url: str
coletado_em: str
def coletar_preco(url: str, seletor_preco: str, seletor_nome: str) -> Produto:
"""Coleta preço de um produto em qualquer loja."""
soup = obter_soup(url)
nome = soup.select_one(seletor_nome)
preco = soup.select_one(seletor_preco)
if not nome or not preco:
raise ValueError("Seletores não encontraram os elementos.")
texto_preco = preco.text.strip()
valor = float(
texto_preco
.replace("R$", "").replace("£", "")
.replace(".", "").replace(",", ".")
.strip()
)
return Produto(
nome=nome.text.strip(),
preco=valor,
url=url,
coletado_em=datetime.now().isoformat()
)
class MonitorPrecos:
def __init__(self, arquivo_dados: str = "precos.json"):
self.arquivo = Path(arquivo_dados)
self.historico: dict = self._carregar()
def _carregar(self) -> dict:
if self.arquivo.exists():
return json.loads(self.arquivo.read_text(encoding="utf-8"))
return {}
def _salvar(self):
self.arquivo.write_text(
json.dumps(self.historico, indent=2, ensure_ascii=False),
encoding="utf-8"
)
def registrar(self, produto: Produto):
chave = produto.url
if chave not in self.historico:
self.historico[chave] = {
"nome": produto.nome,
"precos": [],
"minimo": produto.preco,
"maximo": produto.preco
}
entrada = self.historico[chave]
entrada["precos"].append({
"valor": produto.preco,
"data": produto.coletado_em
})
entrada["minimo"] = min(entrada["minimo"], produto.preco)
entrada["maximo"] = max(entrada["maximo"], produto.preco)
self._salvar()
# Alerta se preço caiu abaixo do mínimo histórico
precos_anteriores = [p["valor"] for p in entrada["precos"][:-1]]
if precos_anteriores and produto.preco < min(precos_anteriores):
print(f" 🔔 ALERTA: {produto.nome}")
print(f" Novo mínimo histórico: R$ {produto.preco:.2f}")
def relatorio(self):
print("\n=== Relatório de Preços ===\n")
for url, dados in self.historico.items():
precos = [p["valor"] for p in dados["precos"]]
atual = precos[-1] if precos else 0
print(f" {dados['nome'][:40]}")
print(f" Atual: R$ {atual:.2f}")
print(f" Mínimo: R$ {dados['minimo']:.2f} | Máximo: R$ {dados['maximo']:.2f}")
print(f" Coletas: {len(precos)}\n")
# Configuração de produtos a monitorar
PRODUTOS = [
{
"url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
"seletor_preco": "p.price_color",
"seletor_nome": "h1"
},
]
monitor = MonitorPrecos()
for config in PRODUTOS:
try:
produto = coletar_preco(
config["url"],
config["seletor_preco"],
config["seletor_nome"]
)
monitor.registrar(produto)
print(f" Coletado: {produto.nome[:40]} — £{produto.preco:.2f}")
time.sleep(1)
except Exception as e:
print(f" Erro em {config['url']}: {e}")
monitor.relatorio()
Resumo
- BeautifulSoup analisa HTML estático — use com
requestspara páginas sem JavaScript find()localiza o primeiro elemento;find_all()retorna uma listaselect()usa seletores CSS — mais expressivo para estruturas complexas- Selenium controla um browser real — necessário para páginas com JavaScript dinâmico
WebDriverWaité preferível atime.sleep()— aguarda condições específicas- Adicione pausas entre requisições — respeite os servidores que você acessa
- Scrapy é a escolha para projetos de scraping em escala — pipeline completo e configurável
- Sempre verifique
robots.txte termos de serviço antes de fazer scraping
Referências e Leituras Complementares
- BeautifulSoup4 — documentação oficial — https://www.crummy.com/software/BeautifulSoup/bs4/doc/
- Selenium — documentação oficial — https://selenium-python.readthedocs.io/
- Scrapy — documentação oficial — https://docs.scrapy.org/en/latest/
- webdriver-manager — https://github.com/SergeyPirogov/webdriver_manager
- books.toscrape.com — site para prática — https://books.toscrape.com/
- quotes.toscrape.com — site para prática — https://quotes.toscrape.com/
- MITCHELL, Ryan. Web Scraping with Python. 3. ed. O'Reilly Media, 2024. — referência completa sobre scraping com Python, cobrindo BeautifulSoup, Scrapy e Selenium.
- LAWSON, Richard. Web Scraping with Python. Packt, 2015. — abordagem prática com foco em projetos reais.