使用 Python 将 HTML 转换为 Markdown:全面指南

用 Python 将 HTML 转换为干净、适合大语言模型处理的 Markdown

目录

将HTML转换为Markdown是现代开发工作流程中的基本任务,特别是在为大型语言模型(LLMs)、文档系统或静态站点生成器(如Hugo)准备网页内容时。

虽然HTML是为网页浏览器设计的,具有丰富的样式和结构,但Markdown提供了一种干净、易读的格式,非常适合文本处理、版本控制和AI消费。如果你是Markdown语法的新手,请查看我们的Markdown速查表以获取全面的参考资料。

信息图:将页面从HTML转换为Markdown

在本全面的综述中,我们将探讨六个用于HTML到Markdown转换的Python包,提供实际的代码示例、性能基准测试和实际应用案例。无论你是构建LLM训练管道、将博客迁移到Hugo,还是抓取文档,你都会找到适合你工作流程的完美工具。

替代方法: 如果你需要更智能的内容提取和语义理解,你也可以考虑使用LLM和Ollama将HTML转换为Markdown,它为复杂布局提供AI驱动的转换。

你将学到的内容:

  • 6个库的详细比较,每个库的优缺点
  • 使用真实HTML样本的性能基准测试
  • 常见用例的生产就绪代码示例
  • LLM预处理工作流程的最佳实践
  • 根据你的需求的具体推荐

为什么Markdown适用于LLM预处理?

在深入探讨工具之前,让我们了解为什么Markdown在LLM工作流程中特别有价值:

  1. 令牌效率:与HTML相比,Markdown使用更少的令牌来表示相同的内容
  2. 语义清晰度:Markdown在不使用冗长标签的情况下保留文档结构
  3. 可读性:人类和LLM都可以轻松解析Markdown的语法
  4. 一致性:标准化格式减少了模型输入中的歧义
  5. 存储:训练数据和上下文窗口的文件大小更小

Markdown的多功能性不仅限于HTML转换——你还可以将Word文档转换为Markdown用于文档工作流程,或在知识管理系统中使用,如Obsidian用于个人知识管理

TL;DR - 快速比较矩阵

如果你时间紧迫,这里是对所有六个库的全面比较概览。这张表格将帮助你快速识别哪个工具符合你的具体需求:

特性 html2text markdownify html-to-markdown trafilatura domscribe html2md
HTML5支持 部分 部分 全面 全面 全面 全面
类型提示 部分 部分
自定义处理程序 有限 优秀 良好 有限 良好 有限
表格支持 基础 基础 高级 良好 良好 良好
异步支持
内容提取 优秀 良好
元数据提取 优秀
CLI工具
速度 中等 非常快 中等 非常快
活跃开发 有限
Python版本 3.6+ 3.7+ 3.9+ 3.6+ 3.8+ 3.10+
依赖项 BS4 lxml lxml BS4 aiohttp

快速选择指南:

  • 需要速度? → trafilatura 或 html2md
  • 需要自定义? → markdownify
  • 需要类型安全? → html-to-markdown
  • 需要简单性? → html2text
  • 需要内容提取? → trafilatura

竞争者:6个Python包的比较

让我们深入探讨每个库,包括实际的代码示例、配置选项和实际见解。每个部分包括安装说明、使用模式以及对优缺点的诚实评估。

1. html2text - 经典选择

由Aaron Swartz最初开发,html2text在Python生态系统中已有十多年的历史。它专注于生成干净、易读的Markdown输出。

安装:

pip install html2text

基本使用:

import html2text

# 创建转换器实例
h = html2text.HTML2Text()

# 配置选项
h.ignore_links = False
h.ignore_images = False
h.ignore_emphasis = False
h.body_width = 0  # 不换行

html_content = """
<h1>Welcome to Web Scraping</h1>
<p>This is a <strong>comprehensive guide</strong> to extracting content.</p>
<ul>
    <li>Easy to use</li>
    <li>Battle-tested</li>
    <li>Widely adopted</li>
</ul>
<a href="https://example.com">Learn more</a>
"""

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

输出:

# Welcome to Web Scraping

This is a **comprehensive guide** to extracting content.

  * Easy to use
  * Battle-tested
  * Widely adopted

[Learn more](https://example.com)

高级配置:

import html2text

h = html2text.HTML2Text()

# 忽略特定元素
h.ignore_links = True
h.ignore_images = True

# 控制格式
h.body_width = 80  # 在80个字符处换行
h.unicode_snob = True  # 使用Unicode字符
h.emphasis_mark = '*'  # 使用*表示强调而不是_
h.strong_mark = '**'

# 处理表格
h.ignore_tables = False

# 保护预格式文本
h.protect_links = True

优点:

  • 成熟且稳定(15年以上的发展)
  • 丰富的配置选项
  • 处理边缘情况良好
  • 没有外部依赖

缺点:

  • 有限的HTML5支持
  • 可能产生不一致的间距
  • 不再积极维护(最后一次重大更新在2020年)
  • 仅支持单线程处理

最适合: 简单的HTML文档、遗留系统、当稳定性至关重要时


2. markdownify - 灵活选项

markdownify利用BeautifulSoup4提供灵活的HTML解析和可自定义的标签处理。

安装:

pip install markdownify

基本使用:

from markdownify import markdownify as md

html = """
<article>
    <h2>Modern Web Development</h2>
    <p>Building with <code>Python</code> and <em>modern frameworks</em>.</p>
    <blockquote>
        <p>Simplicity is the ultimate sophistication.</p>
    </blockquote>
</article>
"""

markdown = md(html)
print(markdown)

输出:


## Modern Web Development

Building with `Python` and *modern frameworks*.

> Simplicity is the ultimate sophistication.

高级使用与自定义处理程序:

from markdownify import MarkdownConverter

class CustomConverter(MarkdownConverter):
    """
    创建具有特定标签处理的自定义转换器
    """
    def convert_img(self, el, text, convert_as_inline):
        """自定义图像处理程序,带有替代文本"""
        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):
        """增强代码块处理,带有语言检测"""
        code = el.find('code')
        if code:
            # 从类属性中提取语言(例如,'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'

# 使用自定义转换器
html = '<pre><code class="language-python">def hello():\n    print("world")</code></pre>'
markdown = CustomConverter().convert(html)
print(markdown)

有关Markdown代码块和语法高亮的更多细节,请参阅我们的指南使用Markdown代码块

选择性标签转换:

from markdownify import markdownify as md

# 完全删除特定标签
markdown = md(html, strip=['script', 'style', 'nav'])

# 仅转换特定标签
markdown = md(
    html,
    heading_style="ATX",  # 使用#表示标题
    bullets="-",  # 使用-表示项目符号
    strong_em_symbol="*",  # 使用*表示强调
)

优点:

  • 基于BeautifulSoup4(强大的HTML解析)
  • 通过子类化高度可定制
  • 活跃维护
  • 良好的文档

缺点:

  • 需要BeautifulSoup4依赖
  • 对大型文档较慢
  • 有限的内置表格支持

最适合: 自定义转换逻辑、已经使用BeautifulSoup4的项目


3. html-to-markdown - 现代强库

html-to-markdown是一个完全类型化、现代的库,具有全面的HTML5支持和广泛的配置选项。

安装:

pip install html-to-markdown

基本使用:

from html_to_markdown import convert

html = """
<article>
    <h1>Technical Documentation</h1>
    <table>
        <thead>
            <tr>
                <th>Feature</th>
                <th>Support</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>HTML5</td>
                <td>✓</td>
            </tr>
            <tr>
                <td>Tables</td>
                <td>✓</td>
            </tr>
        </tbody>
    </table>
</article>
"""

markdown = convert(html)
print(markdown)

高级配置:

from html_to_markdown import convert, Options

# 创建自定义选项
options = Options(
    heading_style="ATX",
    bullet_style="-",
    code_language_default="python",
    strip_tags=["script", "style"],
    escape_special_chars=True,
    table_style="pipe",  # 使用|表示表格
    preserve_whitespace=False,
    extract_metadata=True,  # 提取元标签
)

markdown = convert(html, options=options)

命令行界面:

# 转换单个文件
html-to-markdown input.html -o output.md

# 使用选项转换
html-to-markdown input.html \
    --heading-style atx \
    --strip-tags script,style \
    --extract-metadata

# 批量转换
find ./html_files -name "*.html" -exec html-to-markdown {} -o ./markdown_files/{}.md \;

优点:

  • 全面的HTML5支持,包括语义元素
  • 全面的类型提示
  • 增强的表格处理(合并单元格、对齐)
  • 元数据提取能力
  • 活跃开发和现代代码库

缺点:

  • 需要Python 3.9+
  • 更大的依赖项足迹
  • 学习曲线较陡

最适合: 复杂的HTML5文档、类型安全项目、生产系统


4. trafilatura - 内容提取专家

trafilatura不仅仅是一个HTML到Markdown的转换器——它是一个专为网络抓取和文章提取设计的智能内容提取库。

安装:

pip install trafilatura

基本使用:

import trafilatura

# 从URL下载并提取
url = "https://example.com/article"
downloaded = trafilatura.fetch_url(url)
markdown = trafilatura.extract(downloaded, output_format='markdown')
print(markdown)

注意: Trafilatura包含内置的URL下载功能,但对于更复杂的HTTP操作,你可能会发现我们的cURL速查表在处理API或认证端点时很有帮助。

高级内容提取:

import trafilatura
from trafilatura.settings import use_config

# 创建自定义配置
config = use_config()
config.set("DEFAULT", "EXTRACTION_TIMEOUT", "30")

html = """
<html>
<head><title>Article Title</title></head>
<body>
    <nav>导航菜单</nav>
    <article>
        <h1>主文章</h1>
        <p>重要内容在这里。</p>
    </article>
    <aside>广告</aside>
    <footer>页脚内容</footer>
</body>
</html>
"""

# 仅提取主要内容
markdown = trafilatura.extract(
    html,
    output_format='markdown',
    include_comments=False,
    include_tables=True,
    include_images=True,
    include_links=True,
    config=config
)

# 带元数据提取
result = trafilatura.extract(
    html,
    output_format='markdown',
    with_metadata=True
)

if result:
    print(f"标题: {result.get('title', 'N/A')}")
    print(f"作者: {result.get('author', 'N/A')}")
    print(f"日期: {result.get('date', 'N/A')}")
    print(f"\n内容:\n{result.get('text', '')}")

批量处理:

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

def process_url(url):
    """从URL提取Markdown"""
    downloaded = trafilatura.fetch_url(url)
    if downloaded:
        return trafilatura.extract(
            downloaded,
            output_format='markdown',
            include_links=True,
            include_images=True
        )
    return None

# 并行处理多个URL
urls = [
    "https://example.com/article1",
    "https://example.com/article2",
    "https://example.com/article3",
]

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

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

优点:

  • 智能内容提取(去除冗余内容)
  • 内置URL下载,具有强大的错误处理
  • 元数据提取(标题、作者、日期)
  • 语言检测
  • 优化用于新闻文章和博客文章
  • 快速C型解析

缺点:

  • 可能对一般HTML去除太多内容
  • 专注于文章提取(非通用)
  • 边缘情况的配置复杂性

最适合: 网络抓取、文章提取、LLM训练数据准备


5. domscribe - 语义保留者

domscribe专注于在转换为Markdown时保留HTML的语义含义。

安装:

pip install domscribe

基本使用:

from domscribe import html_to_markdown

html = """
<article>
    <header>
        <h1>理解语义HTML</h1>
        <time datetime="2024-10-24">2024年10月24日</time>
    </header>
    <section>
        <h2>简介</h2>
        <p>语义HTML为内容提供<mark>意义</mark>。</p>
    </section>
    <aside>
        <h3>相关主题</h3>
        <ul>
            <li>无障碍</li>
            <li>SEO</li>
        </ul>
    </aside>
</article>
"""

markdown = html_to_markdown(html)
print(markdown)

自定义选项:

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)

优点:

  • 保留语义HTML5结构
  • 处理现代网页组件良好
  • 清晰的API设计

缺点:

  • 仍处于早期开发阶段(API可能会更改)
  • 相比成熟的替代方案,文档较少
  • 社区较小,可用示例较少

最适合: 语义HTML5文档、无障碍项目、当HTML5语义结构保留至关重要时

注意: 虽然domscribe较新且未经过充分测试,但它填补了语义HTML保留的特定需求,其他工具并未优先考虑这一点。


6. html2md - 异步强库

html2md专为高性能批量转换和异步处理而设计。

安装:

pip install html2md

命令行使用:

# 转换整个目录
m1f-html2md convert ./website -o ./docs

# 使用自定义设置
m1f-html2md convert ./website -o ./docs \
    --remove-tags nav,footer \
    --heading-offset 1 \
    --detect-language

# 转换单个文件
m1f-html2md convert index.html -o readme.md

编程使用:

import asyncio
from html2md import convert_html

async def convert_files():
    """异步批量转换"""
    html_files = [
        'page1.html',
        'page2.html',
        'page3.html'
    ]

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

# 运行转换
results = asyncio.run(convert_files())

优点:

  • 异步处理,高性能
  • 智能内容选择器检测
  • YAML frontmatter生成(非常适合Hugo!)
  • 代码语言检测
  • 并行处理支持

缺点:

  • 需要Python 3.10+
  • CLI导向(API灵活性较低)
  • 文档可以更全面

最适合: 大规模迁移、批量转换、Hugo/Jekyll迁移


性能基准测试

性能很重要,尤其是在处理数千个文档用于LLM训练或大规模迁移时。了解库之间的相对速度差异有助于你为工作流程做出明智的决策。

比较性能分析:

根据典型的使用模式,以下是这些库在三种现实场景中的比较:

  1. 简单HTML:带有文本、标题和链接的基本博客文章(5KB)
  2. 复杂HTML:带有嵌套表格和代码块的技术文档(50KB)
  3. 真实网站:包含导航、页脚、侧边栏和广告的完整网页(200KB)

以下是你可以用来自己测试这些库的示例基准代码:

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

def benchmark(html_content, iterations=100):
    """基准测试转换速度"""

    # 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
    }

典型性能特征(代表性的相对速度):

简单(5KB) 复杂(50KB) 真实网站(200KB)
html2text 中等 更慢 最慢
markdownify 更慢 更慢 最慢
html-to-markdown
trafilatura 非常快 非常快
html2md(异步) 非常快 非常快 最快

关键观察:

  • html2mdtrafilatura在复杂文档中最快,使它们成为批量处理的理想选择
  • html-to-markdown在生产使用中提供了速度和功能的最佳平衡
  • markdownify较慢但最灵活——当需要自定义处理程序时,这种权衡是值得的
  • html2text显示出其年龄的迹象,速度较慢,但对简单用例仍稳定

注意: 当处理数百或数千个文件时,性能差异才变得显著。对于偶尔的转换,任何库都可以正常工作。关注功能和自定义选项而不是性能。

实际应用场景

理论是有帮助的,但实际例子可以展示这些工具在生产环境中的工作方式。以下是四个常见场景,包含完整的、可直接用于您项目的生产就绪代码。

应用场景 1:LLM 训练数据准备

需求:从数千个文档页面中提取干净的文本

推荐trafilatura + 并行处理

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

def process_html_file(html_path):
    """将 HTML 文件转换为 markdown"""
    html = Path(html_path).read_text(encoding='utf-8')
    markdown = trafilatura.extract(
        html,
        output_format='markdown',
        include_links=False,  # 为更干净的训练数据移除
        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

# 并行处理 10,000 个文件
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"已处理 {len(html_files)} 个文件")
print(f"总字符数: {sum(token_counts):,}")

应用场景 2:Hugo 博客迁移

需求:将 WordPress 博客迁移到 Hugo 并包含 frontmatter

推荐html2md CLI

Hugo 是一个流行的静态网站生成器,使用 Markdown 作为内容格式。如需更多 Hugo 特定的技巧,请查看我们的 Hugo 快速参考,并了解如何 向 Hugo 添加结构化数据标记 以获得更好的 SEO。

# 转换所有包含 frontmatter 的文章
m1f-html2md convert ./wordpress-export \
    -o ./hugo/content/posts \
    --generate-frontmatter \
    --heading-offset 0 \
    --remove-tags script,style,nav,footer

或者通过编程方式:

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

def migrate_post(html_file):
    """将 WordPress HTML 转换为 Hugo markdown"""
    html = Path(html_file).read_text()

    # 从 HTML 中提取标题和日期
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, 'html.parser')
    title = soup.find('h1').get_text() if soup.find('h1') else 'Untitled'

    # 转换为 markdown
    options = Options(strip_tags=['script', 'style', 'nav', 'footer'])
    markdown = convert(html, options=options)

    # 添加 Hugo frontmatter
    frontmatter = {
        'title': title,
        'date': '2024-10-24',
        'draft': False,
        'tags': []
    }

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

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

# 处理所有文章
for html_file in Path('./wordpress-export').glob('*.html'):
    migrate_post(html_file)

应用场景 3:带有自定义格式的文档抓取器

需求:抓取带有自定义代码块处理的技术文档

推荐:使用 markdownify 和自定义转换器

这种方法在从维基系统迁移文档时特别有用。如果您正在管理文档,您可能还对 DokuWiki - 自托管维基及其替代方案 感兴趣,以获取自托管文档解决方案。

from markdownify import MarkdownConverter
import requests

class DocsConverter(MarkdownConverter):
    """用于技术文档的自定义转换器"""

    def convert_pre(self, el, text, convert_as_inline):
        """增强的代码块,带语法高亮"""
        code = el.find('code')
        if code:
            # 从类中提取语言
            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):
        """处理特殊文档块"""
        classes = el.get('class', [])

        # 警告块
        if 'warning' in classes:
            return f'\n> ⚠️ **警告**: {text}\n'

        # 信息块
        if 'info' in classes or 'note' in classes:
            return f'\n> 💡 **提示**: {text}\n'

        return text

def scrape_docs(url):
    """抓取并转换文档页面"""
    response = requests.get(url)
    markdown = DocsConverter().convert(response.text)
    return markdown

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

应用场景 4:新闻通讯转 Markdown 归档

需求:将 HTML 电子邮件新闻通讯转换为可读的 markdown

推荐:使用 html2text 并进行特定配置

import html2text
import email
from pathlib import Path

def convert_newsletter(email_file):
    """将 HTML 电子邮件转换为 markdown"""
    # 解析电子邮件
    with open(email_file, 'r') as f:
        msg = email.message_from_file(f)

    # 获取 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

    # 配置转换器
    h = html2text.HTML2Text()
    h.ignore_images = False
    h.images_to_alt = True
    h.body_width = 0
    h.protect_links = True
    h.unicode_snob = True

    # 转换
    markdown = h.handle(html_content)

    # 添加元数据
    subject = msg.get('Subject', '无标题')
    date = msg.get('Date', '')

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

    return output

# 处理新闻通讯归档
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')

按场景推荐

仍然不确定选择哪个库?这里是我根据特定使用场景的最终指南。这些推荐来自于在生产环境中对每个库的实际使用经验。

对于网页抓取和 LLM 预处理

最佳选择trafilatura

Trafilatura 在提取干净内容的同时去除冗余内容,非常适合:

  • 构建 LLM 训练数据集
  • 内容聚合
  • 研究论文收集
  • 新闻文章提取

对于 Hugo/Jekyll 迁移

最佳选择html2md

异步处理和 frontmatter 生成使批量迁移快速且简单:

  • 批量转换
  • 自动元数据提取
  • YAML frontmatter 生成
  • 标题级别调整

对于自定义转换逻辑

最佳选择markdownify

通过继承转换器实现完全控制:

  • 自定义标签处理
  • 领域特定转换
  • 特殊格式要求
  • 与现有 BeautifulSoup 代码集成

对于类型安全的生产系统

最佳选择html-to-markdown

现代、类型安全且功能齐全:

  • 完整的 HTML5 支持
  • 全面的类型提示
  • 高级表格处理
  • 持续维护

对于简单、稳定的转换

最佳选择html2text

当您需要一个“开箱即用”的解决方案时:

  • 无依赖
  • 经过实战考验
  • 丰富的配置
  • 广泛的平台支持

LLM 预处理的最佳实践

无论您选择哪个库,遵循这些最佳实践将确保高质量的 Markdown 输出,优化用于 LLM 消费。这些模式在处理数百万文档的生产工作流程中已被证明是必不可少的。

1. 转换前进行清理

在转换前始终移除不需要的元素,以获得更干净的输出和更好的性能:

from bs4 import BeautifulSoup
import trafilatura

def clean_and_convert(html):
    """在转换前移除不需要的元素"""
    soup = BeautifulSoup(html, 'html.parser')

    # 移除不需要的元素
    for element in soup(['script', 'style', 'nav', 'footer', 'header', 'aside']):
        element.decompose()

    # 移除广告和跟踪
    for element in soup.find_all(class_=['ad', 'advertisement', 'tracking']):
        element.decompose()

    # 转换清理后的 HTML
    markdown = trafilatura.extract(
        str(soup),
        output_format='markdown'
    )

    return markdown

2. 规范化空格

不同的转换器对空格的处理方式不同。规范化输出以确保在您的语料库中保持一致性:

import re

def normalize_markdown(markdown):
    """清理 markdown 空格"""
    # 移除多个空行
    markdown = re.sub(r'\n{3,}', '\n\n', markdown)

    # 移除尾随空格
    markdown = '\n'.join(line.rstrip() for line in markdown.split('\n'))

    # 确保结尾只有一个换行
    markdown = markdown.rstrip() + '\n'

    return markdown

3. 验证输出

质量控制至关重要。实现验证以尽早发现转换错误:

def validate_markdown(markdown):
    """验证 markdown 质量"""
    issues = []

    # 检查是否有 HTML 残留
    if '<' in markdown and '>' in markdown:
        issues.append("检测到 HTML 标签")

    # 检查是否有损坏的链接
    if '[' in markdown and ']()' in markdown:
        issues.append("检测到空链接")

    # 检查是否有过多的代码块
    code_block_count = markdown.count('```')
    if code_block_count % 2 != 0:
        issues.append("未闭合的代码块")

    return len(issues) == 0, issues

4. 批量处理模板

在处理大型文档集合时,使用此生产就绪模板,包含适当的错误处理、日志记录和并行处理:

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):
    """处理单个 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:
            # 规范化
            markdown = normalize_markdown(markdown)

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

            # 保存
            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"处理 {html_path} 时出错: {e}")
        return False

def batch_convert(input_dir, max_workers=4):
    """转换目录中的所有 HTML 文件"""
    html_files = list(Path(input_dir).rglob('*.html'))
    logger.info(f"找到 {len(html_files)} 个 HTML 文件")

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

    success_count = sum(results)
    logger.info(f"成功转换了 {success_count}/{len(html_files)} 个文件")

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

结论

Python 生态系统提供了成熟、生产就绪的工具用于 HTML 到 Markdown 的转换,每个工具都针对不同的场景进行了优化。 您的选择应与您的具体需求相匹配:

  • 快速转换:使用 html2text,因其简单和零依赖
  • 自定义逻辑:使用 markdownify,通过继承实现最大灵活性
  • 网页抓取:使用 trafilatura,用于智能内容提取并去除冗余内容
  • 批量迁移:使用 html2md,用于大规模项目的异步性能
  • 生产系统:使用 html-to-markdown,用于类型安全和全面的 HTML5 支持
  • 语义保留:使用 domscribe,用于保留 HTML5 语义结构

LLM 工作流程的建议

对于 LLM 预处理工作流程,建议采用两层方法:

  1. 首先使用 trafilatura 进行初始内容提取——它智能地移除导航、广告和冗余内容,同时保留主要内容
  2. 对于需要精确结构保留的复杂文档,使用 html-to-markdown,例如带有表格和代码块的技术文档

这种组合可以有效处理 95% 的实际应用场景。

下一步

所有这些工具(除了 html2text)都处于积极维护状态并适用于生产环境。建议:

  1. 安装 2-3 个与您的使用场景匹配的库
  2. 使用您的实际 HTML 样本测试它们
  3. 使用您典型的文档大小进行性能基准测试
  4. 根据输出质量选择,而不仅仅是速度

Python 的 HTML 到 Markdown 转换生态系统已经显著成熟,对于其预期使用场景,您选择的任何工具都不会出错。

其他资源

注意:此比较基于对官方文档、社区反馈和库架构的分析。性能特征代表典型使用模式。对于特定使用场景,请使用您自己的实际 HTML 样本进行基准测试。

其他有用文章