把 PDF、Word、Excel、图片交给 AI 之前,先统一成 Markdown,这个思路我是在做一批文档问答项目时彻底想通的。当时团队接到的需求是把几百份混合格式的资料喂给大模型做检索问答,一开始图省事,直接调各种解析库把文本抠出来塞给模型。结果效果一言难尽:表格数据答错、章节层级混乱、检索经常命中无关段落。后来把所有文档统一转成 Markdown,再走切分和向量化,整个链路才真正稳下来。
这篇文章就是围绕这条实践路径展开的。我会先解释为什么偏偏是 Markdown 而不是纯文本,再给出不同格式文档的转换选型和具体操作步骤,接着聊编排策略和工程化落地的细节,最后把实际项目里高频踩坑的问题整理成排查清单。内容偏工程实践,适合正在做 RAG、知识库问答、文档解析开发的团队参考。
1. 为什么非要把文档先统一成 Markdown
先问一个根本问题:大模型读取文档时,它到底需要什么?
模型处理文本的核心机制是 token 预测,它不会像人一样“看版面”,而是把文本切碎后计算注意力。这意味着同样的内容,不同的组织方式,模型理解的难度完全不同。纯文本把所有信息拍平,标题、列表、表格全混在一起,模型要靠上下文去猜结构;而 Markdown 用轻量符号保留了标题层级、表格行列、强调关系,这些结构信息正是模型理解文档语义的关键锚点。
我拿一个三栏排版的调研报告举例。原始 PDF 转成纯文本,输出可能是这样的:
项目背景 行业分析 市场数据 截至2023年底,市场规模达到...这段文字没有任何层级信息,模型只能通过语义去猜“市场规模”是不是“行业分析”下面的内容。但如果转成 Markdown:
## 项目背景 ### 行业分析 #### 市场数据 截至 2023 年底,市场规模达到...模型一眼就能识别章节归属,后续检索时也能更精准地命中对应小节。
表格的差异更明显。Excel 的单元格坐标、合并规则、数据类型,在纯文本抽取后几乎全部丢失。但 Markdown 表格用管道符和分隔行保留了行列关系:
| 季度 | 营收 | 同比 | | ---- | ---- | ---- | | Q1 | 100 | +10% | | Q2 | 120 | +20% |模型看到的是结构化表格,它知道第三行第二列是“营收”,自然能正确回答“Q2 营收是多少”这类问题。如果换成纯文本“Q1 营收 100,同比增长 10%,Q2 营收 120,同比增长 20%”,看起来也能读,但一旦表格列数多、行数多,文本形式就开始混乱,模型经常抓错对应关系。
还有一个常被忽略的点是 embedding。RAG 场景下文档要切成 chunk 再向量化,纯文本的切分边界往往落在句子中间,语义不完整;而 Markdown 可以根据标题层级来切分,每个 chunk 都有清晰的语义边界。我们实测过,同一份文档,按 Markdown 标题切分后的检索命中率明显高于固定字符数切分。
结论其实很直接:Markdown 是目前综合成本最低的 AI 友好中间格式。它不是银弹,但相比原始文件,它让模型更好读;相比纯文本,它保留了结构。
2. 转换前先看文档类型:PDF、Word、Excel、图片的差异化处理
不同格式的解析难度天差地别,最忌讳的就是拿一个工具通吃所有类型。PDF 是排版文件,Word 是内容文件,Excel 是数据文件,图片是像素文件,它们的解析逻辑完全不同。选型必须先按文档类型拆开看。
2.1 PDF:文字型、扫描型、复杂版面三分法
PDF 是最麻烦的一类。它不是一种“内容格式”,而是一种“印前格式”,内部可能由 Word 导出、扫描件生成、设计软件输出,结构差异极大。
- 文字型 PDF:文本可以直接提取,用 PyMuPDF 或 pdfplumber 就能拿到文字内容和位置信息。速度快,但复杂表格容易乱序。
- 扫描型 PDF:本质是图片集合,必须走 OCR。中文识别推荐 PaddleOCR 或 RapidOCR,精度高但速度慢,大量处理建议带 GPU。
- 复杂版面 PDF:多栏、图文混排、表格密集,这类需要版面分析能力更强的工具,比如 Marker、MinerU,或者成熟的商业转换服务。它们会用模型识别标题、段落、表格区域,再重组为 Markdown,效果好但部署成本高。
2.2 Word:docx 与 doc 命运不同
docx 本质是一个 zip 包,内部是 XML 文档,用 Pandoc 一条命令就能无缝转成 Markdown,标题、列表、粗体基本保留。
老旧的 doc 是二进制格式,Pandoc 无法直接处理,需要先用 LibreOffice 转成 docx 再转 Markdown。这个过程中样式可能有些变化,比如居中标题被改成左对齐,但正文内容一般没问题。
2.3 Excel:先拆 sheet,再固化公式
Excel 的核心价值在于单元格坐标、合并单元格、公式计算。直接转 Markdown 会丢失坐标和公式语义。实际操作中,我习惯按 sheet 拆分,每个 sheet 转成一张或多张 Markdown 表格,并且注意表头语义清晰。
一个关键坑是公式:pandas 读取 Excel 时拿到的是缓存值,不是公式本身。如果 Excel 里有动态计算的单元格,必须先打开文件让公式计算结果并保存,再转 Markdown,否则 AI 看到的是空值。
2.4 图片:OCR 之外还要考虑版面
图片转 Markdown 的核心是 OCR,但 OCR 出来的一串文字通常没有结构。要得到可用的 Markdown,需要先做版面分析,区分标题、正文、表格区域,再按顺序拼装。轻量级可以用 OCR 返回的坐标信息排序,复杂版面只能用模型工具或人工介入。
图片还有一种更省事的路线:直接用多模态大模型识别图片内容。这种方式不要求把图片转成 Markdown,模型本身就能理解版面结构。是否要走 OCR 转 Markdown,取决于你的下游模型能力和业务需求,不必为了统一而统一。
3. 实操:从混合格式文档到 Markdown 的完整流程
选型讲完,接下来看具体怎么落地。我以一套常用的开源组合为例,演示如何把一批混合格式文档统一成 Markdown。整体流程分四步:环境准备、按格式转换、后处理清洗、输出校验。
3.1 环境准备
建议使用虚拟环境或 Docker,避免依赖冲突。我这里基于 Python 3.10:
pip install pymupdf pdfplumber pandas openpyxl pip install rapidocr-onnxruntimePaddleOCR 需要额外安装,如果只处理少量扫描件,RapidOCR 更轻量,效果也够用。复杂版面 PDF 可以再部署 Marker 或 MinerU,这两个项目排版还原能力强,但需要下载模型权重,首次运行较慢。
3.2 PDF:文字型抽取与扫描型 OCR
文字型 PDF 直接提取文本块,保留页面顺序,代码大致这样:
import fitz def pdf_to_markdown(pdf_path): doc = fitz.open(pdf_path) md_lines = [] for page_num in range(len(doc)): page = doc[page_num] blocks = page.get_text("dict")["blocks"] for block in blocks: if "lines" not in block: continue for line in block["lines"]: text = "".join(span["text"] for span in line["spans"]).strip() if text: md_lines.append(text) md_lines.append(f"\n<!-- page {page_num + 1} -->\n") return "\n".join(md_lines)思路是按页面把文本行取出来,页面之间插入注释。更进一步,可以根据字体大小判断标题层级,把大号字转成 Markdown 的 # 或 ##,这对后续切分和检索帮助很大。
扫描型 PDF 需要 OCR。先用 PyMuPDF 把每页渲染成图片,再调 RapidOCR 识别:
import fitz from rapidocr_onnxruntime import RapidOCR ocr = RapidOCR() def ocr_pdf_to_markdown(pdf_path): doc = fitz.open(pdf_path) md_lines = [] for page_num in range(len(doc)): page = doc[page_num] pix = page.get_pixmap(dpi=200) img_path = f"page_{page_num}.png" pix.save(img_path) result, _ = ocr(img_path) if result: for box, text, score in result: md_lines.append(text) md_lines.append("\n") return "\n".join(md_lines)OCR 速度是硬伤,一页扫描件大概要两三秒。大规模处理建议做成批任务并行,或者上 GPU。另外 OCR 偶尔会有错字,清洗阶段可以用规则替换常见错别字。
3.3 Word:Pandoc 的极简路线
docx 转 Markdown 是几类文档里最简单的,一条命令:
pandoc input.docx -t gfm -o output.md-t gfm指定 GitHub 风格 Markdown,适合直接喂给 AI。Pandoc 会保留标题、列表、粗体、斜体,但会丢弃页眉页脚。对 AI 来说这是好事,页眉页脚往往是重复噪音。
旧版 .doc 文件先转成 docx:
libreoffice --headless --convert-to docx input.doc pandoc input.docx -t gfm -o output.md这里有个小坑:LibreOffice 转换时可能丢失某些格式,比如标题样式变成普通文本。如果文档是公司老系统导出的,建议转完后用脚本检查标题数量是否明显偏少。
3.4 Excel:pandas 转 Markdown 表格
Excel 转换我会写成脚本,基础版本如下:
import pandas as pd def excel_to_markdown(xlsx_path, sheet_name=None, header_row=0): df = pd.read_excel(xlsx_path, sheet_name=sheet_name, header=header_row) if isinstance(df, dict): for name, sub_df in df.items(): print(f"## {name}") print(sub_df.to_markdown(index=False)) else: print(df.to_markdown(index=False))header_row参数很关键。很多业务报表第一行是报表标题,第二行才是表头,需要根据实际情况传入 1 或 2。如果省略,pandas 会把标题误当列名,整个表格语义就错了。
合并单元格也要处理。pandas 读取时,合并区域只有左上角有值,其他位置是 NaN。需要做 forward fill:
df = df.fillna(method="ffill") # 或者用 df = df.ffill()这个操作能把合并单元格的值填充到整列,避免表格里出现大片空单元格。
3.5 图片:OCR 加简易版面排序
图片转 Markdown 的轻量做法是:调用 OCR 拿到识别框坐标,按纵向位置排序后输出。只适合文字结构简单的图片,比如截图、通知、公告。
from rapidocr_onnxruntime import RapidOCR ocr = RapidOCR() def image_to_markdown(img_path): result, _ = ocr(img_path) if not result: return "" # 按 y 坐标排序,同一行内按 x 坐标排序 lines = sorted(result, key=lambda item: (item[0][0][1], item[0][0][0])) md_lines = [] for box, text, score in lines: md_lines.append(text) return "\n".join(md_lines)如果图片是复杂的多栏排版,这个简单排序会乱序。这时候要么做竖线检测后按列切割,要么直接用多模态模型,不要硬磕 OCR。
3.6 转换后的统一清洗与质量校验
不同工具产出的 Markdown 质量参差不齐,需要一个统一的清洗层。我常用的规则有三类:
- 清理多余空行和占位符:把连续三个以上换行压缩成两个,去掉独立的分页注释。
- 修正标题层级:把页眉页脚里误判为标题的文本降级,把正文里加粗但不该作为标题的内容移除。
- 修复表格格式:pandas 输出的表格有时缺少分隔行,需要补
| --- | --- |。
校验我习惯分两层。第一层是脚本统计:标题总数、表格总数、字符数、图片引用数,指标异常就说明某个文件转换出了问题。第二层是人工抽检:每批随机抽 5 份,看标题层级、表格对齐、OCR 乱码。
注意:清洗阶段只做格式层面的事,不要动内容和数据。金额、日期、编号这些出现转换异常时,宁可保留原始形态,也不能在清洗时误删。语义正确性留给人工校验。
4. 给 AI 喂 Markdown 的编排策略
转换完 Markdown 只是基础,怎么把这份 Markdown 交给 AI 才是决定理解质量的关键环节。这一节分享几个我实践验证过的编排要点。
4.1 控制单文档大小与切分粒度
一份 100 页的 PDF 转成 Markdown 后可能有两三万行。一旦全塞进提示词,token 会爆,模型注意力也会被无关内容稀释。所以按逻辑章节切分,而不是固定字符数切分。
最常用的做法是:用 Markdown 标题层级作为切分边界,二级标题作为一个 chunk,三级标题作为子块。切分后只把相关章节传给模型,既能节省 token,又能提升准确率。
切分时特别注意:不要把一个表格切到两个 chunk 里。表格是结构化数据,切分必须保持完整。如果表格特别大,单独把表格作为一个文档块,用“表1:营收数据”命名,方便检索时直接命中。
4.2 在 Markdown 中保留元信息与引用锚点
AI 回答问题时,如果能直接给出“这个数据来自报告第 3 章”,可信度会高很多。实现方式是在 Markdown 里插入注释:
## 第三章 市场分析 <!-- source: report_2024.pdf, page 12 --> | 年份 | 营收 | |------|------| | 2023 | 100 万 |切分成 chunk 时,元信息注释会跟着内容走。回答生成时,提示词可以要求模型先检索注释,再引用原文。这个设计对 RAG 应用非常有用,问题答案的可追溯性就是从这一步来的。
4.3 提示词里明确要求“阅读 Markdown 结构”
模型并不知道你喂的是 Markdown 还是纯文本,除非你在系统提示词里告诉它。我常用的表述是:
你收到的是 Markdown 格式文档,请优先利用标题层级和表格结构理解内容。回答问题时引用对应的章节和表格编号。
这一句话就能显著减少模型忽略表格、只抓正文的情况。实测下来,同样内容,加了结构阅读说明后,表格相关问答的准确率能提升近两成,成本几乎为零。
4.4 表格与图片的兜底策略
转换质量差的表格和图片,不要硬塞给 AI。表格结构乱序时,直接把表格截图或原图喂给多模态模型,效果反而更好。扫描件 OCR 结果差时,把原图作为补充信息一起交给模型,让模型结合图片内容和 OCR 文本综合判断。
这背后的逻辑是:统一成 Markdown 是为了提升效率,而不是为了统一而统一。当 Markdown 质量无法保证时,原始图片本身就是高质量的信息载体,多模态模型可以直接理解,没必要强行转文本。
5. 批量场景下的工程化落地
单文件转换是基础,实际项目中往往要处理几百上千份混合格式文档。这一节聊聊工程链路的串联、性能优化和质量保障。
5.1 批量文档处理流水线
我在项目中把整套流程串成一条流水线:
接收文件 -> 类型检测 -> 格式转换 -> Markdown 清洗 -> 切分索引 -> 存入向量库类型检测用扩展名最简单,但更可靠的是读文件头。docx 和 xlsx 都是 zip 格式,PDF 以%PDF开头,图片按扩展名区分。检测完之后进入各自的转换分支。
批处理建议用任务队列,每个文件转换任务丢进去,多个 worker 并行执行。OCR 阶段是性能瓶颈,文件多时单独扩容 OCR worker,或者用 GPU。文字型 PDF 转换占用资源低,CPU 跑就行。
5.2 缓存与增量更新
文档转换很耗资源,同一个文件重复转换很浪费。我会上传文件时计算 MD5 做哈希缓存,如果内容没变,直接读缓存 Markdown。
增量更新也很重要。业务文档经常变更,全量重建索引成本高。我习惯记录每个文件的转换时间和版本号,变更时只重新处理增量文件并更新对应 chunk。知识库规模大了之后,这个策略能省不少成本。
5.3 转换质量监控
链路搭完,不代表它永远正确。我主要盯三个指标:转换成功率、Markdown 平均字符数、表格数量。只要这些指标出现明显波动,大概率是某个转换工具出了问题。
质量评估方面,可以设计一组标准问答,用 AI 自己来评估回答准确率。这个方法成本低,能直观看出转换质量对最终效果的影响。人工抽检作为补充,确认脚本指标看不出问题的部分。
6. 文档转换中的常见问题与排查技巧
最后整理一份实际项目中反复出现的问题清单,算是避坑指南。
6.1 PDF 表格乱序、错位
这是普遍问题。PDF 里的表格没有“行”的概念,只有文本块和坐标。提取时如果列间距过宽,会把同一行的两列拆成两段文本,顺序就乱了。
排查方法:对比原始 PDF 和 Markdown 表格,确定乱序范围。如果是简单表格,用 pdfplumber 的extract_table方法重新提取,再手动转 Markdown。复杂表格直接上 Marker 或 MinerU,效果会好很多。
6.2 Word 转出后标题丢失
Pandoc 对某些自定义样式名识别不全,比如非标准 Heading 样式会变成普通文本。排查时先看原始 docx 的样式名,用 Python 读取styles.xml确认。
解决办法两个:一是转换前用脚本把自定义样式改成标准标题;二是转换后根据文本特征(居中、加粗、字号)补标题。最省事的还是源头规范文档样式,但这往往需要业务部门配合。
6.3 Excel 多级表头如何转成 AI 友好的表格
财务或调研类报表通常有两级表头,pandas 默认只取第一行做列名,上面的分组层级会丢失。处理方式是读两次:第一次用header=0读取第一级,第二次用header=1读取第二级,再把两层表头合并。
另一种更直接的方法:把多级表头拍平为一级。比如“2023 年”“Q1”合并成“2023 年 Q1”。这样 Markdown 表格虽然列名变长,但语义完整,AI 理解起来反而更容易。
6.4 OCR 误识别、乱码
扫描件 OCR 总是有概率出错的。除了常见错别字,双栏版面识别顺序混乱也很典型。处理思路是先做竖线检测,把图片按列切割,分别 OCR,再按顺序拼装。
如果 OCR 质量差到改不动,建议别浪费时间。把扫描件原图作为补充信息一起交给多模态模型,让模型结合 OCR 文本和图片内容判断。根据我的经验,这个方案比反复调 OCR 参数性价比高得多。
6.5 向量化失败或检索效果差
有时候 Markdown 里包含大量 HTML 标签或特殊符号,向量化模型不认这些 token,导致 chunk 索引质量差。我的做法是在清洗阶段保留一份纯文本版本,专供向量化;带格式的 Markdown 版本用于大模型阅读。
这里要区分清楚:向量化用纯文本,生成答案用 Markdown。纯文本保证检索时语义相似的内容能聚合,Markdown 保证模型生成时结构清晰可引用。两者并存,不冲突。
6.6 转换结果与原文不一致
偶尔会出现转换后的 Markdown 内容与原文对不上,比如数字少了 0、句子被截断。这种情况大多是转换工具在处理特殊字符时出错。排查时用 diff 工具对比原始文本和 Markdown 文本,定位到具体差异。
预防办法是检查原始文档里有没有公式、脚注、尾注、文本框。这些内容在转换链路上经常被遗漏。公式复杂时建议直接截图嵌入 Markdown,不要强转纯文本;脚注和尾注可以转化后统一放在文档末尾。
我最后想说的
从纯文本到 Markdown,AI 对文档的理解能力上了一个台阶,前提是我们先给出它真正“读得懂”的文档。转换工具终究只是手段,统一成 Markdown 的真正价值,在于让 AI 把结构当结构看,把表格当表格看,而不是让它在杂乱文本里盲猜。
我还有个体会是格式统一这件事越早做越好。很多团队都是在 RAG 检索效果差、AI 答非所问之后才回头补文档转换,那时候数据已经堆了一堆,再清洗成本就高了。如果一开始就规划好 Markdown 作为中间格式,后面无论是做问答、摘要还是知识库检索,都会顺畅很多。
最后分享一个小习惯:每次转换完一批文档,我会随机抽几份人工检查 Markdown 质量,花不了多少时间,但能避免整批索引被低质量转换污染。文档转换不上心,AI 上层做得再好也白搭。