
说实话今年做 RAG 项目最让我头疼的不是向量模型选型不是 rerank 调参而是最不起眼的文档预处理。尤其是 Windows 本地环境下的 PDF 解析合同是扫描件、报告是双栏、论文全是公式用 pypdf 提出来的文本连分段都是乱的。直到我把 MinerU 4.0 部署到本地的 Windows 工作站离线把 PDF 批量解析成结构化 MarkdownRAG 的文档预处理才算真正打通。这篇文章记录了从环境准备、命令行跑通、Python 批处理到接入 RAG 的完整实战过程踩过的坑和最终方案都会一步步展开。1. 为什么偏偏是 MinerURAG 文档预处理的痛点与选型逻辑1.1 RAG 质量的上限在文档解析这一步就被钉死了RAG 社区里流传一句话Garbage in, garbage out。以前我不太当回事直到自己亲手把一个烂解析结果送进切分器才彻底明白。检索链路大致是文档解析 → 清洗 → 切分 → embedding → 召回 → 重排 → 生成。大多数团队的优化重心放在 embedding 模型和 reranker 上但没有人想过如果第一步文档解析就把结构丢了后面所有环节都是在垃圾里做文章。举个真实例子。我处理过一份双栏排版的行业白皮书用 pdfplumber 按坐标提取文本得到的顺序大概是左栏上半部、右栏上半部、左栏下半部、右栏下半部甚至伴随着页眉页脚来回穿插。这样的文本切出的 chunk语义完全是跳跃的检索命中率再调也上不去。PDF 本身就是一种排版格式它保存的是每段文字在页面上的坐标而不是阅读顺序。解析工具必须通过版面分析把坐标还原成阅读顺序这就是 MinerU 这类工具的价值。1.2 开源方案选了一圈为什么留下 MinerU我当时在 Windows 工作站上横向对比过几类方案简单列一下方案原理扫描件结构还原中文/公式适合场景pypdf / pdfplumber直接抽取文本与坐标不支持弱一般简单纯文本 PDFOCRmyPDFOCR 转文字层支持弱一般扫描件转可检索 PDFMarker深度学习版面还原支持中中英文为主MinerU版面检测 OCR 公式 表格一体化支持强中文友好中文文档/论文/扫描件/复杂表格关键差异在哪里MinerU 不是把文字抠出来而是把整个页面还原成段落、标题、图片、表格、公式这些结构化块最后输出一份 Markdown 和一份 JSON。Markdown 留给人工阅读和直接喂给 RAGJSON 里则保存着每个 block 的坐标、类型、内容——这些是精细切分和清洗的原材料。其实对 RAG 来说表格才是最伤脑筋的东西。纯文本提取会把表格糊成一团而 MinerU 会把表格还原成 HTML 结构这样切分后仍然能保留行、列对应关系。这一条就足够让我选它。1.3 坚持本地离线部署的三点理由第一数据安全。我处理的很多是内部合同、技术文档不可能传到别人的 OCR API 上。本地部署意味着文件从头到尾不出机器模型权重也只是推理时加载在内存和显存里。第二成本可控。批量文档解析按页收费的 API处理几千页 PDF 不是小数目本地跑虽然要电费但机器是现成的。第三可编排。本地命令行和 Python API 支持批处理、断点续跑、失败重试这些能力在 Web 管理后台里不一定给你。离线要澄清一下MinerU 的模型权重第一次使用需要从模型仓库下载一旦下载完成并缓存到本地后续整个解析过程不再依赖网络。真正全内网的机器也完全可玩把模型目录整体拷贝过去即可。2. 环境准备阶段Windows 上这些坑提前踩完2.1 Python 虚拟环境先隔离再动手MinerU 依赖一堆深度学习相关的包直接装到系统 Python 里大概率会把别的环境搞乱。我建议用 conda 建个独立环境Python 版本选 3.10整体兼容性最稳。conda create -n mineru python3.10 -y conda activate mineruWindows 用户很容易忽略一个点项目路径、工作路径里不要带中文和空格。MinerU 底层有一堆 C 扩展和推理框架对 Unicode 路径的支持时好时坏我曾经因为放在C:\Users\张三\Desktop\知识库\下反复报错换到纯英文路径后一切正常。这不是调侃是真实踩出来的。2.2 先想清楚用 CPU 还是 GPU直接决定你的耗时预期如果你机器有 NVIDIA 显卡优先用 GPU。MinerU 的管线里最吃资源的是版面检测模型和公式识别模型测试下来运行设备耗时参考一页普通文字 PDF显存占用参考NVIDIA GPU6GB 以上1~3 秒3~6GBCPU 多核20~60 秒内存为主约 4~8GB注意这是普通文档的参考值扫描版走 OCR 还要再加时间。如果是纯 CPU 机器不建议一次跑太多文档可以配合第 4 章的批处理脚本逐份处理别把系统直接卡死。有 6GB 显存以上的显卡体验会好很多我手头的 RTX 2060 6G 都能跑只是偶尔在大表格页面会紧张。2.3 安装 MinerU 和依赖准备好 C 编译环境用 pip 直接装pip install mineru[full]这里有两个常见问题。第一Windows 上部分依赖需要 C 编译器最典型的是一些视觉模型组件安装时报Microsoft Visual C 14.0 is required先装 Visual Studio 2022 build tools勾选使用 C 的桌面开发组件。第二如果某个包在 PyPI 上没有对应 Windows 的预编译 wheel而系统又去源码编译多半会失败解决办法是更换当前 Python 小版本例如 3.10.11 换到 3.10.0或者找匹配的预编译 wheel 手动安装后再装 MinerU。装完之后确认一下版本mineru --version2.4 模型下载网络就绪后一劳永逸首次运行 MinerU 会自动下载模型国内网络如果不稳定可以先把模型源切到 ModelScopeset MINERU_MODEL_SOURCEModelScope模型默认会缓存到用户目录下的.cache具体路径取决于下载源HuggingFace 默认在%USERPROFILE%\.cache\huggingfaceModelScope 默认在%USERPROFILE%\.cache\modelscope也可以设置MINERU_MODEL_CACHE统一指定缓存位置。下载成功后做一次全量解析验证之后日常使用就是完完全全的离线状态。内网机器想离线部署只需要在一台有网络的机器上下好模型把整个模型目录拷贝进去再设置环境变量指向它即可这个操作我在公司的隔离网段验证过可行。3. 命令行快速跑通第一份 PDF 转 Markdown3.1 最简命令环境准备好后执行cd D:\workspace mineru -p D:\docs\report.pdf -o D:\docs\output命令结束之后输出目录里会多一个以原 PDF 文件名命名的文件夹结构类似output/report/ ├── report.md ├── report.json ├── report_layout.pdf └── images/ ├── 1_0.png ├── 1_1.png └── ...其中report.md就是我最关心的结构化结果。如果这份 PDF 是纯文字版速度很快如果是扫描版MinerU 会自动判断并走 OCR 管线时间会长一些。3.2 常用参数解析不同小版本之间 CLI 参数可能有微调建议先跑mineru --help看一遍。以我当前版本实践到的参数为例参数作用我的用法-p指定输入 PDF 路径-p D:\docs\report.pdf-o指定输出根目录-o D:\docs\output-m解析模式auto/ocr/txt默认 auto 即可--lang指定主要语言中文文档用--lang ch--device指定推理设备有显卡--device cuda无显卡--device cpu-s/-e指定起始页/结束页定位失败页时非常有用-m auto是我最常用的模式。它会逐页判断页面里有没有可复制的文字层有文字层就按文本抽取没有就自动切换到 OCR。绝大多数场景下什么都不用改。如果你确定整个 PDF 都是扫描件可以直接给-m ocr省掉自动判断的开销。3.3 从产物反推解析质量我第一次跑完就盯着report_layout.pdf看。这页可视化文件把检测到的版面块用不同颜色的框标出来标题、正文、表格、图片、公式都一目了然。框的位置如果对得上Markdown 基本不会出大问题如果某块位置错乱再去翻 JSON 里的对应内容定位原因。report.json是另一个宝藏。它把页面上每个 block 的类型、坐标、文本、嵌套关系都记录下来。举个例子RAG 切分时如果只用 Markdown遇到没有标题的纯文本段落切分器只能盲切但用 JSON 里的 block 类型可以按段落边界 表格完整性 坐标纵坐标来做更精细的切分点选择。这是后面进阶玩法的基础。4. Python API 批量处理把解析变成管线的一环命令行只是探路真正做 RAG 文档预处理手里往往是几十个上百个 PDF必须脚本化。4.1 MinerU 的 Python API 入口4.0 提供了比较干净的 Python API核心就是MinerU类和配置对象MinerUConfigfrom pathlib import Path from mineru import MinerU from mineru.core.config import MinerUConfig def parse_single_pdf(pdf_path: Path, output_root: Path) - None: config MinerUConfig( pdf_filenamestr(pdf_path), output_dirstr(output_root), methodauto, devicecuda, langch, ) mineru MinerU(config) mineru.run()需要注意的一点是不同小版本MinerUConfig里能传的字段名可能略有差异。比如有些版本参数叫device有些版本在配置里用devices也有过调整。最稳妥的办法是安装后先用python -c import inspect; from mineru import MinerUConfig; print(inspect.signature(MinerUConfig))查看实际字段。如果只是想跑批处理不折腾配置也可以直接调用命令行import subprocess def parse_cli(pdf_path: str, out_dir: str) - None: subprocess.run( [mineru, -p, pdf_path, -o, out_dir, --device, cuda], checkTrue, )这样依赖的是已经验证过的 CLI反而更不容易出错。4.2 批量任务编排并发别太贪我写批处理脚本时踩过一次并行度的坑。最初用concurrent.futures.ThreadPoolExecutor开 4 个线程同时跑直接把显存撑爆日志里刷出一片 CUDA out of memory。后来改成两个思路GPU 机器上一次只跑一个 PDF 任务但可以用异步方式预载多个文件路径减少 IO 空闲CPU 机器上开 2 个进程分别跑不同文件只要内存没爆就没问题。一个比较实用的批处理脚本骨架import logging from pathlib import Path logging.basicConfig(levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) logger logging.getLogger(__name__) INPUT_DIR Path(rD:\docs\source) OUTPUT_DIR Path(rD:\docs\parsed) def run_batch(): pdf_files sorted(INPUT_DIR.glob(*.pdf)) logger.info(共发现 %d 个 PDF, len(pdf_files)) for pdf in pdf_files: try: parse_single_pdf(pdf, OUTPUT_DIR) logger.info(成功: %s, pdf.name) except Exception as exc: logger.error(失败 %s: %s, pdf.name, exc) # 记录失败列表最后统一重跑这里我特意不做并发优先保证稳定性。对大多数团队来说晚上挂机批量跑第二天收结果比白天抢显存更实际。4.3 断点续跑失败是常态设计上要认批量跑几十份 PDF总有几份解析到一半崩掉。不想每次重头再来就需要在脚本里加已完成跳过逻辑。判断依据可以这样设计def is_done(pdf_path: Path, output_root: Path) - bool: md_file output_root / pdf_path.stem / f{pdf_path.stem}.md if not md_file.exists(): return False if md_file.stat().st_size 1024: return False # 产物过小大概率没有解析成功 return True主循环里在parse_single_pdf之前先判断is_done已经成功过的文件直接跳过。如果某份文档几十页其中只有一页出了问题可以用-s / -e把那一页单独拎出来重跑再把 md 文件拼接回去。这个方法我用了很多次省下大量重跑时间。5. 扫描版、复杂版式与表格实战调优记录5.1 一眼判断 PDF 是文字版还是扫描版在投入 MinerU 解析前先快速判断文档类型能帮你规划解析策略。用 pypdf 做个简单检测from pypdf import PdfReader def has_text_layer(pdf_path: str) - bool: reader PdfReader(pdf_path) for page in reader.pages: text page.extract_text() if text and text.strip(): return True return False纯扫描件没有任何可选中的文字extract_text()返回空或纯粹乱码这类文档直接全部走 OCR。文字版但是夹杂扫描插图-m auto会自动按页选择策略不需要你操心。不过要注意一种特殊情况部分政府公文、老式印刷 PDF文字层确实存在但字体编码是乱码提取出来是锟斤拷这类这种情况在auto模式下 MinerU 可能也会先走文本抽取路径。我遇到时会手动指定该文档为-m ocr强制整体 OCR效果立刻正常。5.2 表格识别效果好但检查别偷懒MinerU 对规则表格的识别非常强输出为 HTMLtable。这给 RAG 切分带来一个好处表格在切片里是完整结构而不是一串扁平文本。但复杂表格仍然有翻车的时候最典型的是合并单元格错位、跨页表格被切分成两个表。应对办法有两个第一解析后用report_layout.pdf抽查有表格的页面确认框范围是否准确。第二跨页表格在 RAG 场景里可以接受拆成两个块但最好在清洗阶段给两个表加相同的标题标识例如[表 3续]否则检索时只命中断头表会缺失上下文。5.3 公式识别按需开关别让它拖慢整个批次MinerU 能把公式识别成 LaTeX这对论文类 RAG 是刚需。但公式识别模型是管线里最重的一环CPU 上跑一篇公式密集的论文耗时能翻好几倍。如果你处理的文档主要是合同、报告、行政文书根本不需要公式建议在配置里关闭公式相关的识别项。具体参数在不同版本中不一致有的版本通过 pipeline 配置控制有的版本通过模型选择切换建议翻一下对应版本文档里的公式识别Formula开关说明。简单说不是所有文档都需要全套模型按需裁剪是 RAG 预处理调优里最划算的一步。5.4 内存与显存压力大的应对真正的大文档几百页、图片密集容易把内存和显存都顶满。我目前的稳定方案是预处理时按页范围切片跑。先用脚本把大 PDF 按 50 页一组拆成多个小 PDF拆页用 pypdf 几分钟就能写完每组单独跑 MinerU最后合并 md。这么做的还有一个额外好处某组失败重跑时只重跑 50 页不连累其他组。Windows 上次序执行多组任务用个简单的循环脚本就能控制好内存峰值。6. 解析结果如何接入 RAG切分、清洗与验证闭环6.1 为什么 Markdown 结构对 RAG 这么重要很多人问我RAG 切分直接用文本不就行了吗真正做了才知道差在哪。MinerU 输出的 Markdown 保留了标题层级#、##、###这让切分器可以直接按标题边界切而不是固定长度盲切。双栏 PDF 被还原成连续阅读顺序切出来的 chunk 天然就是逻辑完整的段落。表格以 HTML 形式存在embedding 模型能根据 HTML 标签理解表格语义。这些都是一行命令背后的直接价值。配合 LangChain 或 LlamaIndex 的 Markdown 切分器或者直接按需要自定义按#标题切分效果远好于固定chunk_size512。6.2 清洗删除页眉页脚与 OCR 噪声MinerU 不是神它也会把页眉页脚、期刊名称这些重复内容带进 Markdown。这些内容在切分后会造成大量重复 chunk污染检索结果。我的清洗策略是先跑一个规则脚本用正则匹配常见页眉页脚模式如连续多页相同的短段落直接过滤把 HTML 表格中的多余空白单元格压缩删除长破折号、全角空格等 OCR 常见噪声如果在 JSON 里能看到 block 类型优先保留正文、表格、公式块页眉页脚如果被识别出来就丢弃。import re def clean_markdown(md_text: str) - str: # 示例过滤连续重复出现的页眉 lines md_text.splitlines() seen {} result [] for line in lines: key line.strip() if key and len(key) 60: seen[key] seen.get(key, 0) 1 if seen.get(key, 0) 3: continue # 超过 3 页重复判定为页眉 result.append(line) return \n.join(result)这个脚本非常粗糙但足以说明思路把预处理阶段和MinerU 解析两个环节拆开清洗逻辑保持独立便于迭代升级。6.3 质检用 5 类样本文档验证效果在正式跑全量之前建议先建立一个小型质检集。我通常选这 5 类类型检查重点纯文字 PDF标题层级、段落顺序扫描版合同/单据OCR 错字率、数字是否完整复杂表格报表表格行列对应、合并单元格学术论文公式 LaTeX、双栏顺序图文混排手册图片是否保留、说明文字顺序每类文档跑完后人工过一眼 Markdown 和版面可视化记录耗时、成功页数、明显错漏页。如果解析结果不理想返回去看对应页的布局框判断是检测框偏移还是 OCR 引擎的问题。这套质检流程只需要多花半小时却能避免全量跑完才发现方案不可用强烈建议不要跳过。最后我还有一个小技巧把 MinerU 输出的 JSON 作为切分元数据存下来与 Markdown 一一对应。这样在 RAG 上线后如果某个查询命中了一个 chunk可以直接定位回原始 PDF 的页码和坐标做引用溯源。这个功能对于企业内部知识库非常实用因为用户永远会追问你这个回答依据的是文档里的哪一段。