mineru-refine
MinerU 解析结果的后处理器(linter / fixer)。
MinerU 把 PDF 解析成 content_list.json——一个 item 对象数组,每个 item 是一段正文、
一个标题、一张表格或一张图。解析质量很好,但结构上有一类高频问题:
- 伪标题——一句普通正文被误标成标题
- 漏标标题——同级编号兄弟都是标题,它却被标成正文
- 跨页断句——一句话被页边界切成两个 item
- 跨页拆表——同一张表被切成多页的多个表
- 表内续行——一条记录的长单元格被切成多个只有一列有字的
<tr>
- 页面家具——页眉、页脚、页码混进正文
- 残留符号——markdown 链接、LaTeX 命令、
\$/\* 转义等解析残骸
- 巨型块——多个小节被糊成一个超长 item
- 段尾粘连——跨页合并把「[相关文件]」这类独立结构块吸进上一段结尾
- 表格噪声——全空
<tr>、单元格内 OCR 空格(含被空格打断的 URL)、伪 LaTeX 包装
($\text{...}$ 套着普通文字,已知符号命令换成 Unicode;\frac 等真公式不动)
- 形近字符的少数派写法——同一文档里
SWOT×24 与 SW0T×6 并存(0↔O 形近误认),
全文频率投票直接改写少数派
- 被吞进表格题注的小节标题——MinerU 把「4.6 核心组织绩效的应用」塞进相邻表格的
table_caption,渲染后貌似漏升级标题,实为结构错位
- 被吞进表格题注的页眉/页脚家具——MinerU 把跑马灯页眉「附件3:…」、页脚「编制人:张威」
塞进跨页表片段的
table_caption,mergeTable 又忠实保留,渲染成残留(靠同文家具佐证识别后删掉)
其中无歧义的表格噪声与频率投票由机械清洗 pass(确定性代码、自带校验、不打 LLM)
直接处理,其余疑点交 LLM 裁决。
mineru-refine 接收 content_list,修掉这些问题,返回同 schema 的 content_list。
下游读到的仍然是"一份 MinerU 结果"——作为透明过滤器接进现有 pipeline,消费方零改动。
两条核心承诺:
- 绝不新增一个字:只做削减与重组(合并、拆分、删除、降级),输出的每个内容字符都
来自输入,且由机器在每一步校验——不是靠 prompt 约束 LLM,而是违反即自动回滚。
默认配置下唯一的字符替换是机械清洗的全文频率投票(
SW0T→SWOT:全文多数派
≥4 次且 ≥3× 少数派、恰差一处形近字符,证据全文自明、确定性落地,逐条留痕于
report.removedSpans,reason=mech:token_vote→…)。
- 绝不搞崩上游(fail-open):任何异常、超时、LLM 不可用,都原样返回输入 items
并大声记 log,
report.failOpen 标记为 true。
修复决策由 LLM 做("这个疑似伪标题该降级,还是误报?"),但 LLM 只负责在预定义的修复
操作里选一个——执行、校验、终止全部由确定性代码控制,是否合格由机器闸门裁决,
不由 LLM 自评。
安装
核心是一份 Rust 实现,各语言绑定直接 import 同一份代码,选项与返回值完全同构:
| 语言 | 安装 | 形态 |
|---|
| Python | pip install mineru-refine | PyO3 原生扩展(文档) |
| JS/TS | bun add mineru-refine / npm i mineru-refine | napi-rs 原生插件,Bun / Node ≥18(文档) |
| Rust | cargo add mineru-refine | core crate(文档) |
| 任意语言 | cargo install mineru-refine --features bin | HTTP server / CLI(见下) |
| Claude Code | /plugin install mineru-refine | 端到端 skill:文件进、干净 markdown 出(文档) |
以上 Python/JS/Rust/HTTP 都接收已解析的 content_list(由 MinerU 产出)。如果你手里只有
原始文件(PDF/DOC/PPT/图片),想要"一步到位"拿干净 markdown,见下面的 Claude Code plugin——它把 MinerU 官方 API 解析与 mineru-refine 清洗串成一个 skill。
快速上手
需要 LLM API key(见环境变量):DEEPSEEK_APIKEY 必需,
QWEN_APIKEY 在启用表格视觉裁决时需要。
Python:
import json
import mineru_refine
items = json.load(open("content_list.json"))
result = mineru_refine.refine(items, image_dir="/abs/path/to/mineru/output")
result["items"] # 清洗后的 content_list,schema 与输入一致
result["report"] # 审计报告:做了什么、删了什么、花了多少 token
JS/TS:
import { refine } from "mineru-refine";
const { items, report } = await refine(contentList, {
imageDir: "/abs/path/to/mineru/output",
});
Rust:
use mineru_refine::{refine, RefineOptions};
let result = refine(items, RefineOptions {
image_dir: Some("/abs/path/to/mineru/output".into()),
..Default::default()
}).await;
// 永不 Err、panic 不外漏:fail-open 内置,看 result.report.fail_open
HTTP(任意语言):
cargo install mineru-refine --features bin
mineru-refine-server # 默认端口 8771,MINERU_REFINE_PORT 可改
curl -X POST localhost:8771/refine \
-d '{"items":[...], "imageDir":"/abs/path/to/mineru/output"}'
curl localhost:8771/health
imageDir 是 MinerU 产物目录(含 images/ 的那个目录),可选:提供则启用跨页拆表的
视觉裁决(用表格裁剪图判断"是不是同一张表"),不提供则该类问题整体跳过、表格原样保留。
HTTP 模式下该目录须与 server 共享文件系统。
建议消费方在读 content_list.json 之后、消费之前调一次,用返回的 items 替换原数组;
调用侧再兜一层超时回退,与内置 fail-open 构成双保险。
Claude Code plugin(PDF 进、干净 Markdown 出)
上面的库/绑定都从 content_list 起步——默认你已经跑过 MinerU。plugin/ 提供一个 Claude Code
plugin,把"解析 + 清洗"两步串成一个 skill:原始文件(PDF/DOC/PPT/图片)进,干净 markdown 出,
无需自己写集成代码。
/plugin marketplace add LcpMarvel/mineru-refine
/plugin install mineru-refine@mineru-refine
装好后直接说「清洗这个 PDF:/abs/path/to/报告.pdf」,skill(mineru-prime)会:
- 解析 —— 文件交 MinerU 官方 API,得
content_list.json + images;
- 清洗 —— 调 mineru-refine 后处理(三层 opt-in 清洗默认全开,追求最干净产物);
- 产出一个 drop-in 替身目录(
images//layout.json 镜像,content_list.json 换清洗版,
full.md 重渲染,外加 refine_report.json),放进 mineru-refine-out/refined/。
首次运行 skill 会引导写入工作目录 .env(持久化):MINERU_API_TOKEN(解析)、
DEEPSEEK_APIKEY(清洗)、QWEN_APIKEY(视觉裁决,强烈建议)。依赖 bun +
unzip;npm 原生绑定首次自动 bun install(含预编译二进制,无需 Rust 工具链)。
详见 plugin/README.md。
选项与返回值
各语言只是命名风格不同(Python 蛇形、JS 驼峰),语义相同: