O módulo html.parser oferece um parser de HTML e XHTML baseado em eventos. Você cria uma subclasse de HTMLParser e implementa métodos chamados quando aparecem tags de abertura, tags de fechamento, texto, comentários, entidades e declarações.
Ele é leve, faz parte da biblioteca padrão e tolera muitos documentos malformados encontrados na web. É útil para extrair links, títulos, texto e metadados de páginas simples, criar validadores específicos e processar HTML em streaming. Porém, não constrói uma árvore DOM completa, não executa JavaScript e não sanitiza conteúdo.
Como HTMLParser funciona
O parser recebe texto por feed(). Quando reconhece elementos completos, chama handlers sobrescritos pela subclasse. Dados incompletos ficam em buffer até a próxima chamada ou até close().
from html.parser import HTMLParser
class DebugParser(HTMLParser):
def handle_starttag(self, tag, attrs):
print("abertura", tag, attrs)
def handle_endtag(self, tag):
print("fechamento", tag)
def handle_data(self, data):
print("texto", repr(data))
parser = DebugParser()
parser.feed("<h1>Olá & Python</h1>")
parser.close()
Por padrão, convert_charrefs=True transforma entidades como & no caractere correspondente, exceto em contextos especiais como script e style.
Extraindo links
from html.parser import HTMLParser
from urllib.parse import urljoin
class LinkParser(HTMLParser):
def __init__(self, base_url: str):
super().__init__()
self.base_url = base_url
self.links: list[str] = []
def handle_starttag(self, tag, attrs):
if tag != "a":
return
attributes = dict(attrs)
href = attributes.get("href")
if href:
self.links.append(urljoin(self.base_url, href))
parser = LinkParser("https://example.com/docs/")
parser.feed('<a href="python.html">Python</a>')
parser.close()
print(parser.links)
Os nomes de tags e atributos são convertidos para minúsculas. As aspas dos valores são removidas e atributos sem valor recebem None. Use urllib.parse no Python para resolver e validar URLs.
Valide URLs extraídas
Um link pode usar javascript:, data:, file: ou apontar para uma origem inesperada. O parser apenas devolve a string; ele não decide se é segura.
from urllib.parse import urlsplit
parts = urlsplit(candidate)
if parts.scheme not in {"http", "https"}:
return
if parts.hostname not in {"example.com", "www.example.com"}:
return
Ao baixar os destinos, aplique proteção contra SSRF, redirects, limites e timeouts conforme o guia de urllib.request no Python.
Extraindo texto visível
handle_data() recebe texto comum, mas também o conteúdo de script e style. Para criar texto de leitura, acompanhe o contexto atual.
class TextParser(HTMLParser):
ignored = {"script", "style", "noscript"}
def __init__(self):
super().__init__()
self.skip_depth = 0
self.parts: list[str] = []
def handle_starttag(self, tag, attrs):
if tag in self.ignored:
self.skip_depth += 1
def handle_endtag(self, tag):
if tag in self.ignored and self.skip_depth:
self.skip_depth -= 1
def handle_data(self, data):
if self.skip_depth == 0:
cleaned = " ".join(data.split())
if cleaned:
self.parts.append(cleaned)
def text(self) -> str:
return " ".join(self.parts)
HTML malformado pode fechar tags em ordem inesperada. Um contador simples funciona para casos controlados, mas não substitui um parser com árvore e regras completas do HTML5.
Extraindo título e meta description
class MetadataParser(HTMLParser):
def __init__(self):
super().__init__()
self.in_title = False
self.title_parts = []
self.description = None
def handle_starttag(self, tag, attrs):
attributes = dict(attrs)
if tag == "title":
self.in_title = True
elif tag == "meta" and attributes.get("name", "").lower() == "description":
self.description = attributes.get("content")
def handle_endtag(self, tag):
if tag == "title":
self.in_title = False
def handle_data(self, data):
if self.in_title:
self.title_parts.append(data)
@property
def title(self):
return " ".join("".join(self.title_parts).split())
Páginas podem conter múltiplas tags, valores vazios ou metadados gerados por JavaScript. Defina uma política de seleção e limite o tamanho acumulado.
Processamento incremental
feed() aceita fragmentos. O parser mantém tags incompletas no buffer.
parser = LinkParser("https://example.com/")
for chunk in ["<a hr", 'ef="/docs">Doc', "umentação</a>"]:
parser.feed(chunk)
parser.close()
Isso combina com leitura em blocos e evita guardar a página inteira. Porém, o argumento de feed() precisa ser str. Use um decoder incremental para transformar bytes sem cortar uma sequência UTF-8. O guia de codecs no Python explica esse fluxo.
Decoder incremental com limite
import codecs
parser = TextParser()
decoder = codecs.getincrementaldecoder("utf-8")(errors="replace")
total = 0
while chunk := response.read(64 * 1024):
total += len(chunk)
if total > 5 * 1024 * 1024:
raise ValueError("HTML muito grande")
parser.feed(decoder.decode(chunk))
parser.feed(decoder.decode(b"", final=True))
parser.close()
Determine o encoding a partir de um contexto confiável. Declarações HTML, headers e BOM podem discordar. Para scraping complexo, uma biblioteca que implemente as regras de encoding do HTML pode ser mais adequada.
Comentários e declarações
handle_comment() recebe comentários sem os delimitadores. handle_decl() trata declarações como DOCTYPE.
class AuditParser(HTMLParser):
def handle_comment(self, data):
if "TODO" in data:
print("comentário de revisão encontrado")
def handle_decl(self, decl):
print("declaração", decl)
Não confie em comentários para esconder segredos; eles são enviados ao cliente e facilmente lidos.
Entidades de caracteres
Com convert_charrefs=True, entidades nomeadas e numéricas são convertidas automaticamente. Quando você precisa observar a forma original, crie o parser com convert_charrefs=False e implemente handle_entityref() e handle_charref().
A conversão de entidade não sanitiza HTML. O texto <script> pode tornar-se uma string contendo sinais de menor e maior; se depois você o inserir como HTML sem escape, pode criar uma vulnerabilidade.
O parâmetro scripting
Desde Python 3.14.1, HTMLParser aceita scripting. Quando verdadeiro, o conteúdo de noscript é entregue como texto bruto em vez de ser processado como markup. Isso aproxima o comportamento do caso em que scripting está habilitado, mas não executa JavaScript.
parser = TextParser(scripting=True)
Se sua subclasse define __init__, aceite e encaminhe os argumentos necessários para super().__init__().
HTML inválido e limites do parser
O parser tolera markup inválido, mas não verifica se tags de fechamento correspondem às de abertura e não gera automaticamente eventos de fechamento implícito. Uma página com <p><b>texto</p> pode produzir eventos que não representam a árvore construída por um navegador.
Para seletores CSS, manipulação estrutural e fidelidade ao HTML5, use Beautiful Soup, lxml ou html5lib conforme o projeto. O artigo de web scraping com Beautiful Soup mostra uma abordagem baseada em árvore.
Parsing não é sanitização
HTMLParser não remove scripts, URLs perigosas, atributos de evento ou CSS malicioso. Não use a classe sozinha para permitir HTML de usuários em uma página.
Sanitização exige uma allowlist de tags, atributos e protocolos, regras de contexto e uma biblioteca mantida para segurança. Mesmo depois de sanitizar, aplique escape no contexto correto ao renderizar.
Não execute conteúdo extraído
Nunca passe texto de script a eval(), shell ou interpretador. Não abra automaticamente links encontrados e não use valores de atributos como caminhos locais sem validação. Trate todo HTML externo como não confiável.
Proteção de recursos
Limite bytes baixados, quantidade de links, tamanho de cada atributo e texto acumulado. Um documento pode conter milhões de tags ou um atributo enorme.
if len(self.links) > 10_000:
raise ValueError("Links demais")
if href and len(href) > 4_096:
return
O parser não oferece uma política global de recursos; sua subclasse precisa aplicá-la.
Reutilização e reset
Crie uma instância por documento ou chame reset() e limpe também todo estado definido pela subclasse. Esquecer listas e flags mistura resultados de páginas diferentes.
Testando o parser
Inclua casos com atributos sem aspas, entidades, comentários, tags divididas entre chunks, tags mal fechadas, scripts, URLs relativas e valores vazios. Teste limites e certifique-se de que close() seja chamado.
Erros comuns
Os erros frequentes são alimentar bytes em vez de texto, esquecer close(), assumir uma árvore HTML5 correta, extrair texto de script, seguir qualquer URL, não limitar tamanho, reutilizar estado, confundir entidades convertidas com sanitização e renderizar texto extraído como HTML.
Boas práticas
Use uma instância por documento, decoder incremental, limites de bytes e eventos, validação de URLs e tratamento explícito de scripts. Escolha uma biblioteca de árvore quando precisar de estrutura completa. Para conteúdo de usuário, use sanitizador dedicado e escape contextual.
Conclusão
html.parser é uma ferramenta leve e útil para extração orientada a eventos. Ele processa HTML em blocos, tolera markup imperfeito e permite coletar texto, links e metadados sem dependências. Seus limites precisam ser claros: não é navegador, não é parser HTML5 completo e não é sanitizador de segurança.
Consulte a documentação oficial de html.parser e a especificação viva do HTML.







