
最近我在整理一批历史 PDF 资料想把里面的文字、公式、表格全部掏出来做成干净的 Markdown 存档。工具我第一反应就是 MinerU——这个开源项目现在很火解析效果确实能打能输出带版式结构的 Markdown公式表格识别也很准。但我手上这台 Linux 机器太尴尬了没有独立显卡内存也就 8GB。本地跑 MinerU 的时候光是加载 PyTorch 和版面检测模型内存就快被吃光了CPU 推理更是慢得感人。为它买一块显卡机器老、电源小拆机折腾一圈也未必稳定实在不划算。我后来换了一条路不碰本地模型直接调 MinerU 的云端接口来解析 PDF。这篇文章就把这套思路从选型到落地的过程完整记录下来代码、参数、踩坑都在里面希望能帮到同样被 MinerU 本地环境劝退的朋友。1. 为什么 MinerU 在本地“跑不动”先找到真正的瓶颈1.1 MinerU 不是简单 PDF 取词它内部是一条深度学习流水线可能有人会觉得PDF 解析不就是把文字复制出来吗如果只是取词那任何一台机器都能跑。但 MinerU 的目标是把“版面乱、有公式、有扫描页”的 PDF 还原成结构化 Markdown这个事情比想象中复杂得多。具体实现里它要先做版面检测把标题、正文、表格、图片区域分别找出来再做阅读顺序还原确保内容不跳跃接着对公式和扫描文字做识别最后统一输出成 Markdown 或 JSON。这里面每一步都依赖深度学习模型模型文件本身就有几百 MB推理时还要占用大量内存。PyTorch 在 CPU 上的推理速度大家心里有数尤其是版面检测和公式识别这种计算密集型的环节CPU 跑起来基本是“拖拉机拉火车”。用大白话打个比方MinerU 本地运行等于为了喝杯咖啡把咖啡烘焙机、磨豆机、萃取机全买回家。你需要的其实只是那杯咖啡但为了自己冲一杯你必须挺过整套设备的占地、电费和维护成本。对于配置普通的 Linux 机器来说这就是资源不足的根源。1.2 本地部署对 Linux 机器的真实要求很多人第一次尝试 MinerU 时是被 GitHub 上那句“支持 CPU 推理”带上路的但实际跑下来会发现“能用”和“好用”是两回事。我把本地部署的硬件门槛和体验整理成一个表方便你对照场景CPU 本地推理GPU 本地推理内存需求至少 16G推荐 32G8G 以上可用但模型加载同样吃内存30 页 PDF 解析耗时5~15 分钟10~40 秒额外依赖PyTorch CPU 版、检测模型、依赖库CUDA、GPU 驱动、显存 4G 以上最常见的坑GLIBC 版本冲突、Python 版本不匹配显卡驱动和 CUDA 版本匹配问题很多 Linux 用户的主力是笔记本、迷你主机或者服务器内存看着不小但 CPU 推理一跑风扇直接起飞其他任务全部卡顿这体验基本宣告“本地跑”不适合日常使用。我试过一次 8GB 内存的机器跑起来系统都开始用 swap 了最后等结果的时间比手动整理还长。所以真正的问题不是 MinerU 本身而是“本地运行”这个前提。这时候把解析任务交给云端让计算发生在远处自己只负责发请求和收结果就是最合理的解法。2. 云端接口的思路把 MinerU 当成远程计算服务调用2.1 云端接口的两种使用形态第一种是官方托管的 API 服务。MinerU 相关的开放平台现在提供在线 API注册后能拿到一个 API Token通过 HTTP 接口上传 PDF 并获取结果。这种形态的优点非常明显本地不需要初始化任何模型环境请求发过去之后计算直接跑在云端的 GPU 上结束后拿结果就行。对一台没有显卡的 Linux 机器来说整个过程只需要能发 HTTP 请求依赖基本为零。第二种是自建解析服务。假如你的 PDF 经常涉及内部资料不方便传到第三方平台或者你们团队本来就有 GPU 服务器只是你个人的电脑没有显卡那就可以在这台 GPU 服务器上把 MinerU 部署成一个 HTTP 服务。社区里已经有人打包好了 Docker 镜像部署过程相当于把完整环境装进容器本地通过接口访问完全走 REST 那套流程。标题里说的“云端接口”其实就是这两种形态的统称。2.2 选型判断什么时候走官方 API什么时候自己架选型核心就两个问题数据隐私和调用量。如果只是个人偶尔处理一些公开资料、文档不敏感用官方 API 最省事注册之后拿 Token关注一下免费额度就行。如果是团队内部经常处理合同、报表这类敏感文档强烈建议自建服务把 MinerU 跑在受控的 GPU 机器上只对内网开放如果偶尔需要通过外网访问可以用常规网络通道映射到本地整个过程文档数据不出内网。维度官方托管 API自建 GPU 服务接入成本低拿 Token 即可高需要部署环境和 GPU数据是否出网会传给第三方不出内网单次成本按量或包月付费主要是服务器成本并发上限受平台限制取决于自身 GPU 容量适合场景个人、原型验证团队、敏感数据我在实操中的策略是先用官方接口验证解析效果确认 MinerU 能够满足业务需求后再在团队一台 GPU 服务器上用 Docker 自建一套作为内部文档系统的解析中间层。两步走的好处是第一步验证算法效果的成本最低第二步再解决长期成本和数据合规问题不用一开始就被部署细节拖住。3. Linux 下调用 MinerU 云端接口的完整流程3.1 准备阶段环境、凭证与连通性测试调用云端接口不需要多复杂的环境Python 3.8 以上即可大部分 Linux 发行版自带。需要额外装一个 requests 库装的时候注意用 pip 而不是系统包管理器直接装 Python 包避免污染系统环境pip install requests然后去对应的开放平台申请 API Token。拿到 Token 后不要写死在脚本里我习惯用环境变量保存既避免误提交到 Git 仓库也方便切换不同账号export MINERU_API_TOKEN你申请的token正式写脚本之前先用 curl 发一个简单的上传请求验证网络、Token、接口都正常。比如接口设计成 POST 上传文件大概长这样curl -X POST \ -H Authorization: Bearer ${MINERU_API_TOKEN} \ -F filetest.pdf \ https://api.example.mineru/v1/tasks这一步能快速暴露 90% 的接入问题域名通不通、Token 对不对、接口路径是否更新。不要一上来就写完整脚本先把链路打通后面就顺了。3.2 核心调用流程上传、轮询、获取结果为什么不是“一次请求直接返回结果”因为 PDF 解析是耗时任务几十页的文档在 GPU 上也要跑一会儿HTTP 请求不可能保持连接等那么久。所以云端接口通常采用异步任务模式客户端上传 PDF 文件服务器立刻返回一个 task_id客户端拿着 task_id 轮询任务状态状态变成 succeeded 后根据返回的 JSON 获取 Markdown、图片等结果。这个流程跟点外卖很像下单上传→ 拿到订单号task_id→ 刷新订单状态轮询→ 显示已送达succeeded后去取餐拿结果。理解了这个模式代码就顺理成章了。下面是一个完整可参考的 Python 脚本我一直在用这套骨架改动字段名就能适配不同平台的接口import os import sys import time import requests API_BASE os.environ.get(MINERU_API_BASE, https://api.example.mineru/v1) API_TOKEN os.environ.get(MINERU_API_TOKEN, ) HEADERS {Authorization: fBearer {API_TOKEN}} def upload_pdf(pdf_path: str) - str: with open(pdf_path, rb) as f: resp requests.post( f{API_BASE}/tasks, headersHEADERS, files{file: (os.path.basename(pdf_path), f, application/pdf)}, timeout60, ) resp.raise_for_status() data resp.json() task_id data.get(task_id) or data.get(id) if not task_id: raise RuntimeError(funexpected response: {data}) return task_id def poll_task(task_id: str, interval: int 3, timeout: int 300) - dict: start time.time() while time.time() - start timeout: resp requests.get( f{API_BASE}/tasks/{task_id}, headersHEADERS, timeout15 ) resp.raise_for_status() data resp.json() status data.get(status) print(f[{time.time() - start:.0f}s] task status: {status}) if status succeeded: return data if status in (failed, error): raise RuntimeError(ftask failed: {data}) time.sleep(interval) raise TimeoutError(ftask timeout after {timeout}s) def main() - None: pdf_path sys.argv[1] if len(sys.argv) 1 else test.pdf task_id upload_pdf(pdf_path) print(fuploaded, task_id{task_id}) result poll_task(task_id) result_data result.get(data, {}) markdown result_data.get(markdown, ) with open(result.md, w, encodingutf-8) as f: f.write(markdown) images result_data.get(images, []) print(fmarkdown saved, total images: {len(images)}) if __name__ __main__: main()使用方式python3 mineru_cloud.py history.pdf几个细节值得强调。第一字段名task_id、data、status可能因为平台版本不同而有所差异我写的是最常见的结构如果实际响应不一致先把返回的 JSON 打印出来对照实际字段再改代码别硬套模板。第二上传的文件名建议用英文部分服务端对中文文件名的处理不够规范容易返回 400。第三轮询间隔默认 3 秒如果平台有请求频率限制把间隔调到 5~10 秒更稳妥。3.3 结果落地Markdown 保存与图片资源管理云端接口返回的不只是 Markdown 文本通常还有 JSON 格式的结构化结果。我归档资料时会用固定目录组织每个 PDF 对应一个目录里面放 result.md、meta.json 和 images/ 子目录。这样后续不管是批量导入知识库还是做全文检索都方便处理。archive/ ├── history/ │ ├── result.md │ ├── meta.json │ └── images/ │ ├── img001.png │ └── img002.png如果 PDF 中有大量图片云端结果里通常会有图片下载地址需要自己把这些图片下载到本地。注意这类地址往往有有效期拿到结果后要立刻拉取不要拖到第二天再处理否则链接过期就麻烦了。我一般会在同一个脚本里把图片下载和 Markdown 保存一起做完减少二次操作。还有一个细节解析出来的 Markdown 里标题层级可能跟原始文档的视觉层级不完全一致。归档前我会用脚本做一次简单的标题清洗统一层级格式比如把一级标题统一成“#”开头方便后面导入其他系统这个习惯能省很多后期整理的功夫。4. 参数调优与批量处理的进阶玩法4.1 解析参数怎么选不同平台的云端接口暴露的参数不一样但影响解析结果和耗时的核心参数就那么几个。我整理成表格方便按需取用参数作用我的建议ocr是否强制 OCR扫描版 PDF 必须开文本型 PDF 不开能省大量时间language识别语言用中英混合避免繁体中文被识别成英文公式转 LaTeX公式识别输出理工科文档打开纯文本文档可以关表格识别是否重建表格结构办公文档建议打开但会增加耗时输出格式markdown / json / html归档用 markdown做数据抽取用 json以我的经验ocr 参数是最影响耗时的一项。文本型 PDF 本来就是可复制的文字你强制 OCR 一遍等于把已经打包好的文件重新扫描打印一遍纯属浪费资源。如果接口提供自动判断模式优先开启让它自动识别文档是否包含可拷贝文本能不开 OCR 就不开。但遇到扫描版或图片型 PDFOCR 就必须打开否则解析结果就是空的。4.2 批量处理与并发控制单个 PDF 如果超过平台限制比如 50MB或者内容太多导致解析超时可以先在本地切分成多个小文件再逐个上传。Linux 下准备一个 poppler-utils 包里的 pdfseparate 命令就够了pdfseparate input.pdf page-%d.pdf切分这一步千万别嫌麻烦。切分后每个子任务耗时更短失败重试的成本也低得多比如说某一个 50 页文档的第 17 页解析失败了你只需要重新上传这一页对应的小文件不用整个文档重跑一遍。并发控制同样重要。即使手里有 100 个 PDF 要处理也不要一次性全部提交上去。我踩过的坑是并发开太高平台直接开始限流后续任务卡在 pending 状态排队整体效率反而更低。建议在本地维护一个任务队列同时控制并发数量在 3~5 个左右。核心思路用 Python 的 ThreadPoolExecutor 示例from concurrent.futures import ThreadPoolExecutor, as_completed def process_one(pdf_path: str): task_id upload_pdf(pdf_path) result poll_task(task_id) save_result(pdf_path, result) return pdf_path with ThreadPoolExecutor(max_workers4) as executor: futures [executor.submit(process_one, p) for p in pdf_files] for future in as_completed(futures): file_done future.result() print(fdone: {file_done})重试逻辑也要设计好。任务失败如果是偶发网络抖动重试一次大概率就过了但如果同一个文件连续重试 3 次都失败多半是文件本身有问题比如 PDF 已损坏、设置了打开密码或者内容特殊。这时候先去检查文件而不是盲目重试浪费配额。5. 实测高频问题与排查思路5.1 问题速查表我把实际使用中碰到过的高频问题汇总成一个表遇到类似情况可以直接对着排查现象可能原因处理办法上传后立刻返回 401Token 缺失或过期检查环境变量是否设置重新生成 Token上传超时PDF 太大或网络差先用 pdfseparate 切分增大上传超时时间任务一直 pending平台排队或本地并发过高降低并发数超过 10 分钟没变化再查平台状态返回成功但 markdown 为空页面全是图片或扫描内容打开 OCR 参数重新解析中文乱码或繁体识别错语言参数没设置把 language 设为中英混合HTTP 429触发限流加大轮询间隔加退避重试检查配额用量图片下载链接返回 403链接已过期结果返回后立即下载图片资源5.2 内网环境下的替代思路如果你的 Linux 机器在内网无法直接访问外部 API还有一个思路可以参考找一台既能访问外网、或者本身就有 GPU 的中转机器在上面部署 MinerU 服务然后通过 SSH 端口转发把服务映射到本地回环地址。这样本地调用时就像在访问 localhost 一样SSH 本身会做加密传输属于常规运维手段不需要额外买显卡。这种方式对“本机没显卡、但有远程机器可用”的场景特别实用。5.3 心态提醒别把云端接口当黑盒第一次接入云端 API 时强烈建议先拿一个 2~3 页的小样本测试把返回的 JSON 完整打印出来看一看搞清楚里面到底有哪些字段。不要急着把几百个 PDF 一次性传上去先验证字段结构、再处理图片下载、最后接入业务流程这是减少返工的最佳路径。如果某个平台的接口文档写得不清不楚直接在命令行里用 curl 打一下接口把响应保存成文件慢慢研究比反复猜测字段名高效得多。最后说一点个人习惯所有云端接口都要注意文件隐私边界。PDF 里经常藏着大量敏感信息上传前先确认这个平台是否可信、是否满足你的合规要求。别因为本地跑不动就放松了这根弦数据安全这件事从源头把关永远比事后补救省心。我做这套调整已经好几个月了最大的感受是本地那台老机器终于不用为 MinerU 的模型环境折腾了PDF 解析也变成了我自动化流水线上一个标准动作。如果你也处在“机器不行、但又要用 MinerU”的尴尬位置先别急着买显卡——翻一翻官方文档里的 API 说明或者找一台现成的 GPU 服务器把 MinerU 包装成 HTTP 服务两三个小时就能跑通。对比硬件投入这个方案明显划算得多。