beautifulsoup4 & lxml
BeautifulSoup analisa HTML/XML bagunçado em uma árvore navegável - busca por tags, atributos, texto. Combine-o com lxml como backend do parser para velocidade e XPath quando os documentos crescem ou ficam malformados.
Receita
from bs4 import BeautifulSoup
html = '<div class="item"><a href="/p/1">Widget</a><span class="price">$9</span></div>'
soup = BeautifulSoup(html, "lxml")
for item in soup.select(".item"):
name = item.find("a").get_text(strip=True)
price = item.find(class_="price").get_text(strip=True)
print(name, price)Quando usar isso:
- Extrair dados de exportações HTML ou portais legados sem APIs
- Limpar HTML de CMS antes de armazenar ou exibir
- Analisar respostas XML RSS/Atom ou SOAP
- Fixtures de teste que criam snapshots de fragmentos HTML
Exemplo de Trabalho
from __future__ import annotations
from dataclasses import dataclass
import httpx
from bs4 import BeautifulSoup
@dataclass
class Product:
sku: str
title: str
price: str
def parse_catalog(html: str) -> list[Product]:
soup = BeautifulSoup(html, "lxml")
products: list[Product] = []
for card in soup.select("article.product"):
sku = card.get("data-sku")
title_el = card.select_one("h2.title")
price_el = card.select_one("span.price")
if not sku or not title_el or not price_el:
continue
products.append(
Product(
sku=sku,
title=title_el.get_text(strip=True),
price=price_el.get_text(strip=True),
)
)
return products
def fetch_catalog(url: str) -> list[Product]:
with httpx.Client(timeout=10.0, headers={"User-Agent": "catalog-bot/1.0"}) as client:
response = client.get(url)
response.raise_for_status()
return parse_catalog(response.text)
if __name__ == "__main__":
sample = """
<article class="product" data-sku="W1">
<h2 class="title">Widget</h2><span class="price">$9.00</span>
</article>
"""
print(parse_catalog(sample))O que isso demonstra:
- Seletores CSS via
select/select_one - Análise defensiva quando nós estão ausentes
get_text(strip=True)para texto normalizado- Separação da busca HTTP da lógica de análise para testes
Mergulho Profundo
Como Funciona
- Backends de Parser -
html.parser(stdlib),lxml(C rápido),html5lib(mais flexível). - Navegação -
.parent,.next_sibling,.find,.find_all,.select(CSS). - lxml XPath -
tree.xpath("//div[@class='item']")quando CSS é complicado. - Codificação - BeautifulSoup detecta a codificação de tags meta; passe
from_encodingquando estiver incorreta.
Escolha do Parser
| Parser | Velocidade | Flexível | Dependência |
|---|---|---|---|
| lxml | Rápido | Moderado | libxml2 |
| html.parser | Lento | Moderado | nenhum |
| html5lib | Mais Lento | Muito | html5lib |
Notas Python
# Analisa XML com lxml diretamente para esquemas rigorosos
from lxml import etree
root = etree.fromstring(xml_bytes)
for node in root.xpath("//item[@id]"):
print(node.get("id"), node.text)Armadilhas
- Raspagem sem permissão - Risco legal e de Termos de Serviço. Correção: use APIs oficiais; limite as requisições.
- Seletores frágeis - Classes CSS mudam em redesenhos. Correção: prefira atributos
data-*; testes de contrato em fixtures HTML. - Não lidar com paginação - Conjuntos de dados incompletos. Correção: siga
rel=nextou cursor da API. - Executar scripts raspados - XSS se reexibido. Correção: extraia apenas texto ou sanitize com bleach.
- Memória em páginas enormes - Carregar 50MB de HTML. Correção: analise em streaming com
lxml iterparse. - Fuso horário em datas raspadas - Strings ambíguas. Correção: analise para UTC no limite da extração.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| API JSON Oficial | O provedor oferece API estável | HTML sem API e com baixa frequência de alteração |
selectolax | Velocidade máxima de análise | Precisa de exemplos do ecossistema BeautifulSoup |
| Playwright/Selenium | SPAs renderizados com JavaScript | HTML de servidor estático |
feedparser | Feeds RSS/Atom | Catálogos HTML arbitrários |
FAQs
BeautifulSoup ou lxml sozinhos?
Use BeautifulSoup para ergonomia; backend lxml para velocidade. Use lxml diretamente quando precisar de XPath em XML rigoroso.
Como lidar com URLs relativas?
urllib.parse.urljoin(base_url, href) em valores a["href"] extraídos.
Como testar parsers?
Mantenha fixtures HTML em tests/fixtures/ e afirme dataclasses analisadas - sem rede em testes unitários.
O BeautifulSoup modifica o HTML?
Ele pode "embelezar" ou reescrever tags - trate a saída como derivada, não como fonte autoritativa.
Como respeitar o robots.txt?
Use urllib.robotparser ou um framework de raspagem que verifica robots antes da busca.
E páginas autenticadas?
Passe cookies de sessão via cliente httpx - nunca cometa credenciais; use gerenciador de segredos.
Como evitar ser bloqueado?
Limite a taxa, rotacione user agents de forma responsável, cacheie respostas ETag - não faça crawling agressivo.
Analisar tabelas para CSV?
pandas.read_html usa lxml/html5lib internamente - bom para extração de tabelas pontuais.
Namespaces em XML?
Registre namespaces no XPath do lxml: namespaces={"ns": "http://..."}.
Posso usar BS4 em código async?
A análise é limitada por CPU - execute parse_catalog(html) em asyncio.to_thread após a busca assíncrona.
Relacionados
- httpx & requests - busca de HTML
- re - Expressões Regulares - limpeza de texto após análise
- Skill de Pipeline de Dados - ETL de fontes da web
- Validação de Entrada - sanitização de conteúdo
Versões de Stack: Esta página foi escrita para Python 3.14.0 (estável 3.14, manutenção 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, e uv 0.6+.