$ python modulo_2.py
Web scraping
Extrair dados de páginas HTML de forma ética, robusta e reutilizável. Requests + BeautifulSoup são a base de 80% das automações de coleta.
O que você vai aprender
- Fazer requisições HTTP com headers e tratamento de erros
- Parsear HTML com BeautifulSoup (CSS selectors e find)
- Paginação, delays e respeito a robots.txt
- Salvar resultados em JSON e CSV
- Detectar e contornar bloqueios simples
1. Instalação
pip install requests beautifulsoup4 lxml
2. Requisição básica e boa prática
import requests
from bs4 import BeautifulSoup
import time
HEADERS = {
"User-Agent": "Mozilla/5.0 (compatible; IRNBot/1.0; +https://irndevs.com)",
"Accept-Language": "pt-BR,pt;q=0.9",
}
def get_html(url: str, tentativas: int = 3) -> str | None:
for i in range(tentativas):
try:
r = requests.get(url, headers=HEADERS, timeout=15)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except requests.RequestException as e:
print(f"Tentativa {i+1} falhou: {e}")
time.sleep(2 ** i)
return None
DICA
Sempre envie um User-Agent identificável. Em produção, respect robots.txt e adicione delays entre requisições.
3. Parsing com BeautifulSoup
html = get_html("https://exemplo.com/lista")
soup = BeautifulSoup(html, "lxml")
# CSS selector (preferido)
titulos = [el.get_text(strip=True) for el in soup.select("h2.titulo")]
# find / find_all
links = soup.find_all("a", class_="item")
for a in links:
href = a.get("href")
texto = a.get_text(strip=True)
print(texto, href)
4. Exemplo completo: coletar lista de posts
scraper_posts.py
#!/usr/bin/env python3
import json
from pathlib import Path
from bs4 import BeautifulSoup
import requests
import time
HEADERS = {"User-Agent": "IRNBot/1.0 (+https://irndevs.com)"}
BASE = "https://quotes.toscrape.com"
def scrape_pagina(num: int) -> list[dict]:
url = f"{BASE}/page/{num}/"
r = requests.get(url, headers=HEADERS, timeout=15)
r.raise_for_status()
soup = BeautifulSoup(r.text, "lxml")
itens = []
for q in soup.select("div.quote"):
itens.append({
"texto": q.select_one("span.text").get_text(strip=True),
"autor": q.select_one("small.author").get_text(strip=True),
"tags": [t.get_text(strip=True) for t in q.select("a.tag")],
})
return itens
def main():
todos = []
for pagina in range(1, 4): # primeiras 3 páginas
print(f"Página {pagina}...")
todos.extend(scrape_pagina(pagina))
time.sleep(1.5) # educado
Path("quotes.json").write_text(
json.dumps(todos, ensure_ascii=False, indent=2),
encoding="utf-8"
)
print(f"Salvos {len(todos)} itens em quotes.json")
if __name__ == "__main__":
main()
5. Paginação e parada inteligente
Muitos sites não têm número fixo de páginas. Detecte o botão “próxima” ou o fim da lista.
proxima = soup.select_one("li.next a")
if proxima:
url_proxima = BASE + proxima["href"]
else:
print("Última página alcançada")
ÉTICA E LEGAL
Scraping agressivo pode violar termos de uso. Prefira APIs oficiais quando existirem. Nunca sobrecarregue o servidor (use delays e rate limits).
6. Salvar em CSV
import csv
with open("quotes.csv", "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=["texto", "autor", "tags"])
w.writeheader()
for item in todos:
w.writerow({
"texto": item["texto"],
"autor": item["autor"],
"tags": ",".join(item["tags"]),
})
EXERCÍCIO
1. Adapte o scraper para um site de sua escolha (preços, notícias ou jobs).
2. Implemente retry com backoff exponencial.
3. Salve tanto JSON quanto CSV.
4. Adicione um campo
2. Implemente retry com backoff exponencial.
3. Salve tanto JSON quanto CSV.
4. Adicione um campo
coletado_em com datetime ISO.