Convertir HTML a Markdown con Python: Una guía completa

Python para convertir HTML en Markdown limpio y listo para LLM

Índice

Convertir HTML a Markdown es una tarea fundamental en los flujos de trabajo de desarrollo modernos, especialmente cuando se prepara contenido web para Modelos de Lenguaje Grande (LLMs), sistemas de documentación o generadores de sitios estáticos como Hugo.

Aunque el HTML está diseñado para navegadores web con riqueza de estilo y estructura, el Markdown ofrece un formato limpio y legible que es ideal para el procesamiento de texto, el control de versiones y la consumición por parte de IA. Si eres nuevo en la sintaxis de Markdown, consulta nuestro Markdown Cheatsheet para una referencia completa.

infografía: convertir página de html a markdown

En esta revisión completa, exploraremos seis paquetes de Python para la conversión de HTML a Markdown, proporcionando ejemplos prácticos de código, benchmarks de rendimiento y casos de uso reales. Ya estés construyendo un pipeline de entrenamiento de LLM, migrando un blog a Hugo o raspando documentación, encontrarás la herramienta perfecta para tu flujo de trabajo.

Enfoque alternativo: Si necesitas una extracción de contenido más inteligente con comprensión semántica, también podrías considerar convertir HTML a Markdown usando LLM y Ollama, que ofrece una conversión impulsada por IA para diseños complejos.

Lo que aprenderás:

  • Comparación detallada de 6 bibliotecas con pros y contras de cada una
  • Benchmarks de rendimiento con muestras reales de HTML
  • Ejemplos de código listos para producción para casos de uso comunes
  • Mejores prácticas para flujos de trabajo de preprocesamiento de LLM
  • Recomendaciones específicas según tus requisitos

¿Por qué Markdown para el preprocesamiento de LLM?

Antes de sumergirnos en las herramientas, entendamos por qué Markdown es especialmente valioso para flujos de trabajo de LLM:

  1. Eficiencia de tokens: Markdown utiliza significativamente menos tokens que HTML para el mismo contenido
  2. Claridad semántica: Markdown preserva la estructura del documento sin etiquetas verbosas
  3. Legibilidad: Tanto humanos como LLM pueden analizar fácilmente la sintaxis de Markdown
  4. Consistencia: Un formato estandarizado reduce la ambigüedad en las entradas del modelo
  5. Almacenamiento: Tamaños de archivo más pequeños para datos de entrenamiento y ventanas de contexto

La versatilidad de Markdown va más allá de la conversión de HTML: también puedes convertir documentos de Word a Markdown para flujos de trabajo de documentación, o usarlo en sistemas de gestión de conocimiento como Obsidian para gestión de conocimiento personal.

TL;DR - Matriz de comparación rápida

Si estás apurado, aquí hay una comparación completa de todas las seis bibliotecas a primera vista. Esta tabla te ayudará a identificar rápidamente qué herramienta coincide con tus requisitos específicos:

Característica html2text markdownify html-to-markdown trafilatura domscribe html2md
Soporte HTML5 Parcial Parcial Completo Completo Completo Completo
Tipos de datos No No Parcial No Parcial
Manejadores personalizados Limitado Excelente Bueno Limitado Bueno Limitado
Soporte de tablas Básico Básico Avanzado Bueno Bueno Bueno
Soporte asíncrono No No No No No
Extracción de contenido No No No Excelente No Bueno
Extracción de metadatos No No Excelente No
Herramienta CLI No No No
Velocidad Media Lenta Rápida Muy rápida Media Muy rápida
Desarrollo activo No Limitado
Versión de Python 3.6+ 3.7+ 3.9+ 3.6+ 3.8+ 3.10+
Dependencias Ninguna BS4 lxml lxml BS4 aiohttp

Guía rápida de selección:

  • Necesitas velocidad? → trafilatura o html2md
  • Necesitas personalización? → markdownify
  • Necesitas seguridad de tipos? → html-to-markdown
  • Necesitas simplicidad? → html2text
  • Necesitas extracción de contenido? → trafilatura

Los contendientes: 6 paquetes de Python comparados

Vamos a profundizar en cada biblioteca con ejemplos prácticos de código, opciones de configuración y insights del mundo real. Cada sección incluye instrucciones de instalación, patrones de uso y evaluaciones honestas de fortalezas y limitaciones.

1. html2text - La opción clásica

Originalmente desarrollado por Aaron Swartz, html2text ha sido un pilar en el ecosistema de Python durante más de una década. Se centra en producir salida de Markdown limpia y legible.

Instalación:

pip install html2text

Uso básico:

import html2text

# Crear instancia del convertidor
h = html2text.HTML2Text()

# Configurar opciones
h.ignore_links = False
h.ignore_images = False
h.ignore_emphasis = False
h.body_width = 0  # No envolver líneas

html_content = """
<h1>Bienvenido al Web Scraping</h1>
<p>Este es un <strong>guía completa</strong> para extraer contenido.</p>
<ul>
    <li>Fácil de usar</li>
    <li>Bien probado</li>
    <li>Ampliamente adoptado</li>
</ul>
<a href="https://example.com">Aprender más</a>
"""

markdown = h.handle(html_content)
print(markdown)

Salida:

# Bienvenido al Web Scraping

Este es un **guía completa** para extraer contenido.

  * Fácil de usar
  * Bien probado
  * Ampliamente adoptado

[Aprender más](https://example.com)

Configuración avanzada:

import html2text

h = html2text.HTML2Text()

# Saltar elementos específicos
h.ignore_links = True
h.ignore_images = True

# Controlar el formato
h.body_width = 80  # Envolver en 80 caracteres
h.unicode_snob = True  # Usar caracteres unicode
h.emphasis_mark = '*'  # Usar * para énfasis en lugar de _
h.strong_mark = '**'

# Manejar tablas
h.ignore_tables = False

# Proteger texto pre-formateado
h.protect_links = True

Ventajas:

  • Madura y estable (más de 15 años de desarrollo)
  • Opciones de configuración extensas
  • Maneja bien casos límite
  • Sin dependencias externas

Desventajas:

  • Soporte limitado de HTML5
  • Puede producir espaciado inconsistente
  • No se mantiene activamente (última actualización mayor en 2020)
  • Solo procesamiento en un solo hilo

Mejor para: Documentos HTML simples, sistemas legados, cuando la estabilidad es primordial


2. markdownify - La opción flexible

markdownify aprovecha BeautifulSoup4 para proporcionar un análisis flexible de HTML con manejo personalizable de etiquetas.

Instalación:

pip install markdownify

Uso básico:

from markdownify import markdownify as md

html = """
<article>
    <h2>Desarrollo web moderno</h2>
    <p>Construyendo con <code>Python</code> y <em>marcos modernos</em>.</p>
    <blockquote>
        <p>La simplicidad es la sofisticación ultimate.</p>
    </blockquote>
</article>
"""

markdown = md(html)
print(markdown)

Salida:


## Desarrollo web moderno

Construyendo con `Python` y *marcos modernos*.

> La simplicidad es la sofisticación ultimate.

Uso avanzado con manejadores personalizados:

from markdownify import MarkdownConverter

class CustomConverter(MarkdownConverter):
    """
    Crear convertidor personalizado con manejo de etiquetas específico
    """
    def convert_img(self, el, text, convert_as_inline):
        """Manejador personalizado de imágenes con texto alternativo"""
        alt = el.get('alt', '')
        src = el.get('src', '')
        title = el.get('title', '')

        if title:
            return f'![{alt}]({src} "{title}")'
        return f'![{alt}]({src})'

    def convert_pre(self, el, text, convert_as_inline):
        """Manejo mejorado de bloques de código con detección de lenguaje"""
        code = el.find('code')
        if code:
            # Extraer lenguaje de atributo de clase (ej. 'language-python')
            classes = code.get('class', [''])
            language = classes[0].replace('language-', '') if classes else ''
            return f'\n```{language}\n{code.get_text()}\n```\n'
        return f'\n```\n{text}\n```\n'

# Usar convertidor personalizado
html = '<pre><code class="language-python">def hello():\n    print("world")</code></pre>'
markdown = CustomConverter().convert(html)
print(markdown)

Para más detalles sobre el uso de bloques de código Markdown y resaltado de sintaxis, consulta nuestra guía sobre Uso de bloques de código Markdown.

Conversión selectiva de etiquetas:

from markdownify import markdownify as md

# Eliminar completamente etiquetas específicas
markdown = md(html, strip=['script', 'style', 'nav'])

# Convertir solo etiquetas específicas
markdown = md(
    html,
    heading_style="ATX",  # Usar # para encabezados
    bullets="-",  # Usar - para viñetas
    strong_em_symbol="*",  # Usar * para énfasis
)

Ventajas:

  • Basado en BeautifulSoup4 (análisis robusto de HTML)
  • Muy personalizable mediante subclase
  • Mantenimiento activo
  • Buena documentación

Desventajas:

  • Requiere dependencia de BeautifulSoup4
  • Puede ser más lento para documentos grandes
  • Soporte limitado de tablas predefinido

Mejor para: Lógica de conversión personalizada, proyectos que ya usan BeautifulSoup4


3. html-to-markdown - El poderoso moderno

html-to-markdown es una biblioteca completamente tipada, moderna con soporte completo de HTML5 y opciones de configuración extensas.

Instalación:

pip install html-to-markdown

Uso básico:

from html_to_markdown import convert

html = """
<article>
    <h1>Documentación técnica</h1>
    <table>
        <thead>
            <tr>
                <th>Característica</th>
                <th>Soporte</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>HTML5</td>
                <td>✓</td>
            </tr>
            <tr>
                <td>Tablas</td>
                <td>✓</td>
            </tr>
        </tbody>
    </table>
</article>
"""

markdown = convert(html)
print(markdown)

Configuración avanzada:

from html_to_markdown import convert, Options

# Crear opciones personalizadas
options = Options(
    heading_style="ATX",
    bullet_style="-",
    code_language_default="python",
    strip_tags=["script", "style"],
    escape_special_chars=True,
    table_style="pipe",  # Usar | para tablas
    preserve_whitespace=False,
    extract_metadata=True,  # Extraer etiquetas meta
)

markdown = convert(html, options=options)

Interfaz de línea de comandos:

# Convertir un solo archivo
html-to-markdown input.html -o output.md

# Convertir con opciones
html-to-markdown input.html \
    --heading-style atx \
    --strip-tags script,style \
    --extract-metadata

# Conversión por lotes
find ./html_files -name "*.html" -exec html-to-markdown {} -o ./markdown_files/{}.md \;

Ventajas:

  • Soporte completo de HTML5 incluyendo elementos semánticos
  • Seguro de tipos con pistas completas
  • Manejo mejorado de tablas (celdas fusionadas, alineación)
  • Capacidad de extracción de metadatos
  • Desarrollo activo y código base moderno

Desventajas:

  • Requiere Python 3.9+
  • Huella de dependencia más grande
  • Curva de aprendizaje más pronunciada

Mejor para: Documentos HTML5 complejos, proyectos seguros de tipos, sistemas de producción


4. trafilatura - El especialista en extracción de contenido

trafilatura no es solo una herramienta de conversión de HTML a Markdown, sino una biblioteca inteligente de extracción de contenido especialmente diseñada para el raspado web y la extracción de artículos.

Instalación:

pip install trafilatura

Uso básico:

import trafilatura

# Descargar e extraer desde URL
url = "https://example.com/articulo"
descargado = trafilatura.fetch_url(url)
markdown = trafilatura.extract(descargado, output_format='markdown')
print(markdown)

Nota: Trafilatura incluye descarga integrada de URLs, pero para operaciones HTTP más complejas, podrías encontrar útil nuestra Guía de comandos cURL al trabajar con APIs o puntos finales autenticados.

Extracción avanzada de contenido:

import trafilatura
from trafilatura.settings import use_config

# Crear configuración personalizada
config = use_config()
config.set("DEFAULT", "EXTRACTION_TIMEOUT", "30")

html = """
<html>
<head><title>Título del artículo</title></head>
<body>
    <nav>Menú de navegación</nav>
    <article>
        <h1>Artículo principal</h1>
        <p>Contenido importante aquí.</p>
    </article>
    <aside>Publicidad</aside>
    <footer>Contenido del pie de página</footer>
</body>
</html>
"""

# Extraer solo contenido principal
markdown = trafilatura.extract(
    html,
    output_format='markdown',
    include_comments=False,
    include_tables=True,
    include_images=True,
    include_links=True,
    config=config
)

# Extraer con metadatos
resultado = trafilatura.extract(
    html,
    output_format='markdown',
    with_metadata=True
)

if resultado:
    print(f"Título: {resultado.get('title', 'N/A')}")
    print(f"Autor: {resultado.get('author', 'N/A')}")
    print(f"Fecha: {resultado.get('date', 'N/A')}")
    print(f"\nContenido:\n{resultado.get('text', '')}")

Procesamiento por lotes:

import trafilatura
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path

def process_url(url):
    """Extraer markdown desde URL"""
    descargado = trafilatura.fetch_url(url)
    if descargado:
        return trafilatura.extract(
            descargado,
            output_format='markdown',
            include_links=True,
            include_images=True
        )
    return None

# Procesar múltiples URLs en paralelo
urls = [
    "https://example.com/articulo1",
    "https://example.com/articulo2",
    "https://example.com/articulo3",
]

with ThreadPoolExecutor(max_workers=5) as executor:
    resultados = list(executor.map(process_url, urls))

for i, markdown in enumerate(resultados):
    if markdown:
        Path(f"articulo_{i}.md").write_text(markdown, encoding='utf-8')

Ventajas:

  • Extracción inteligente de contenido (elimina elementos de boilerplate)
  • Descarga integrada de URLs con manejo robusto de errores
  • Extracción de metadatos (título, autor, fecha)
  • Detección de idioma
  • Optimizado para artículos de noticias y blogs
  • Parseo rápido basado en C

Desventajas:

  • Puede eliminar demasiado contenido para HTML general
  • Enfocado en extracción de artículos (no general)
  • Complejidad de configuración para casos límite

Mejor para: Raspado web, extracción de artículos, preparación de datos de entrenamiento para LLM


5. domscribe - El preservador semántico

domscribe se centra en preservar el significado semántico del HTML al convertirlo a Markdown.

Instalación:

pip install domscribe

Uso básico:

from domscribe import html_to_markdown

html = """
<article>
    <header>
        <h1>Entendiendo HTML semántico</h1>
        <time datetime="2024-10-24">24 de octubre de 2024</time>
    </header>
    <section>
        <h2>Introducción</h2>
        <p>El HTML semántico proporciona <mark>significado</mark> al contenido.</p>
    </section>
    <aside>
        <h3>Temas relacionados</h3>
        <ul>
            <li>Accesibilidad</li>
            <li>SEO</li>
        </ul>
    </aside>
</article>
"""

markdown = html_to_markdown(html)
print(markdown)

Opciones personalizadas:

from domscribe import html_to_markdown, MarkdownOptions

options = MarkdownOptions(
    preserve_semantic_structure=True,
    include_aria_labels=True,
    strip_empty_elements=True
)

markdown = html_to_markdown(html, options=options)

Ventajas:

  • Preserva la estructura semántica de HTML5
  • Maneja bien componentes modernos de la web
  • Diseño de API limpio

Desventajas:

  • Aún en desarrollo temprano (API puede cambiar)
  • Documentación limitada en comparación con alternativas maduras
  • Comunidad más pequeña y menos ejemplos disponibles

Mejor para: Documentos HTML5 semánticos, proyectos enfocados en accesibilidad, cuando la preservación de la estructura semántica de HTML5 es crítica

Nota: Aunque domscribe es más nuevo y menos probado que alternativas, llena una necesidad específica para la preservación de la estructura semántica de HTML que otras herramientas no priorizan.


6. html2md - El poderoso asíncrono

html2md está diseñado para conversiones de alto rendimiento en lotes con procesamiento asíncrono.

Instalación:

pip install html2md

Uso desde línea de comandos:

# Convertir directorio completo
m1f-html2md convert ./website -o ./docs

# Con configuración personalizada
m1f-html2md convert ./website -o ./docs \
    --remove-tags nav,footer \
    --heading-offset 1 \
    --detect-language

# Convertir un solo archivo
m1f-html2md convert index.html -o readme.md

Uso programático:

import asyncio
from html2md import convert_html

async def convert_files():
    """Conversión asíncrona por lotes"""
    html_files = [
        'page1.html',
        'page2.html',
        'page3.html'
    ]

    tasks = [convert_html(file) for file in html_files]
    results = await asyncio.gather(*tasks)
    return results

# Ejecutar conversión
resultados = asyncio.run(convert_files())

Ventajas:

  • Procesamiento asíncrono para alto rendimiento
  • Detección inteligente de selección de contenido
  • Generación de YAML frontmatter (ideal para Hugo!)
  • Detección de lenguaje de código
  • Soporte de procesamiento paralelo

Desventajas:

  • Requiere Python 3.10+
  • Enfoque en CLI (menos flexible API)
  • Documentación podría ser más completa

Mejor para: Migraciones a gran escala, conversiones por lotes, migraciones de Hugo/Jekyll


Benchmarking de rendimiento

El rendimiento importa, especialmente cuando se procesan miles de documentos para el entrenamiento de LLM o migraciones a gran escala. Entender las diferencias relativas de velocidad entre bibliotecas te ayuda a tomar decisiones informadas para tu flujo de trabajo.

Análisis comparativo de rendimiento:

Basado en patrones típicos de uso, aquí está cómo se comparan estas bibliotecas en tres escenarios realistas:

  1. HTML simple: Post de blog básico con texto, encabezados y enlaces (5KB)
  2. HTML complejo: Documentación técnica con tablas anidadas y bloques de código (50KB)
  3. Sitio web real: Página completa incluyendo navegación, pie de página, barra lateral y anuncios (200KB)

Aquí hay un ejemplo de código de benchmark que puedes usar para probar estas bibliotecas por ti mismo:

import time
import html2text
from markdownify import markdownify
from html_to_markdown import convert
import trafilatura

def benchmark(html_content, iterations=100):
    """Benchmark de velocidad de conversión"""

    # html2text
    start = time.time()
    h = html2text.HTML2Text()
    for _ in range(iterations):
        _ = h.handle(html_content)
    html2text_time = time.time() - start

    # markdownify
    start = time.time()
    for _ in range(iterations):
        _ = markdownify(html_content)
    markdownify_time = time.time() - start

    # html-to-markdown
    start = time.time()
    for _ in range(iterations):
        _ = convert(html_content)
    html_to_markdown_time = time.time() - start

    # trafilatura
    start = time.time()
    for _ in range(iterations):
        _ = trafilatura.extract(html_content, output_format='markdown')
    trafilatura_time = time.time() - start

    return {
        'html2text': html2text_time,
        'markdownify': markdownify_time,
        'html-to-markdown': html_to_markdown_time,
        'trafilatura': trafilatura_time
    }

Características de rendimiento típicas (velocidades relativas representativas):

Paquete Simple (5KB) Complejo (50KB) Sitio real (200KB)
html2text Moderada Más lenta Más lenta
markdownify Más lenta Más lenta Más lenta
html-to-markdown Rápida Rápida Rápida
trafilatura Rápida Muy rápida Muy rápida
html2md (asíncrono) Muy rápida Muy rápida Más rápida

Observaciones clave:

  • html2md y trafilatura son los más rápidos para documentos complejos, lo que los hace ideales para procesamiento por lotes
  • html-to-markdown ofrece el mejor equilibrio entre velocidad y características para uso en producción
  • markdownify es más lenta pero más flexible—el intercambio vale la pena cuando necesitas manejadores personalizados
  • html2text muestra su edad con un rendimiento más lento, pero sigue siendo estable para casos de uso simples

Nota: Las diferencias de rendimiento se vuelven significativas solo cuando se procesan cientos o miles de archivos. Para conversiones ocasionales, cualquier biblioteca funcionará bien. Enfócate en características y opciones de personalización en su lugar.

Casos de uso reales

La teoría es útil, pero los ejemplos prácticos demuestran cómo funcionan estas herramientas en producción. Aquí hay cuatro escenarios comunes con código completo y listo para producción que puedes adaptar para tus propios proyectos.

Caso de uso 1: Preparación de datos de entrenamiento para LLM

Requisito: Extraer texto limpio de miles de páginas de documentación

Recomendado: trafilatura + procesamiento paralelo

import trafilatura
from pathlib import Path
from concurrent.futures import ProcessPoolExecutor

def process_html_file(html_path):
    """Convertir archivo HTML a markdown"""
    html = Path(html_path).read_text(encoding='utf-8')
    markdown = trafilatura.extract(
        html,
        output_format='markdown',
        include_links=False,  # Eliminar para datos de entrenamiento más limpios
        include_images=False,
        include_comments=False
    )

    if markdown:
        output_path = html_path.replace('.html', '.md')
        Path(output_path).write_text(markdown, encoding='utf-8')
        return len(markdown)
    return 0

# Procesar 10,000 archivos en paralelo
html_files = list(Path('./docs').rglob('*.html'))

with ProcessPoolExecutor(max_workers=8) as executor:
    token_counts = list(executor.map(process_html_file, html_files))

print(f"Procesados {len(html_files)} archivos")
print(f"Caracteres totales: {sum(token_counts):,}")

Caso de uso 2: Migración de blog de Hugo

Requisito: Migrar blog de WordPress a Hugo con frontmatter

Recomendado: html2md CLI

Hugo es un popular generador de sitios estáticos que utiliza Markdown para el contenido. Para más consejos específicos de Hugo, consulta nuestra Hoja de trucos de Hugo y aprende sobre Agregar marcado de datos estructurados a Hugo para mejorar el SEO.

# Convertir todos los posts con frontmatter
m1f-html2md convert ./wordpress-export \
    -o ./hugo/content/posts \
    --generate-frontmatter \
    --heading-offset 0 \
    --remove-tags script,style,nav,footer

O de forma programática:

from html_to_markdown import convert, Options
from pathlib import Path
import yaml

def migrate_post(html_file):
    """Convertir HTML de WordPress a markdown de Hugo"""
    html = Path(html_file).read_text()

    # Extraer título y fecha del HTML
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, 'html.parser')
    title = soup.find('h1').get_text() if soup.find('h1') else 'Sin título'

    # Convertir a markdown
    options = Options(strip_tags=['script', 'style', 'nav', 'footer'])
    markdown = convert(html, options=options)

    # Añadir frontmatter de Hugo
    frontmatter = {
        'title': title,
        'date': '2024-10-24',
        'draft': False,
        'tags': []
    }

    output = f"---\n{yaml.dump(frontmatter)}---\n\n{markdown}"

    # Guardar
    output_file = html_file.replace('.html', '.md')
    Path(output_file).write_text(output, encoding='utf-8')

# Procesar todos los posts
for html_file in Path('./wordpress-export').glob('*.html'):
    migrate_post(html_file)

Caso de uso 3: Scraper de documentación con formato personalizado

Requisito: Extraer documentación técnica con manejo personalizado de bloques de código

Recomendado: markdownify con convertidor personalizado

Este enfoque es especialmente útil para migrar documentación desde sistemas de wiki. Si estás gestionando documentación, también podrías estar interesado en DokuWiki - wiki autohospedado y alternativas para soluciones de documentación autohospedadas.

from markdownify import MarkdownConverter
import requests

class DocsConverter(MarkdownConverter):
    """Convertidor personalizado para documentación técnica"""

    def convert_pre(self, el, text, convert_as_inline):
        """Bloque de código mejorado con resaltado de sintaxis"""
        code = el.find('code')
        if code:
            # Extraer lenguaje desde la clase
            classes = code.get('class', [])
            language = next(
                (c.replace('language-', '') for c in classes if c.startswith('language-')),
                'text'
            )
            return f'\n```{language}\n{code.get_text()}\n```\n'
        return super().convert_pre(el, text, convert_as_inline)

    def convert_div(self, el, text, convert_as_inline):
        """Manejar bloques especiales de documentación"""
        classes = el.get('class', [])

        # Bloques de advertencia
        if 'warning' in classes:
            return f'\n> ⚠️ **Advertencia**: {text}\n'

        # Bloques de información
        if 'info' in classes or 'note' in classes:
            return f'\n> 💡 **Nota**: {text}\n'

        return text

def scrape_docs(url):
    """Extraer y convertir página de documentación"""
    response = requests.get(url)
    markdown = DocsConverter().convert(response.text)
    return markdown

# Usar
docs_url = "https://docs.example.com/api-reference"
markdown = scrape_docs(docs_url)
Path('api-reference.md').write_text(markdown)

Caso de uso 4: Archivo de boletines en formato Markdown

Requisito: Convertir boletines en formato HTML a markdown legible

Recomendado: html2text con configuración específica

import html2text
import email
from pathlib import Path

def convert_newsletter(email_file):
    """Convertir boletín HTML a markdown"""
    # Parsear correo
    with open(email_file, 'r') as f:
        msg = email.message_from_file(f)

    # Obtener contenido HTML
    html_content = None
    for part in msg.walk():
        if part.get_content_type() == 'text/html':
            html_content = part.get_payload(decode=True).decode('utf-8')
            break

    if not html_content:
        return None

    # Configurar convertidor
    h = html2text.HTML2Text()
    h.ignore_images = False
    h.images_to_alt = True
    h.body_width = 0
    h.protect_links = True
    h.unicode_snob = True

    # Convertir
    markdown = h.handle(html_content)

    # Añadir metadatos
    subject = msg.get('Subject', 'Sin asunto')
    date = msg.get('Date', '')

    output = f"# {subject}\n\n*Fecha: {date}*\n\n---\n\n{markdown}"

    return output

# Procesar archivo de boletines
for email_file in Path('./newsletters').glob('*.eml'):
    markdown = convert_newsletter(email_file)
    if markdown:
        output_file = email_file.with_suffix('.md')
        output_file.write_text(markdown, encoding='utf-8')

Recomendaciones por escenario

¿Aún no estás seguro de qué biblioteca elegir? Aquí tienes mi guía definitiva basada en casos de uso específicos. Estas recomendaciones provienen de la experiencia práctica con cada biblioteca en entornos de producción.

Para scraping web y preprocesamiento de LLM

Ganador: trafilatura

Trafilatura destaca por extraer contenido limpio mientras elimina elementos innecesarios. Ideal para:

  • Crear conjuntos de datos de entrenamiento para LLM
  • Agregación de contenido
  • Recolección de artículos de investigación
  • Extracción de artículos de noticias

Para migraciones de Hugo/Jekyll

Ganador: html2md

El procesamiento asincrónico y la generación de frontmatter hacen que las migraciones masivas sean rápidas y fáciles:

  • Conversiones por lotes
  • Extracción automática de metadatos
  • Generación de frontmatter en YAML
  • Ajuste de niveles de encabezado

Para lógica de conversión personalizada

Ganador: markdownify

Subclase el convertidor para tener completo control:

  • Manejadores personalizados de etiquetas
  • Conversiones específicas del dominio
  • Requisitos de formato especial
  • Integración con código existente de BeautifulSoup

Para sistemas de producción con tipos seguros

Ganador: html-to-markdown

Moderno, con tipos seguros y completo en características:

  • Soporte completo para HTML5
  • Indicaciones de tipo completas
  • Manejo avanzado de tablas
  • Mantenimiento activo

Para conversiones simples y estables

Ganador: html2text

Cuando necesitas algo que “funcione”:

  • Sin dependencias
  • Probado en batallas
  • Configuración extensa
  • Soporte en múltiples plataformas

Buenas prácticas para preprocesamiento de LLM

Independientemente de la biblioteca que elijas, seguir estas buenas prácticas garantizará una salida de Markdown de alta calidad optimizada para el consumo por parte de LLM. Estos patrones han demostrado ser esenciales en flujos de trabajo de producción que procesan millones de documentos.

1. Limpiar antes de convertir

Siempre elimina elementos no deseados antes de la conversión para obtener una salida más limpia y un mejor rendimiento:

from bs4 import BeautifulSoup
import trafilatura

def clean_and_convert(html):
    """Eliminar elementos no deseados antes de la conversión"""
    soup = BeautifulSoup(html, 'html.parser')

    # Eliminar elementos no deseados
    for element in soup(['script', 'style', 'nav', 'footer', 'header', 'aside']):
        element.decompose()

    # Eliminar anuncios y seguimiento
    for element in soup.find_all(class_=['ad', 'advertisement', 'tracking']):
        element.decompose()

    # Convertir HTML limpio
    markdown = trafilatura.extract(
        str(soup),
        output_format='markdown'
    )

    return markdown

2. Normalizar espacios en blanco

Diferentes convertidores manejan los espacios en blanco de manera diferente. Normaliza la salida para garantizar coherencia en tu corpus:

import re

def normalize_markdown(markdown):
    """Limpiar espaciado en markdown"""
    # Eliminar múltiples líneas en blanco
    markdown = re.sub(r'\n{3,}', '\n\n', markdown)

    # Eliminar espacio en blanco final
    markdown = '\n'.join(line.rstrip() for line in markdown.split('\n'))

    # Asegurar una sola línea en blanco al final
    markdown = markdown.rstrip() + '\n'

    return markdown

3. Validar la salida

El control de calidad es esencial. Implementa validación para detectar errores de conversión temprano:

def validate_markdown(markdown):
    """Validar calidad del markdown"""
    issues = []

    # Verificar restos de HTML
    if '<' in markdown and '>' in markdown:
        issues.append("Detectados tags HTML")

    # Verificar enlaces rotos
    if '[' in markdown and ']()' in markdown:
        issues.append("Detección de enlace vacío")

    # Verificar bloques de código excesivos
    code_block_count = markdown.count('```')
    if code_block_count % 2 != 0:
        issues.append("Bloque de código no cerrado")

    return len(issues) == 0, issues

4. Plantilla de procesamiento por lotes

Cuando procesas grandes colecciones de documentos, usa esta plantilla lista para producción con manejo adecuado de errores, registro y procesamiento paralelo:

from pathlib import Path
from concurrent.futures import ProcessPoolExecutor
import trafilatura
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def process_file(html_path):
    """Procesar un solo archivo HTML"""
    try:
        html = Path(html_path).read_text(encoding='utf-8')
        markdown = trafilatura.extract(
            html,
            output_format='markdown',
            include_links=True,
            include_images=False
        )

        if markdown:
            # Normalizar
            markdown = normalize_markdown(markdown)

            # Validar
            is_valid, issues = validate_markdown(markdown)
            if not is_valid:
                logger.warning(f"{html_path}: {', '.join(issues)}")

            # Guardar
            output_path = Path(str(html_path).replace('.html', '.md'))
            output_path.write_text(markdown, encoding='utf-8')

            return True

        return False

    except Exception as e:
        logger.error(f"Error procesando {html_path}: {e}")
        return False

def batch_convert(input_dir, max_workers=4):
    """Convertir todos los archivos HTML en un directorio"""
    html_files = list(Path(input_dir).rglob('*.html'))
    logger.info(f"Encontrados {len(html_files)} archivos HTML")

    with ProcessPoolExecutor(max_workers=max_workers) as executor:
        results = list(executor.map(process_file, html_files))

    success_count = sum(results)
    logger.info(f"Convertidos con éxito {success_count}/{len(html_files)} archivos")

# Uso
batch_convert('./html_docs', max_workers=8)

Conclusión

La ecosistema de Python ofrece herramientas maduras y listas para producción para la conversión de HTML a Markdown, cada una optimizada para diferentes escenarios. Tu elección debe alinearse con tus requisitos específicos:

  • Conversiones rápidas: Usa html2text por su simplicidad y ausencia de dependencias
  • Lógica personalizada: Usa markdownify para máxima flexibilidad mediante subclase
  • Scraping web: Usa trafilatura para extracción inteligente de contenido con eliminación de elementos innecesarios
  • Migraciones masivas: Usa html2md para rendimiento asincrónico en proyectos a gran escala
  • Sistemas de producción: Usa html-to-markdown para seguridad de tipos y soporte completo de HTML5
  • Preservación semántica: Usa domscribe para mantener la estructura semántica de HTML5

Recomendaciones para flujos de trabajo de LLM

Para flujos de trabajo de preprocesamiento de LLM, se recomienda un enfoque de dos niveles:

  1. Comenzar con trafilatura para la extracción inicial de contenido — elimina navegación, anuncios y elementos innecesarios mientras preserva el contenido principal
  2. Recurrir a html-to-markdown para documentos complejos que requieren preservación precisa de la estructura, como documentación técnica con tablas y bloques de código

Esta combinación maneja eficazmente el 95% de los escenarios reales.

Pasos siguientes

Todas estas herramientas (excepto html2text) están activamente manteniéndose y listas para producción. Es mejor:

  1. Instalar 2-3 bibliotecas que coincidan con tu caso de uso
  2. Probarlas con tus muestras reales de HTML
  3. Benchmarkear el rendimiento con los tamaños típicos de documentos
  4. Elegir basado en la calidad de la salida, no solo en la velocidad

El ecosistema de Python para la conversión de HTML a Markdown ha madurado significativamente, y no te equivocarás con ninguna de estas opciones para sus casos de uso específicos.

Recursos adicionales

Nota: Esta comparación se basa en el análisis de la documentación oficial, comentarios de la comunidad y la arquitectura de la biblioteca. Las características de rendimiento son representativas de los patrones típicos de uso. Para casos de uso específicos, ejecuta tus propios benchmarks con tus muestras reales de HTML.

Otros artículos útiles