html.parser en Python: analiza HTML

Publicado el: 20/08/2026
Tempo de leitura: 4 minutos
Código HTML en una pantalla que representa análisis con html.parser en Python

El módulo html.parser ofrece un parser de HTML y XHTML orientado a eventos. Se crea una subclase de HTMLParser y se sobrescriben métodos invocados al encontrar tags de apertura, cierre, texto, comentarios, referencias y declaraciones.

Es ligero, forma parte de la biblioteca estándar y tolera muchos documentos malformados de la web. Sirve para extraer enlaces, títulos, texto y metadatos, crear validadores específicos y procesar HTML en streaming. No construye un DOM completo, no ejecuta JavaScript y no sanitiza contenido.

Cómo funciona HTMLParser

El parser recibe texto con feed(). Los elementos completos generan eventos; los fragmentos incompletos se guardan hasta la siguiente llamada o hasta close().

from html.parser import HTMLParser

class DebugParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print("inicio", tag, attrs)

    def handle_endtag(self, tag):
        print("fin", tag)

    def handle_data(self, data):
        print("texto", repr(data))

parser = DebugParser()
parser.feed("<h1>Hola &amp; Python</h1>")
parser.close()

Con convert_charrefs=True, las referencias se convierten en caracteres Unicode, salvo en contextos especiales como script y style.

Extraer enlaces

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)

Los nombres de tags y atributos se convierten a minúsculas, las comillas se eliminan y los atributos sin valor reciben None. Consulta la guía de urllib.parse en Python.

Valida las URLs extraídas

Un enlace puede usar javascript:, data:, file: o una origen inesperada. El parser solo devuelve texto; no decide si es seguro.

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

Al descargar destinos, aplica protección SSRF, redirects, límites y timeout según la guía de urllib.request en Python.

Extraer texto visible

handle_data() recibe texto normal y también contenido de script y style. Mantén contexto para crear texto legible.

class TextParser(HTMLParser):
    ignored = {"script", "style", "noscript"}

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        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 puede cerrar tags en orden extraño. Un contador sirve en entradas controladas, pero no reemplaza las reglas completas de construcción de árbol HTML5.

Extraer título y 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())

Las páginas pueden contener tags duplicados, valores vacíos o metadatos generados por JavaScript. Define una política y limita el contenido acumulado.

Parsing incremental

feed() acepta fragmentos y guarda tags incompletas.

parser = LinkParser("https://example.com/")
for chunk in ["<a hr", 'ef="/docs">Doc', "umentación</a>"]:
    parser.feed(chunk)
parser.close()

Esto combina con descargas por bloques. Sin embargo, feed() requiere str. Usa un decoder incremental para no cortar secuencias UTF-8. La guía de codecs en Python explica este proceso.

Decodificación incremental con límite

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 demasiado grande")
    parser.feed(decoder.decode(chunk))

parser.feed(decoder.decode(b"", final=True))
parser.close()

Determina el encoding mediante una política fiable. Headers, BOM y declaraciones HTML pueden contradecirse. Scraping complejo puede requerir una biblioteca con reglas completas de encoding HTML.

Comentarios y declaraciones

handle_comment() recibe el contenido del comentario. handle_decl() procesa declaraciones como DOCTYPE.

class AuditParser(HTMLParser):
    def handle_comment(self, data):
        if "TODO" in data:
            print("comentario de revisión")

    def handle_decl(self, decl):
        print("declaración", decl)

Nunca pongas secretos en comentarios: se envían a todos los clientes.

Referencias de caracteres

Con convert_charrefs=True, las entidades se convierten automáticamente. Para observar la forma original usa convert_charrefs=False e implementa handle_entityref() y handle_charref().

Convertir entidades no sanitiza HTML. Una cadena con ángulos decodificados puede ser peligrosa si después se inserta como markup sin escape contextual.

Parámetro scripting

Desde Python 3.14.1, HTMLParser acepta scripting. Si es verdadero, el contenido de noscript se entrega como texto bruto en vez de parsearse. No ejecuta JavaScript.

parser = TextParser(scripting=True)

Si la subclase define __init__, acepta y reenvía los argumentos a super().__init__().

HTML inválido y límites

El parser tolera markup inválido, pero no comprueba que los cierres correspondan ni genera todos los cierres implícitos de un navegador. La secuencia de eventos puede diferir del DOM real.

Para selectores CSS, edición estructural y fidelidad HTML5 usa Beautiful Soup, lxml o html5lib. Consulta la guía de web scraping con Beautiful Soup.

Parsing no es sanitización

HTMLParser no elimina scripts, atributos de evento, URLs peligrosas ni CSS malicioso. No lo uses solo para permitir HTML de usuarios.

La sanitización necesita una biblioteca mantenida, allowlists de tags, atributos y protocolos, y escape contextual al renderizar.

No ejecutes contenido extraído

No pases texto de script a eval(), shell u otro intérprete. No abras automáticamente URLs ni uses atributos como rutas locales. Trata todo HTML externo como no confiable.

Protección de recursos

Limita bytes, cantidad de links, longitud de atributos y texto acumulado.

if len(self.links) > 10_000:
    raise ValueError("Demasiados enlaces")
if href and len(href) > 4_096:
    return

El parser no ofrece política global; la subclase debe imponerla.

Reutilización y reset

Crea una instancia por documento o llama a reset() y limpia también todo estado propio. Olvidar listas y flags mezcla resultados.

Pruebas

Incluye atributos sin comillas, entidades, comentarios, tags divididas entre chunks, anidamiento incorrecto, scripts, URLs relativas, valores vacíos y límites. Asegura que close() se llame siempre.

Errores comunes

Los fallos típicos son pasar bytes, olvidar close(), asumir estructura igual al navegador, recoger scripts, aceptar cualquier URL, omitir límites, reutilizar estado, confundir entidades con sanitización y renderizar texto extraído como HTML.

Buenas prácticas

Usa una instancia por documento, decoder incremental, límites de bytes y eventos, validación de URLs y tratamiento explícito de scripts. Elige un parser de árbol para estructura completa. Para contenido de usuarios, usa sanitizador y escape contextual.

Conclusión

html.parser es una herramienta ligera para extracción orientada a eventos. Procesa HTML en bloques, tolera markup imperfecto y recopila texto, enlaces y metadatos sin dependencias. Sus límites son claros: no es navegador, parser HTML5 completo ni sanitizador de seguridad.

Consulta la documentación oficial de html.parser y la HTML Living Standard.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Persona usando un portátil en una sesión web que representa cookies con http.cookiejar en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    http.cookiejar en Python: gestiona cookies

    Aprende http.cookiejar en Python para mantener sesiones, aplicar políticas, persistir cookies de forma segura e integrar urllib.request.

    Ler mais

    Tempo de leitura: 5 minutos
    20/08/2026
    Rack de servidores que representa conexiones HTTP de bajo nivel con http.client en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    http.client en Python: HTTP de bajo nivel

    Aprende http.client en Python para controlar conexiones HTTP y HTTPS, streaming, headers, TLS, reutilización, límites y errores.

    Ler mais

    Tempo de leitura: 4 minutos
    20/08/2026
    Teclas formando HTTP que representan solicitudes con urllib.request en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    urllib.request en Python: HTTP nativo

    Aprende urllib.request en Python para GET, POST, JSON y descargas con timeout, TLS, redirects, proxies, límites y manejo de errores.

    Ler mais

    Tempo de leitura: 4 minutos
    19/08/2026
    Cables Ethernet conectados que representan servidores de red con socketserver en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    socketserver en Python: crea servidores

    Aprende socketserver en Python para crear servidores TCP y UDP con handlers, concurrencia, límites, timeouts y apagado seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    19/08/2026
    Sala de servidores iluminada que representa conexiones TLS seguras con ssl en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ssl en Python: conexiones TLS seguras

    Aprende ssl en Python para crear clientes y servidores TLS, validar certificados y hostname, configurar CA, versiones mínimas y mTLS.

    Ler mais

    Tempo de leitura: 5 minutos
    19/08/2026
    Pantalla de verificación de cuenta que representa autenticación de mensajes con HMAC en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HMAC en Python: autentica mensajes

    Aprende HMAC en Python para firmar y validar webhooks, archivos y mensajes con SHA-256, claves seguras y comparación resistente a

    Ler mais

    Tempo de leitura: 5 minutos
    19/08/2026