这是系列文章的第二篇,重点介绍原生文字健康检测、块级文字对齐、递归序列化、跨页表格、图片资产以及人工校正治理。
公开说明:所有网络地址、账号、Token、密钥、源文档名称、内部哈希和个人路径均已删除或泛化。
系列目录
- 解析架构与质量基线
- 块级融合与人工质量验收
- Chunk、Embedding 与混合检索
- 服务部署、增量发布与安全运维
1. 为什么必须增加融合层
MinerU 能提供版面、阅读顺序、表格、图片和 bbox,但它的视觉正文并不总是适合作为最终正文。
实际测试发现,在健康原生文字页中,VLM 可能:
- 删除标准号和技术对象;
- 将一组英文缩写变成
/; - 丢失代码标识符;
- 改变表格 caption;
- 对看似重复的技术文本进行“概括”。
另一方面,原生文字层也可能损坏:肉眼看到的是正常中文,直接提取却只剩标点、符号和无意义短串。
所以最终采用统一融合流水线:
MinerU:结构、顺序、bbox、表格、图片、页面映射
PDF native:健康页面的正文真值
MinerU VLM:损坏文字层、扫描页和视觉页的正文
Fusion:按 block 对齐并输出统一 Schema
2. 原生文字健康检测
早期规则只检查:
- 控制字符;
- Unicode 私用区;
- 替换字符;
Gxx类伪字符。
这不够。有些损坏文字完全由合法 ASCII 字符组成,例如大量 !、#、$ 和短串。编码层面没有异常,语义层面却不可读。
最终加入以下指标:
- CJK 字符比例;
- 可读英文单词比例;
- ASCII 符号异常比例;
- 标点/符号熵;
- 私用区字符比例;
- 替换字符比例;
Gxx伪字符比例;- 英文异常逐字空格;
- native 与 VLM 的脚本分布差异;
- 页面可见内容覆盖率;
- 页类型分类。
2.1 native 与 VLM 对照
对于中文可见文字页,如果出现:
- native 几乎无中文;
- native 主要由符号和无意义短串组成;
- VLM 有大量正常中文;
- 两者脚本分布差异巨大;
则必须将 native 判定为损坏文字层。
这种判断不能只靠一个字符数阈值。图像页的错误 caption 可能超过十个字符,但仍完全不可读。
2.2 页面分类
建议为页面增加显式类型:
TEXT_PAGE
TABLE_PAGE
FIGURE_PAGE
VISUAL_ONLY_PAGE
OCR_PAGE
MIXED_PAGE
FIGURE_PAGE 和 VISUAL_ONLY_PAGE 需要优先检查图片 caption 与视觉覆盖率,不能仅因 native 字符数非零就判为健康。
2.3 决策逻辑
if native_text_health == HEALTHY:
authoritative_source = native_pdf
else:
authoritative_source = mineru_hybrid_ocr
这只是页面级决策。真正输出正式正文前,还需要完成 block 级对齐。
3. 什么才是真正的块级融合
把整页 native 正文放在 body_text,再附上一组使用 VLM 错误文字的 bbox,不是块级融合。
正确流程:
- 每份 PDF 打开一次;
- 每页
PdfPage和TextPage构建一次; - 提取带坐标的 native spans;
- 读取 MinerU block、类型、order 和 bbox;
- 使用几何重叠、顺序、脚本和文本特征进行匹配;
- 健康页 block 使用对齐后的 native 文字;
- OCR 页 block 使用 MinerU Hybrid High 结果;
- 无法对齐时显式标记
UNRESOLVED; - 任意
UNRESOLVED阻断后续 Chunk。
性能上必须缓存同页对象。同一页所有 block 共用一次 native 文字提取,页面处理完再释放;不能为每个 block 重新打开 PDF。
4. 统一 Block Schema
正式 Schema 可设计为:
{
"block_id": "stable-block-id",
"document_id": "stable-document-id",
"original_page": 32,
"order": 17,
"type": "text",
"bbox": [72.1, 118.4, 523.8, 166.2],
"bbox_coordinate_space": "pdf_page",
"bbox_origin": "top_left",
"page_width": 595.28,
"page_height": 841.89,
"bbox_unit": "pt",
"text_source": "native_pdf",
"text_alignment_status": "ALIGNED",
"raw_text": "原始提取文字",
"normalized_text": "确定性修复后的文字",
"search_text": "面向检索的文字",
"normalization_operations": [],
"search_normalization_operations": [],
"dictionary_suggestions": [],
"requires_review": false,
"structure_source": "mineru",
"table_html": null,
"asset_path": null,
"source_sha256": "<source-pdf-sha256>"
}
4.1 坐标元数据
仅保存 [x0, y0, x1, y1] 不够。每个 block、表格片段和图片都应保存:
bbox_coordinate_spacebbox_originpage_widthpage_heightbbox_unit
否则迁移到另一个渲染器后,很难判断 bbox 使用 PDF point、像素还是归一化坐标,原点在左上还是左下。
4.2 资产路径
正式 asset_path 必须是相对于文档输出根目录的 POSIX 路径:
assets/structured-crops/page-0037-figure-001.png
不能把某台 Windows 工作站的绝对路径写进迁移主字段。运行时可以额外提供 runtime_absolute_path,但它不能成为 Canonical 的正式定位方式。
5. 递归序列化 MinerU 内容
一个很隐蔽的问题来自 content_list_v2 的序列化器。如果只读取浅层字段:
text
table_body
caption
footnote
那么标题、列表、公式和嵌套内容会被静默遗漏。
递归序列化器至少支持:
title_contentparagraph_contentlist_itemsitem_contentequation_inlinetable_captiontable_footnoteimage_captionimage_footnote- 普通
text - 嵌套
list与dict
推荐将遍历逻辑写成字段白名单加通用递归,而不是不断给顶层 if 打补丁。
还应增加确定性门禁:MinerU 识别出的 list_items 数量必须等于最终序列化数量。
VLM 原始序列化内容可以保留用于审计,但应明确标记:
non_authoritative_vlm_audit
否则人工审核者容易误以为其中的错误文字就是最终融合正文。
6. 跨页表格的正确模型
跨页表格必须同时保存两种资产。
6.1 logical_table
表示一张完整逻辑表:
- 稳定
logical_table_id; - 正式 caption;
- 合并后的完整结构;
- page fragment ID 列表;
- 唯一片段数;
- 合并后行数。
6.2 page_table_fragment
表示每个原始页面自己的真实表格片段。它应直接来源于 MinerU middle.json 中该页面的 preproc_blocks,而不是从合并表反推。
{
"fragment_id": "stable-fragment-id",
"logical_table_id": "stable-table-id",
"original_page": 8,
"order": 3,
"bbox": [60.0, 100.0, 540.0, 780.0],
"bbox_coordinate_space": "pdf_page",
"bbox_origin": "top_left",
"page_width": 595.28,
"page_height": 841.89,
"bbox_unit": "pt",
"fragment_index": 2,
"fragment_count": 3,
"continuation": true,
"table_html": "<table>...</table>"
}
7. 表格双向关联门禁
必须同时满足:
- 每个 fragment 恰好被一个活动 logical table 引用;
- logical table 引用的 fragment,其
logical_table_id反向一致; - 同一 fragment 不得出现在多个 logical table 中;
fragment_count等于唯一 fragment 数量;fragment_index在1..fragment_count内连续且不重复;- 同一表的片段按原始页码和页内 order 排序;
- logical table 行数与各页片段合并去重后的行数一致;
- fragment 必须具有真实
original_page和 bbox。
如果两个 logical table 同时引用同一组 fragment,而 fragment 又只反向指向其中一个表,JSON 仍然合法,但所有权关系已经损坏。这类问题必须阻断,而不是自动挑一个表继续。
7.1 caption 的权威来源
健康 native 页的表格 caption 也必须通过 native bbox 对齐获得。否则 VLM 可能把:
表 A.1
变成:
表 A1
这种小数点丢失会直接影响图表引用检索。
8. 图片资产治理
图片处理遵循:
- 原始裁剪图是主资产;
- Mermaid 或图片描述只是辅助;
- 不允许 Mermaid 替代原图;
- 每个图对象保留图题、原始页码、bbox、相对路径和文件 SHA-256;
- 图片对象数量与真实文件数量一致;
- 图题使用可信文字源;
- 服务器只保留必要结构化裁剪,不保留重复整页图。
图像页尤其容易出现“native 字符数大于阈值,但内容实际是乱码”的误判。因此图题也必须经过文字健康检查。
9. 三层文字设计
9.1 raw_text
原始提取结果,永久不改。它是审计、回放和后续改进算法的依据。
9.2 normalized_text
只进行确定性修复,例如:
10. 3. 1->10.3.1;- 标准号内部确定的异常空格;
- 可以由上下文和格式规则唯一确定的符号。
所有修改写入 normalization_operations。
9.3 search_text
用于 BM25 和 Embedding,可额外进行:
- 中文异常逐字空格修复;
- OCR 产生的 LaTeX
\sim->~; - 标准号检索格式统一;
- 不改变语义的空白和标点处理。
所有修改写入 search_normalization_operations。
10. 不确定术语只能建议,不能覆盖
如果 OCR 将一个技术词识别成近形字,而系统只有“疑似正确”的判断,应保留原文:
{
"raw_text": "OCR 原始结果",
"normalized_text": "OCR 原始结果",
"dictionary_suggestions": [
{
"candidate": "疑似正确词",
"reason": "domain_dictionary_match"
}
],
"requires_review": true
}
字典建议服务于人工审核,不能借“自动纠错”之名覆盖 OCR 证据。
11. 自动质量门禁
融合完成后至少检查:
- 原始 PDF SHA-256 不变;
- 页面映射与原 PDF 一致;
- JSON 严格解析,无 NaN/Infinity;
UNRESOLVEDblock 为 0;- 可见正文或表格页不能同时正文为空且无表格片段;
- OCR 页正文达到最低覆盖率;
- native/VLM 脚本差异没有触发误信任;
- 标准号、年份和技术标识召回;
- 条款号、图号和表号格式;
- 阅读顺序;
- 跨页表格首列或行号连续性;
- 图片对象与文件一一对应;
- list item 识别数与序列化数一致;
- logical table 与 fragment 双向一致;
- bbox 坐标元数据完整;
- 所有正式资产路径均为相对路径。
不能只因为文件存在、JSON 可解析就输出 PASS。
12. 人工 QC 检查什么
自动检查后,人工逐页对照原图,重点检查:
- 标准号和年份;
- 条款编号;
- 英文缩写和代码标识;
- 数字字段;
0/O、1/I/l、5/S、8/B;- 表题、表头和数字单元格;
- 跨页表格连续性;
- 图题和真实图片;
- 代码块的大小写、标签号、括号和跨页闭合关系。
每个待核对项都包含:
路线 / 文档 / 原始页码 / 输出文件 / 提取文本
人工结果:PASS | FAIL | NOT_CHECKED
备注
自动状态与人工状态分离:
PASS_PENDING_HUMAN_QC
PASS_WITH_REVIEW_FLAGS
BLOCKED
自动结构检查通过,只代表可以进入人工复核,不代表 OCR 内容已经正确。
13. Pilot 修复了什么
全量前先运行约 100 页 Pilot。它暴露了两个会污染全库的类别:
NATIVE_TEXT_HEALTH_FALSE_POSITIVE
LOGICAL_TABLE_FRAGMENT_OWNERSHIP_INVALID
前者导致正常中文页面使用乱码 native 层,后者导致两个逻辑表争用同一组页级 fragment。
修复后重新构建融合层,不重跑已存在的 MinerU 模型产物,并对全部 Pilot 页面重新执行门禁。只有自动检查和人工抽查均通过后,才进入 2,999 页全量。
14. 全量分批与断点恢复
全量按小批次处理,每次持久化:
- 当前批次;
- 已完成文档;
- 失败文档;
- 每个文档的重试次数;
- 输入来源哈希;
- 输出 Manifest 哈希;
- 文档 QC 状态。
最终 41 份文档分 14 批完成,未留下失败文档。中途中断时只继续未完成文档,不重跑成功结果。
15. 人工校正包
少数技术代码页即使整体通过,仍可能出现确定性序列化错误。正确校正流程是:
- 固定异常
block_id; - 保存异常 raw 文本;
- 保存原始页证据;
- 创建 proposed correction;
- 逐行人工核对;
- 保存文件清单和内部 SHA-256;
- 审核 PASS 后只替换目标 block 的正式文本;
- 保留 raw 文本和校正记录;
- 重跑块级、页级、严格 JSON 和全量门禁。
校正包可以包含:
correction-request.json
original-page.png
original-abnormal-text.txt
proposed-correction.txt
human-review.json
package-manifest.json
SHA256SUMS
不要直接打开 Canonical JSON 手工改字。没有证据、范围和哈希的修改无法可靠重放。
16. 本阶段完成标准
- 所有健康页使用 native block 对齐文本
- 所有 OCR 页同时保留 raw 和 normalized/search 文本
-
UNRESOLVEDblock 为 0 - 表格 logical/fragment 关系双向一致
- 每个图片对象有真实资产
- 所有 bbox 坐标元数据完整
- 资产路径可迁移
- 自动门禁通过
- 人工抽查通过或明确保留 review flag
- Canonical 输入和输出哈希冻结
当 Canonical 通过这些门禁后,才允许进入 Chunk 和 Embedding。下一篇将介绍结构感知切分、Qwen3 Embedding、PostgreSQL、HNSW、BM25、RRF 与 Reranker 的完整构建过程。
创建可增量发布的私有知识库(二):块级融合与人工质量验收
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法