html.parser no Python: analise HTML

Publicado em: 20/08/2026
Tempo de leitura: 6 minutos
Código HTML em uma tela representando análise com html.parser no Python

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á &amp; Python</h1>")
parser.close()

Por padrão, convert_charrefs=True transforma entidades como &amp; no caractere correspondente, exceto em contextos especiais como script e style.

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 &lt;script&gt; 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Desenvolvedor configurando servidor HTTPS e certificado TLS com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crie servidor HTTPS local no Python

    Aprenda HTTPSServer no Python para servir HTTPS localmente, configurar certificados, usar threads e entender limites de segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026
    Arquivos protegidos representando extração segura de TAR com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extraia TAR com segurança

    Aprenda tarfile extraction_filter no Python para extrair arquivos TAR com validação, segurança e controle de caminhos.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python exibindo avisos controlados com catch_warnings
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: capture warnings em testes Python

    Aprenda catch_warnings no Python para capturar, testar e controlar avisos com filtros específicos e segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python representando referências persistentes do pickle
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serialize referências externas

    Aprenda pickle persistent_id no Python para serializar referências externas com IDs estáveis, validação, segurança e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binário representando buffers e memória no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: conte valores sem copiar buffers

    Aprenda memoryview.count no Python para contar bytes e valores em buffers sem cópias, com formatos, limites e boas práticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python com anotações de tipos
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evite imports circulares em anotações

    Aprenda annotationlib no Python para ler anotações adiadas, evitar imports circulares e criar ferramentas de introspecção seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026