HTML转Markdown实战指南:从正文提取到格式还原的完整方案
2026/9/15 17:38:16 网站建设 项目流程

大概两个月前,我帮一位朋友迁移博客,他丢过来几十个从网页保存的 HTML 文件,说“帮我把正文转成 Markdown 吧,我要统一丢进笔记系统”。我当时还以为这是个 20 分钟就能搞完的体力活,结果真正动手才发现,HTML 转 Markdown 这件事远没有想象中那么简单。直接复制粘贴全是乱掉的样式,用在线转换器又经常把导航栏、评论区、广告一起带进来,整理完的“正文”比原网页还长。后来我把整个流程拆开,从正文提取、标签清洗,到格式还原、批量处理,一步一步调,才终于攒出一套能稳定复用的操作方案。这篇实战指南就是把这些经验和坑全部记录下来,适合经常搞网页内容采集、博客迁移、知识库整理,或者想在编辑器里把网页片断转成干净 Markdown 的人。

1. 思路拆解:HTML转Markdown到底难在哪

1.1 直接复制粘贴为什么总是翻车

很多人最初接触这个问题时,都想着“从网页复制,直接在 Markdown 编辑器里粘贴不就行了”。试过一次你就会发现,几乎每个网站都在跟你作对。浏览器复制出来的是富文本内容,里面夹杂着大量内联样式、<div><span>的嵌套结构。你粘到 Typora、Obsidian 或者 VS Code 里,表面上看起来还能显示,只要点开源码模式,铺天盖地的<style><font>class="..."就能把你淹没。

更麻烦的是,现在的网页很少是纯静态结构。为了布局方便,很多正文被拆成了几十个小块,分别包在<div>里,中间穿插着图片、卡片、引用块、代码高亮组件。这种结构不是靠“复制粘贴”能解决的,哪怕你用浏览器自带的“开发者工具”去选“Copy outerHTML”,得到的也还是一坨带着大量无关标签的代码。直接用 Markdown 正则去清洗的话,很容易把正文里的代码示例、标签字符也一起误删,越洗越乱。

我还见过一种更隐蔽的翻车方式:有一些网站本身是前后端分离的,正文是接口返回的 JSON 数据,HTML 只是壳子。你抓下来的是整个页面骨架,正文根本不在里面。如果你一直盯着“HTML 转 Markdown”这个问题,很容易忽略掉“先确认正文到底在不在 HTML 里”这一步。

1.2 正文整理的核心目标

所以“HTML 转 Markdown 正文整理”这件事,真正要处理的不是“把标签换一下”,而是两层问题:

第一层是正文提取。把页面里真正属于文章主体的内容挑出来,去掉导航、页脚、侧边栏、评论区、推荐阅读、广告位、脚本样式。市面上很多在线转换器不管这层,它们只是把整个 HTML 全部转成 Markdown,结果转换完以后,标题、列表、代码块、表格全都挤在一起,正文反而找不到。真正的整理流程应该先做正文提取,再做格式转换,顺序不能反。

第二层是结构还原。HTML 转成 Markdown 后,要确保几个关键信息不丢:标题层级、段落分隔、列表嵌套、代码块语言标识、表格结构、图片链接、引用块、数学公式。Markdown 本身是一种“约定优先”的标记语言,它对空行的要求非常严格。比如 HTML 里<p>text</p>天然是段落,但转成 Markdown 以后,如果你不保留段落之间的空行,渲染出来就会挤成一段;再比如<table>转成 Markdown 表格时,如果单元格里有|字符或者换行,表格就会瞬间崩掉。这些细节才是正文整理的真正难点。

很多人一上来就问“有没有一个命令直接把 HTML 变成 Markdown”,我的回答是:转换命令确实存在,但如果你不先想清楚上面两层问题,无论用什么工具,产出的内容依然没法直接用。把思路理顺之后,我们再来看工具选型,会轻松很多。

2. 工具选型:不同的场景,不同的方案

2.1 轻量转换:html2text

如果你要处理的 HTML 是已经提取好的正文片段,比如只有几个<p><h2><img>,那最简单的工具就是 Python 社区的老牌库html2text。它做的事情非常纯粹:把 HTML 标签翻译成 Markdown 标记,比如<h1>变成#<b>变成**<a>变成[text](url)<img>变成![alt](src)

使用方式很简单,先安装:

pip install html2text

然后写一个小脚本:

import html2text converter = html2text.HTML2Text() converter.ignore_links = False converter.body_width = 0 # 不自动折行 markdown_text = converter.handle(html_content) print(markdown_text)

它的优点是轻量、无外部依赖、用来处理干净的 HTML 片段非常高效。但也有一个硬伤:它不做正文提取。如果你把整个网页丢给它,它会把导航链接、脚本里的文字、页脚版权信息全部转成 Markdown,你得到的还是一堆噪音。所以我通常只把html2text用在“已经确定了正文边界”的场景,比如从爬虫里拿到了明确的 article 容器。

2.2 命令行万金油:Pandoc

如果说html2text是小刀,那 Pandoc 就是瑞士军刀。它能把 HTML 转成 Markdown,也能把 Markdown 转成 Word、PDF、EPUB、LaTeX,几乎什么文档格式之间的转换都能做。最常用的一条命令是这样:

pandoc input.html -o output.md

默认情况下,Pandoc 会保留文档的标题结构、列表、表格、代码块,并且会把 HTML 里的粗体、斜体、链接、图片都转换成对应的 Markdown 语法。它还能处理行内公式,比如 LateX 语法的$...$会保留下来。

Pandoc 最大的优势是转换质量非常稳,尤其是表格和代码块。HTML 里的<table>到了 Pandoc 手里,大部分能转成标准的 Markdown 表格,单元格里的|它也会自动做转义处理。代码块里的<pre><code class="python">也能变成带语言标识的 fenced code block。

不过 Pandoc 同样默认不做正文提取。它更适合你已经有干净 HTML 文件的场景。比如你已经用其他工具把正文从页面里抠出来了,再用 Pandoc 做格式转换,会很舒服。

2.3 智能提取:Trafilatura 和 Readability

如果你遇到的是“从一个完整网页里直接提取正文”,那就得用专门做正文提取的库。最省心的是trafilatura,它是专门为爬虫、文本挖掘设计的,能自动识别网页主体内容,并且支持直接输出 Markdown 格式。

安装命令:

pip install trafilatura

基本用法:

import trafilatura downloaded = trafilatura.fetch_url("https://example.com/article") result = trafilatura.extract( downloaded, output_format="markdown", include_images=True, include_links=True, with_metadata=True ) print(result)

这一个库就能干掉很多工作:它会自动跳过导航和页脚,识别文章标题、发布时间、作者,还能把正文提取成干净的 Markdown。和它类似思路的还有 Mozilla 的readability,不过readability输出的是 HTML,你还需要再接一层转换工具。如果不想装 Python 生态,也可以用 Node 版的@mozilla/readability,提取正文后再用 Pandoc 转 Markdown。

这里有一个经验:抓取网页时,最好把原始 HTML 完整保存下来,然后离线再提取。这样就算网站改版、断网,原始数据还在,你还可以换不同工具重新提。

2.4 我给这些工具的排序与组合建议

如果你问我具体怎么选,我一般按这个思路判断:

场景推荐工具理由
已经有一小段干净的 HTML 片段html2text轻量快速,少依赖,几行代码就能用
有一个完整 HTML 文件,正文结构比较标准Pandoc格式转换最稳,表格代码块都不容易崩
有一个完整网页,需要自动抠正文再去掉导航trafilatura自带正文提取,输出 Markdown,一步到位
技术栈是 Node.js,想在前端或爬虫里处理@mozilla/readability + 转换工具生态熟悉,提取效果也不错
只是偶尔转一个网页,不想写代码Pandoc 在线版 / 浏览器插件快速解决,但要注意检查和去噪

组合方案是我平时最常用的:trafilatura提取正文 → 保存 Markdown;如果碰到提取结果不满意,就退回 Pandoc 单独转 HTML 片段。这样既兼顾了自动化,又保留了人工干预的口子。工具本身没有绝对的好坏,关键看你能不能把每类工具用在它最擅长的位置。

3. 实操过程:从杂乱HTML到干净Markdown正文

3.1 准备运行环境

这节我会把完整流程走一遍。假定你用的是 Windows 或 macOS,装了 Python 3.8 以上版本。需要安装的库不多,我在本地一般只装这两样:

pip install trafilatura html2text

如果你还想用 Pandoc 做格式转换,需要单独到 Pandoc 官网下载安装包,或者用包管理器安装,macOS 上可以brew install pandoc,Windows 上可以用winget install pandoc。装完以后,在终端里输入pandoc --version验证一下,能看到版本号就说明环境没问题。

这里多说一句:很多教程会让你一次性装一堆依赖,实际上没必要。我们把任务拆成“正文提取”和“格式转换”两步,每步只用一个核心工具,出问题也好排查。如果真的遇到某种格式 Pandoc 不认,再补装其他库也不迟。

3.2 第一步:提取正文

我先用一个实际的例子演示。假设我有这样一个网页文件article.html,里面混着导航、侧边栏和正文。用 trafilatura 提取正文并转成 Markdown:

import trafilatura with open("article.html", "r", encoding="utf-8") as f: html_content = f.read() result = trafilatura.extract( html_content, output_format="markdown", include_images=True, include_links=True, with_metadata=True ) with open("article_output.md", "w", encoding="utf-8") as f: f.write(result)

如果resultNone,说明 trafilatura 没识别到正文。这时候别慌,可能是网页结构比较特殊,或者是登录墙页面,也有可能是正文被包在一个它觉得“非主体”的容器里。可以换用trafilatura.extract(html_content, output_format="html", no_fallback=False)多试几个参数,或者退回到 readability 这类“通用网页正文提取算法”。

提取完成后,打开article_output.md的第一眼可能会很惊喜,因为导航和页脚基本都不见了,只剩标题、段落、图片、列表。但别急着下结论,你还需要检查两件事:一是标题和正文之间是否隔了多余的空行,二是如果正文里有代码块,代码缩进是否被破坏了。这两个问题在自动化提取时非常常见。

3.3 第二步:HTML片段转Markdown

如果 trafilatura 提取出来的结果不理想,或者你手里的素材本来就是一个干净的 HTML 片段,那就直接用 Pandoc 转换。假设我把正文片段存成了body.html,想转成 Markdown 文件,命令是这样:

pandoc body.html -o body.md --wrap=none

这里的--wrap=none很关键。默认情况下 Pandoc 会在 72 个字符左右自动折行,这对普通段落影响不大,但如果你的 Markdown 编辑器是 Obsidian、Typora 这类的“所见即所得”工具,就会看到段落中间被硬换行,非常讨厌。设置成--wrap=none,可以避免这个问题,让每一段保持一行。

如果 HTML 片段里有表格,Pandoc 会尽量转成 Markdown 表格,但有个前提:表格结构不能太随意。嵌套表格、跨行跨列(rowspan/colspan)在 Markdown 里本来就不支持,Pandoc 遇到这种情况可能会变成普通的 HTML 代码保留在 Markdown 里,这个不算 bug,是 Markdown 语法本身的限制。

转换完之后,我通常会用文本编辑器打开文件,快速检查一下<div><span>这样的标签是否还残留。如果发现还有,就进入下一步的清洗。

3.4 第三步:清洗残留标签和结构还原

即使经过 Pandoc,Markdown 里偶尔还会出现零星的原生 HTML 标签。常见的有这么几种:

  • 包裹图片或文字的<figure><figcaption>
  • 脚注相关的<sup><sub>
  • 视频、iframe 对应的<iframe>
  • 表格单元格里残留的<span><br>

我处理这些残留标签,一般分两步走。第一步用 BeautifulSoup 做结构化清洗,而不是一上来就写正则。因为 HTML 标签是嵌套结构,正则很难处理好“保留标签内容,只去掉标签本身”这件事。用 BeautifulSoup 可以这样:

from bs4 import BeautifulSoup markdown_with_html = open("body_output.md", encoding="utf-8").read() soup = BeautifulSoup(markdown_with_html, "html.parser") for tag in soup(["span", "div"]): tag.unwrap() # 去掉标签,保留内部内容 for br in soup.find_all("br"): br.replace_with("\n") cleaned = str(soup)

第二步是处理一些常见的格式隐患。比如 Markdown 表格里如果有竖线|,必须转义成\|,否则表格就会多出几列;再比如代码块里的 HTML 标签,如果不放在 fenced code block 里,就会被 Markdown 渲染器误当成真实标签解析掉。我的习惯是:先清洗 HTML,再转 Markdown;如果转出来的 Markdown 里还有 HTML,就再做一轮针对性的字符串替换。

3.5 第四步:处理换行、表格、图片路径、数学公式

这一步是正文整理的精华,也是容易踩坑最多的地方。我单独拆开讲。

换行。

Markdown 的换行规则和 Word 不太一样。你敲一个回车,渲染出来不一定换行;要么在行尾加两个空格,要么在两个段落之间留一个空行。HTML 转完 Markdown 后,经常会出现“所有段落挤在一行”的情况,原因是原始 HTML 里的文本节点没有在合适的位置拆行。解决方法是:转换后统一把连续多个空行压成一个空行,然后检查每个段落是否以空行分隔。Pandoc 加--wrap=none之后,段落之间会保留空行,但如果是html2text,建议设置body_width=0后再人工处理空行。

表格。

HTML 表格转成 Markdown 表格,最大的问题是单元格里的内容可能出现换行、图片、列表。Markdown 表格不支持单元格内换行,所以遇到这种结构,要么把内容简化成纯文本,要么保留为 HTML 表格。如果目标是放到 GitHub、语雀这类渲染环境,我会优先保留 HTML 表格;如果目标是放到 Obsidian、Typora,Markdown 表格通常就够了。另外,如果你需要把 Markdown 表格导出到 Excel,可以先复制到支持 Markdown 的表格工具里,再粘贴到 Excel,比在 Excel 里手工排版靠谱得多。

图片路径。

网页里的图片路径通常是相对路径,比如/images/a.png。如果把这个 URL 原样写进 Markdown,图片可能无法显示。我的做法是在提取正文后,批量把相对路径改成完整 URL,或者把图片下载到本地,再重新生成相对路径。下载图片后还要注意文件名冲突,最好按文章目录命名,比如images/post1_01.png

import re text = markdown_text base_url = "https://example.com" # 把 ![alt](/images/a.png) 替换成完整地址 text = re.sub(r"!\[([^\]]*)\]\((/[^)]*)\)", rf"![\1]({base_url}\2)", text)

数学公式。

如果你的正文里包含数学公式,html2text和 Pandoc 默认都能处理 LaTeX 形式的公式,但要注意转义问题。HTML 里的<span class="math">\( ... \)</span>经过 Pandoc 转换后通常会变成\\( ... \\),有时会多出反斜杠。我一般会在转换后把\\(\\)替换成$,把\\[\\]替换成$$,这样各种 Markdown 渲染器都能正确识别。

3.6 第五步:放到编辑器里做最终检查

转换完成并不等于整理完成。我最后一定会把 Markdown 文件放到编辑器里过一遍,因为不同的 Markdown 方言对语法的解析可能不一样。比如 GitHub 的 GFM(GitHub Flavored Markdown)和 Obsidian、Typora 的解析就有细微区别。

检查的重点有三个:

  1. 标题层级是否连贯。有些网页的标题是从<h3>开始的,转成 Markdown 后可能没有<h1>作为总标题,我一般会手动补一个二级或一级标题,方便以后在笔记系统里生成目录。
  2. 代码块是否有语言标识。很多网页代码高亮是用<pre><code class="language-python">表示的,转出来的 Markdown 如果丢了语言信息,后续阅读就少了高亮提示。缺了的可以补上。
  3. 链接和图片是否有失效。有些站点有防盗链,图片直接放链接会被拦。检查的时候如果光靠肉眼看不出来,建议用一个简单的脚本批量检查 URL 状态码。

这一步做完,整个转换才算真正结束。

4. 常见问题排查与避坑手册

4.1 高频问题速查表

我把自己在实际操作里经常遇到的问题整理成了一张表,碰到类似情况可以直接按表排查。

现象原因解决办法
转换后正文里全是导航链接没有先做正文提取,直接转换整个页面先用 trafilatura/readability 提取主体,再做格式转换
段落挤在一行,没有换行Markdown 需要空行才能分段,转换器没有帮你加上转换后统一处理空行,或使用--wrap=none后再手洗
表格里多出一列或错位单元格内容里有未转义的 `` 或换行
图片全都不显示相对路径没有拼接完整 URL,或图片被防盗链用正则批量补全 base URL,或下载图片到本地并改路径
代码块里出现大段 HTML 标签<code>内部没有被正确封闭把代码放到带语言标识的 fenced code block 中
数学公式变成了一堆反斜杠Pandoc 会把\(转成\\(转换后统一替换为$/$$
大文件转换过程卡死或者内存暴涨页面太大,正文提取算法逐节点分析太慢先截取正文容器再提取,或者用流式处理
HTML 文件浏览器打开没问题,但 Pandoc 不认文件编码不是 UTF-8,或带有 BOM用脚本统一转成 UTF-8 无 BOM 格式

4.2 我在实践中攒下的独家避坑技巧

这部分是我最想说的,因为都是网上文档里看不到的细节。

第一,不要在“整页 HTML”上直接跑格式转换。很多人拿着一个带完整导航、页脚的 HTML 文件就丢给 Pandoc,Pandoc 确实能转,但转换结果会非常“酸爽”。导航里的每个链接都变成一条列表,侧边栏的推荐文章变成一堆无序列表,正文反而被淹没。我见过最夸张的一次,一篇 3000 字的文章,转换出来有 8000 多字,大部分都是噪音。正确顺序永远是“先正文,后转换”。

第二,不要轻易用正则去配对 HTML 标签。HTML 是树形结构,嵌套很深,正则适合做“去掉某个标签名”这种浅层操作,一旦涉及跨标签处理,非常容易误伤。比如你想去掉<div>标签,直接匹配<div></div>,如果 div 里还套了 div,就会把结构切断。用 BeautifulSoup 的unwrap()方法会更安全,它会把标签剥离,但保留标签内部的所有内容。

第三,清洗时注意保留代码内容。正文里如果讲的是编程技术,内容中很可能出现if a < b这样的字符。如果你用正则像<[^>]*>这样去匹配标签,会把< b也当作标签开头删掉,彻底搞坏代码。所以清洗前一定要先把代码块提取出来,用占位符替代,清洗完再把代码放回去。具体的做法是:先匹配 fenced code block,替换成临时标记如PLACEHOLDER_CODE_BLOCK_1,等标签清理完再恢复。

第四,下载图片时要防止文件名冲突。不同文章里经常出现同名图片,比如image.png。如果都下载到同一个目录,会互相覆盖。我的习惯是每篇文章建一个独立图片目录,比如images/20240601-post-slug/,并且给文件名加一个前缀或序号。这样既避免冲突,后期打包迁移也方便。

第五,正文提取和格式转换最好分开执行。用 trafilatura 提取正文时,它已经输出 Markdown 了,但如果你对提取结果不满意,可能需要调整参数重新提取,这时候再跑一遍 Pandoc 会浪费时间。更好的办法是先用 trafilatura 输出 HTML 中间格式,确认提取范围正确后,再用 Pandoc 转 Markdown。这样两步可以分别调试,效率更高。

4.3 再聊几个绕不开的衍生场景

弄明白了 HTML 转 Markdown 以后,你大概率还会碰到“Markdown 怎么转成 Word/PDF”这种反向需求。我简单提一下几个常见操作,免得你去踩我踩过的坑。

Pandoc 可以把 Markdown 转成 Word 文档:

pandoc input.md -o output.docx

但要注意,Word 对于 Markdown 里的表格支持不稳定,尤其当表格包含合并单元格或复杂布局时,转出来的 Word 表格容易崩。我之前试过在低代码平台上搭工作流,把 Markdown 转成 Word,看起来挺方便,但一遇到带公式的大文档就各种格式错位。所以如果你只是偶尔转几个文档,本地用 Pandoc 完全够;如果你想做自动化流水线,一定得对格式差异有预期。

至于 Markdown 转 PDF,常见做法有两种:一是用 VS Code 装 Markdown PDF 插件,二是用 Pandoc + LaTeX。如果你用 VS Code 的 Markdown PDF 插件,需要提前下载 PrinceXML 之类的渲染引擎,不然插件会提示缺少组件。另一个问题是中文 PDF 的字体会缺,建议在配置里指定系统中文字体,否则导出后中文会变成方块。这个坑我第一次遇到时也懵了很久。

还有人会从 PDF 里转 Markdown,想着“反向操作”。我能直接劝一句:除非 PDF 里的文本是纯文本、没有复杂表格和数学公式,否则自动转换的准确率非常低。像扫描 PDF、数学公式多的 PDF,转出来基本是乱码加错位。更好的方案是先用 OCR 工具识别文本,再做人工校对,最后转成 Markdown。

这些衍生场景本质上都属于“文档格式迁移”这件事,理解了 HTML 转 Markdown 的核心逻辑以后,其他格式互相转也就不难了。核心永远是“先提取、后转换、最后人工检查”。

我自己现在的固定流程是:trafilatura 提取正文 → Pandoc 转换为 Markdown → 编辑器里检查标题、表格、图片路径三处要点。这套流程我已经用了半年多,处理过几百个网页文件,除了一些结构特别诡异的网站需要单独调试,大部分情况都能一次搞定。以我的亲身体会来说,只要你不急着偷懒,把每一步的输入输出都看清楚,HTML 转 Markdown 其实没有想象中那么折磨人。最后再分享一个小习惯:写转换脚本时,尽量保存一份原始 HTML 的备份,别直接覆盖原文件。有了原始数据,就算工具抽风、脚本写错,你也能随时重来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询