工具大全
文档处理作者:Coocon2026年3月18日920 次阅读约 14 分钟阅读

文档格式转换实战指南:Markdown、HTML、PDF 互转全解析

每个文档都有它最适合的格式。Markdown 适合写作,HTML 适合网页展示,PDF 适合打印分发,Word 适合办公协作。麻烦在于,现实工作中你经常需要在这些格式之间穿梭——把博客文章转成 PDF 发给客户,把 Word 文档迁移到知识库,把网页内容提取成 Markdown 归档。

格式转换看似简单,真正动手时却问题频出:格式乱了、表格消失了、中文变乱码了。这篇指南梳理各种转换路径的最佳方案,帮你少走弯路。

四大格式特点对比

在选择转换路径之前,先理解各格式的本质特点:

格式 可读性 可编辑性 渲染效果 文件大小 适用场景
Markdown (.md) 极高(纯文本) 极高 依赖渲染器 极小 技术文档、博客写作、README
HTML (.html) 中(含标签) 浏览器渲染 小~中 网页、邮件模板、文档展示
PDF (.pdf) 高(视觉) 极低 固定排版 中~大 打印、正式文件、跨平台分发
Word (.docx) Office 渲染 办公协作、需要批注修订

核心规律:可编辑性越高的格式,排版固定性越低;排版越固定的格式,转换损耗越大。 PDF 是单向格式,从 PDF 往外转时质量损耗最大。

Markdown → PDF

这是最常见的转换需求,有三种方案,效果各有差异:

方案一:浏览器打印(推荐入门用)

  1. 在支持 Markdown 预览的工具中打开文档(VS Code 预览、MagicTools、Typora)
  2. Ctrl+P(Mac:Cmd+P)打开打印对话框
  3. 目标打印机选择「另存为 PDF」
  4. 调整页面设置:去掉页眉页脚勾选,设置合适的边距

优点:操作简单,零学习成本,所见即所得。 缺点:代码高亮、字体、分页位置依赖浏览器渲染,不同机器可能有细微差异。

方案二:Pandoc 命令行(推荐专业用)

Pandoc 是格式转换领域的瑞士军刀,支持 40+ 种格式互转。

# 安装(Mac)
brew install pandoc
# 同时需要安装 LaTeX(用于 PDF 输出)
brew install --cask mactex-no-gui

# 基本转换
pandoc input.md -o output.pdf

# 带中文支持(必须指定字体,否则中文显示为方块)
pandoc input.md -o output.pdf \
  --pdf-engine=xelatex \
  -V mainfont="PingFang SC" \
  -V geometry:margin=2cm

# 自定义 CSS 样式(通过 HTML 中间步骤)
pandoc input.md -o output.pdf \
  --pdf-engine=wkhtmltopdf \
  --css=style.css

优点:输出质量最高,格式控制精细,支持批量处理,适合生产环境。 缺点:需要安装 LaTeX 环境(约 4GB),中文配置有一定学习成本。

方案三:在线工具(临时需求)

MagicTools 内置导出功能,写好 Markdown 后一键导出 PDF,无需安装任何软件。适合偶发性需求。

Markdown → HTML

Markdown 转 HTML 是最"无损"的转换,因为 Markdown 本来就是 HTML 的简化写法。

静态博客生成

主流方案:Hugo、Jekyll、Gatsby、Astro。以 Hugo 为例:

# 一条命令把整个 articles/ 目录转成网站
hugo --source . --destination ./public

# 生成的 public/ 包含完整的 HTML 站点,可直接部署

这类工具会自动处理代码高亮、目录生成、相关文章推荐等功能。

单文件转换

# Pandoc 转换,嵌入完整样式(standalone 模式)
pandoc input.md -o output.html --standalone

# 引用外部样式文件
pandoc input.md -o output.html --standalone --css=github-markdown.css

转换结果可以直接在浏览器打开,或嵌入到现有网页中。

邮件模板

把 Markdown 内容转成 HTML 后,注意邮件客户端对 CSS 支持极差,需要把所有样式内联化:

# 使用 juice 工具内联 CSS
npm install -g juice
pandoc input.md -o temp.html --standalone --css=email.css
juice temp.html output-email.html

HTML → Markdown

这条路径最常用于:博客平台迁移(WordPress → Hexo/Hugo)、从网页提取内容归档、清洗爬虫抓取的内容。

在线工具(推荐)

MagicTools HTML 转 Markdown 支持三种输入方式:

  • 直接粘贴 HTML 代码
  • 输入 URL 自动抓取网页
  • 粘贴富文本(从网页复制后粘贴)

背后使用 Turndown 引擎,能正确处理表格、代码块、列表嵌套等复杂结构。

命令行批量转换

迁移整个 WordPress 博客时,命令行方案更高效:

# 安装 html2text(Python)
pip install html2text

# 单文件转换
html2text article.html > article.md

# 批量转换整个目录
for f in html-pages/*.html; do
  html2text "$f" > "markdown-output/${f%.html}.md"
done

转换质量注意事项

  • 导航栏、侧边栏、广告等无关内容需要手动清理
  • 图片 URL 会保留原始地址,需要另行下载并替换为本地路径
  • 复杂的嵌套表格可能丢失格式

PDF → Markdown

这是所有转换路径中质量最差的一条,原因是 PDF 本质上是"打印指令集",文字的逻辑顺序(段落、标题层级)并不直接存储在文件里。

OCR 方案(适合扫描版 PDF)

扫描版 PDF(纸质文档扫描而来)必须用 OCR:

  • Adobe Acrobat Pro:识别精度最高,支持中文,价格较贵
  • Microsoft Office Lens:免费移动端 APP,扫描 + OCR,输出 Word 后再转 Markdown
  • 在线 OCR:ilovepdf.com、smallpdf.com 提供免费的 OCR 转换

文字层 PDF 转换

如果 PDF 包含可选中的文字(不是扫描版),可以用:

# pdftotext(poppler 工具包)
pdftotext -layout input.pdf output.txt
# 注意:只能提取纯文本,标题层级和格式信息会丢失

# pdf2md 工具(更好保留结构)
npm install -g pdf2md-cli
pdf2md input.pdf > output.md

现实预期:PDF → Markdown 几乎必然需要手动修正,只适合"有比没有好"的场景。重要文档建议保留源文件(Word、Markdown 原稿)。

Word/DOCX → Markdown

企业环境中大量文档存在于 Word 格式,迁移到 Markdown 知识库时这条路径很常用。

Pandoc(推荐,质量最高)

# 基本转换
pandoc input.docx -o output.md

# 提取 Word 中的图片到 media/ 目录
pandoc input.docx -o output.md --extract-media=./media

# 批量转换
for f in *.docx; do
  pandoc "$f" -o "${f%.docx}.md" --extract-media="./media/${f%.docx}"
done

Pandoc 能正确识别 Word 的标题样式(Heading 1 → #,Heading 2 → ##),保留粗体、斜体、表格和图片。

Word 另存为(简单但质量差)

在 Word 中 File → Save As → Plain Text,只能保留纯文本,丢失所有格式。不推荐作为迁移方案。

各转换路径质量评级

转换路径 格式保真度 操作难度 推荐工具
Markdown → HTML ★★★★★ Pandoc / 在线工具
Markdown → PDF ★★★★☆ Pandoc + XeLaTeX
HTML → Markdown ★★★★☆ MagicTools / Turndown
Word → Markdown ★★★★☆ Pandoc
Markdown → Word ★★★☆☆ Pandoc
PDF → Markdown ★★☆☆☆ OCR + 手动修正
PDF → Word ★★★☆☆ Adobe / 在线工具

本机实测:8 个转换方向各跑一次真实文件

问题背景

上面的评级表是经验打分,不是测出来的。本站自己就有一套基于 Gotenberg(LibreOffice + Chromium)的文件格式转换工具,8 个方向上线以来,我没有拿一份「带中文、带表格、带图片」的真实文件从头到尾走一遍,也没有量过一次转换到底要几秒、表格和图片能不能活着到 PDF。这一节把这件事补上。

问题分析

要把「能转」和「转得对」分开量:

  1. 耗时只有一个可信口径:从本机发出请求到拿到完整 PDF 的端到端时间(curl 的 time_total),它包含上传、排队、引擎渲染和下载。Gotenberg 内部的纯渲染时间对外没有暴露,我不拆、也不估。
  2. 文本保真用 poppler 的 pdftotext 把 PDF 里的文字抽出来,去掉全部空白后和源文逐字比对(Python difflib 字符级相似度),再单独数 6 个关键标记(ZX7Q-4K磁悬浮3.1415926 这类不会撞车的 token)和 24 个表格单元格各召回了几个。中文乱码、丢字、表格整块消失都会直接掉分。
  3. 结构与视觉pdfinfo 数页数,用 pdftoppm 渲染出 PNG,数非白像素比例判断是不是白页,再数示意图专用的两种颜色(蓝柱、绿圆)的像素数判断图片有没有进 PDF。
  4. 浏览器端方向(pdf-to-jpg / pdf-to-png)还要额外证明一件事:文件确实没离开浏览器。办法是拦下页面发出的所有非 GET 请求,为零才算数。

技术方案与选型

  • 直接打生产端点,不在本机起转换栈。 本机没有 Docker,Homebrew 也没有 Gotenberg 的 formula,本地起不了 LibreOffice + Chromium 那套容器;而 npm run dev 连的是生产数据库,每次转换都会写一条计数记录进去。生产端点本来就是用户真实走的路径,测它才对题。代价是数字里带网络,后面会明说。
  • 源文件用 uv 装 python-docx / python-pptx / openpyxl / Pillow 现造,不用网上下载的样例。三份 Office 文件内容一致:四段中文正文、一行关键标记、一张 6 行 4 列表格、一张 Pillow 画的 640×320 示意图;HTML 和 Markdown 手写,同样的段落和表格,Markdown 多一个代码块。排除 Word 或 PowerPoint 另存:本机没装,而且手工做的文件不可复现。
  • 服务端 5 个方向用 curl 打 POST /api/convert?direction=<id>,字段名 file,带浏览器 UA(裸 curl 的 UA 会被拦成 403),-L 跟随重定向。匿名限额每 IP 每天 10 次,成功和失败都计数,响应头 X-Quota-Used 会告诉你用了几次。预算:5 个方向各 1 次,最慢的 markdown-to-pdf 再补 2 次、word-to-pdf 补 1 次看抖动,留 1 次容错,一次也没浪费在试请求格式上。
  • pdf-to-jpg / pdf-to-png 用 Playwright 打开线上工具页真实点一遍setInputFiles 喂文件、点「开始转换」、等结果图出现,再从页面里把 blob 图片取回本机。输入就是本次 markdown-to-pdf 吐出来的那份 PDF,不另找。
  • merge-pdf 走 /api/pdf/process(pdf-lib),匿名可用,不占转换配额,把 docx、pptx、xlsx 三份产出合成一份。

实测过程

先造文件:

uv venv tmp/2026-09-12-doc-convert/.venv
uv pip install python-docx python-pptx openpyxl pillow
.venv/bin/python make-sources.py
# figure.png 2024 B · sample.docx 39149 B · sample.pptx 31732 B
# sample.xlsx 8321 B · sample.html 1618 B · sample.md 1152 B

服务端方向的请求长这样(每个方向换 direction 和文件):

curl -sL -A 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ... Chrome/150.0.0.0 Safari/537.36' \
  -D out/markdown-to-pdf-1.hdr -F 'file=@src/sample.md' -o out/markdown-to-pdf-1.pdf \
  -w '{"http":%{http_code},"time_total":%{time_total},"time_starttransfer":%{time_starttransfer}}' \
  'https://tools.cooconsbit.com/api/convert?direction=markdown-to-pdf'

6 个服务端方向加 3 次重复样本一次跑完,全部 HTTP 200,配额从 2/10 用到 9/10:

curl 打生产端点:6 方向 + 3 次重复样本的真实输出

浏览器端两个方向由 Playwright 驱动,页面里「100% 本地转换」那行绿色提示不是摆设,脚本拦截到的非 GET 请求是 0:

Playwright 驱动线上 pdf-to-jpg / pdf-to-png:耗时、输出尺寸、上传请求数

这是 Playwright 点完「开始转换」后的真实页面:

线上 PDF 转 JPG 工具页转换完成后的截图

实践效果

耗时(本机 → 生产端点端到端,含网络;DNS 把请求解析到了阿里云北京那台机器):

方向 引擎 样本数 耗时 产出
markdown-to-pdf Chromium 3 10.97 / 11.84 / 14.56 s(min / 中位 / max) 1 页,261200 B
html-to-pdf Chromium 1 8.02 s 1 页,195736 B
word-to-pdf LibreOffice 2 2.93 / 8.01 s 1 页,67686 B
excel-to-pdf LibreOffice 1 3.67 s 2 页,59157 B
ppt-to-pdf LibreOffice 1 1.22 s 2 页,62871 B
merge-pdf pdf-lib 1 0.30 s 5 页,164713 B
pdf-to-jpg 浏览器 pdfjs 1 点击到出图 2.56 s(页面加载另计 1.60 s) 1224×1584 JPEG,192570 B
pdf-to-png 浏览器 pdfjs 1 点击到出图 2.12 s(页面加载另计 0.95 s) 1224×1584 PNG,255420 B

三点读法:

  • Chromium 引擎比 LibreOffice 慢一个量级。同样是一页纸,markdown-to-pdf 三次都在 11 秒以上,html-to-pdf 8 秒;而 LibreOffice 三个 Office 方向最慢 8 秒、最快 1.2 秒。time_starttransfertime_total 只差几十毫秒,说明时间几乎全花在服务端等第一个字节,不是下载。
  • LibreOffice 有明显的冷启动:word-to-pdf 第一次 8.01 秒,几分钟后第二次 2.93 秒;紧跟在 docx、xlsx 之后跑的 ppt-to-pdf 只要 1.22 秒。Chromium 侧没看到这种冷热差,三次 markdown 都在 11 到 15 秒之间。
  • 浏览器端的 2 秒多里大头是首次加载 pdfjs 库。文件确实没有离开浏览器,两次转换各拦截到 0 次上传请求。

保真度矩阵pdftotext 文本相似度取 -layout-raw 两种抽取的较高值;像素统计取示意图所在页):

fidelity.py 输出:页数、文本相似度、token 与单元格召回、非白像素、图片色块

方向 页数 文本相似度 关键标记 表格单元格 图片
word-to-pdf 1 100.00% 6/6 24/24 在(蓝 17375 / 绿 5671 像素)
html-to-pdf 1 100.00% 6/6 24/24 源文件无图
ppt-to-pdf 2 99.65% 6/6 24/24 在(第 2 页,蓝 10514 / 绿 3407)
markdown-to-pdf 1 96.04% 6/6 24/24 源文件无图
excel-to-pdf 2 85.93% 6/6 24/24 在(蓝 9402 / 绿 3069)
merge-pdf 5 = 1 + 2 + 2 三份标题 3/3
pdf-to-jpg 1 张 与 poppler 渲染同页均值绝对差 4.77 / 255
pdf-to-png 1 张 4.72 / 255

Word 那条是满分:标题、四段、标记行、6×4 表格带边框、示意图、结尾一句,全部在,位置也对:

word-to-pdf 产出用 pdftoppm 渲染的第一页

没拿满分的三条各有具体原因,不是「转换质量一般」四个字能概括的:

  • excel-to-pdf 85.93%:表被横向劈成两页。 我把 A 列设成 60 个字符宽、后面 4 列各 16,超过了 A4 竖版可打印宽度,LibreOffice 按默认打印设置把「样本大小 / 备注 / 数值」三列连同合计推到了第 2 页,A5 那句长文本也在页边被裁掉半句。24 个单元格和 6 个标记一个没丢,数字列的 SUM 公式算出了 1665,只是读者要翻页拼。相似度掉分掉在这里。
  • ppt-to-pdf 99.65%:内容全在,但第 2 页版式重叠。 标题「表格与图片 ZX7Q-4K / 磁悬浮 / 2026-09-12」字号太大折成两行压到了表格上;表格单元格里「word-to-pdf」折行后行高撑大,整张表向下长,和我放在 4.5 英寸处的文本框叠在一起。这是源文件排得太密,LibreOffice 照实渲染;本机没有 PowerPoint,无法对照它自己会怎么排。
  • markdown-to-pdf 96.04%:代码块右侧被裁。 那行 curl ... -o out.pdf 命令超过了 800px 的正文宽度,包装模板给 pre 的是 overflow-x: auto,屏幕上能横向滚,打印成 PDF 没有滚动条,pdftotext 抽出来的那行到 /api/c 就断了。正文四段、表格、标记全部无损。

字体是另一个只有实测才看得见的事。用 pdffonts 看三份 PDF 内嵌了什么:

方向 内嵌字体
markdown-to-pdf NotoSansCJKjp-Regular
html-to-pdf NotoSansCJKsc-Bold / Regular
word-to-pdf NotoSansCJKhk-Regular + Carlito(Calibri 替身)+ NotoSerif

Markdown 的包装模板写的是 font-family: -apple-system, "Noto Sans SC", ...,Gotenberg 镜像里没有叫这个名字的字体,fontconfig 回退到了 Noto Sans CJK 的日文变体;我手写的 HTML 声明了 lang="zh""Noto Sans CJK SC",拿到的就是简体变体。这份样本里我肉眼没找出写法不对的字,但日文变体对一部分汉字的默认字形和简体不同,换一批字就可能露出来。DOCX 那条则是 LibreOffice 自己的回退:Calibri 换成度量兼容的 Carlito,中文落到了港版变体。

踩坑

  • macOS 自带的 bash 是 3.2,declare -A 关联数组不存在。 我用 [word-to-pdf]=src/sample.docx 建方向到文件的映射,报的错是 word: unbound variable:bash 3.2 把方括号里的 word-to-pdf 当算术表达式求值了。换成 case 函数即可,或者显式用 /opt/homebrew/bin/bash。好在报错发生在 curl 之前,没烧配额。
  • pdftotext -layout 会把多列表格交错着输出,单元格召回会误报丢字。 PPT 那页表格里「word-to-pdf」折成了两行,-layout 模式下这两半中间夹着同一行其他列的内容,去空白后拼不回原词,第一版脚本报了 19/24。改成 -layout-raw 两种抽取取并集后 24/24。判「丢没丢」要用内容流顺序,判「版式对不对」再用 -layout
  • 「疑似丢图」是我自己数错了页。 第一版脚本只渲染第 1 页数颜色像素,PPT 的图在第 2 页,于是报了丢图。像素统计必须落在图所在的那一页,否则测的是白纸。
  • Excel 转 PDF 前先设「调整为一页宽」。 列宽加起来超过纸宽,LibreOffice 和 Excel 都会横向分页,文字被页边裁掉是设计如此,不是转换坏了。
  • Markdown 里超长的单行代码在 PDF 里会被裁掉,不会折行。 这是本站包装模板 pre { overflow-x: auto } 在打印介质下的必然结果,正确做法是给 prewhite-space: pre-wrap。这一条和上面的字体名回退都是本次实测挖出来的自家问题,当天已改:模板换成 <html lang="zh-CN">、字体名改为镜像里真实存在的 "Noto Sans CJK SC"prewhite-space: pre-wrap; word-break: break-all。验证方式是把改后的模板套在同一份 Markdown 渲染出的 HTML 上,走生产的 html-to-pdf(同一个 Chromium)再跑一次:pdffonts 里只剩 NotoSansCJKsc,那行 curl 命令折成两行、-o out.pdf 完整出现在 pdftotext 结果里。
  • 裸 curl 打接口会被拦成 403,必须带浏览器 UA。响应头没有 Cloudflare 的痕迹(server: nginx/1.26.2),拦截发生在站点自己这一层。

原始输出、源文件、四份脚本(make-sources.py / run-server.sh / run-pdfjs.mjs / fidelity.py)和 results.jsonl 都留在仓库的 tmp/2026-09-12-doc-convert/,表里每个数字都能回到那里对上。

FAQ

Q:转换后格式乱了怎么办?

A:格式乱的原因通常有三种:第一,源文件格式不规范(如 Word 中用空行代替段落样式),解决方法是规范源文件再转;第二,转换工具不支持某些特性(如特殊字体、复杂表格),换一个工具试试;第三,编码问题(中文乱码),Pandoc 命令行加 --from=utf-8 或确保文件以 UTF-8 保存。如果是 PDF → 其他格式,格式乱基本是正常现象,需要手动修正。

Q:表格在转换中容易丢失怎么处理?

A:表格是格式转换最容易出问题的元素。处理建议:HTML → Markdown 转换时,优先使用 MagicTools 或 Turndown,它对表格支持最好;PDF 中的表格丢失基本无法自动恢复,只能手动重新写 Markdown 表格;Word 表格用 Pandoc 转换质量较好,但合并单元格会丢失合并信息;万不得已时,可以把表格截图,用图片替代——虽然不优雅,但总比格式乱好。

Q:免费工具能处理批量转换吗?

A:完全可以。Pandoc 是免费开源工具,可以写 Shell 脚本批量处理整个目录。Python 的 html2textmarkdownify 库也支持批量调用。对于 100 个以内文件的批量转换,命令行脚本配合 Pandoc 是最高效的方案,完全免费,且可以精确控制输出格式。超过 1000 个文件时可以考虑并行处理(xargs -P 4 或 Python 的 multiprocessing)以提升速度。

总结

文档格式转换没有银弹,但有规律可循:

  • 高频需求:Markdown ↔ HTML、Markdown → PDF,这两条路径工具成熟,质量可靠
  • 迁移需求:Word → Markdown、HTML → Markdown,Pandoc + 在线工具能处理绝大多数情况
  • 最难转:PDF → 任何格式,做好手动修正的心理准备

最重要的建议:保留原始格式文件。转换是临时操作,原稿才是资产。用 Markdown 写作,用 Git 版本管理,随时可以转换成任何你需要的格式,这才是最有弹性的文档工作流。