Ch28: Python docreader——解析边界、单测与竞赛最快路径
Part 8 · 前置:Ch27 分块 · 后续:Ch29 docparser 案例:case-1785 · case-1743
结论先行:docreader/ 是 格式脏活层——Excel IMAGE、PDF OCR、MHTML 内嵌图。竞赛 Python 路径 = parser 改动 + pytest,通常 1–3 天可合入。设计原则:噪声在 parse 最早阶段过滤,不要指望 Rerank 兜底(GIGO)。
一、日常类比
docreader 像进厂质检:Excel/PDF 是不同供应商的包装箱。有的在箱里塞了「占位符纸条」(DISPIMG),有的把图片贴在外面但说明书写「见上图」。质检必须在 开箱时 扔掉纸条;若纸条进了流水线(chunk → 向量),后面仓库管理员(Rerank)无法区分「真知识」和「印刷垃圾」。
二、目录职能地图
docreader/
├── parser/ # excel_parser, pdf_parser, mhtml, ...
├── tests/ # 边界用例 — 大师 PR 必带
└── api/ # gRPC/HTTP,Go 侧 consumer
| 格式 | 典型坑 | 案例 |
|---|---|---|
| Excel WPS | DISPIMG 文本进 cell.value | #1785 |
| MHTML | 图片分辨率/链接在 Py→Go 丢失 | #1743 |
| 扫描 PDF | OCR 5–15% 错字 → BM25 miss | 通用 |
| 双栏 PDF | 阅读顺序左右栏交叉 | Ch39 |
三、大师四步法(来自 case-1785)
- 复现 — 最小 fixture(xlsx/mhtml 单文件进
tests/fixtures/) - 根因 — 追踪
parse_into_text()哪一步把噪声当文本 - 最早过滤 — 正则/规则在写入结果 之前
- 参数化单测 — 每个变体一行
import re
import pytest
IMAGE_FN = re.compile(r'^=?(?:_xlfn\.)?DISPIMG\s*\(', re.I)
@pytest.mark.parametrize("cell_value", [
'DISPIMG("ID_x",1)',
'=_xlfn.DISPIMG("ID_x",1)',
'正常文本',
])
def test_image_function_filtered(cell_value):
if IMAGE_FN.match(cell_value.strip()):
assert extract_text(cell_value) == ""
else:
assert "正常" in extract_text(cell_value)
四、与 Go 的边界契约
docreader 输出 纯文本或 Markdown 字符串 → Go docparser 不再猜测 Excel 语义。
| 应留在 Python | 应交给 Go |
|---|---|
| 格式检测、OCR、IMAGE 过滤 | HTML→MD 归一化、chunk 边界 |
| 二进制解析 | 与 chunker 的格式 invariant |
Noise 不过界:Python 漏检的 HTML 表,Go 仍可部分修复(#1725);但 DISPIMG 字符串一旦写入,下游无法自动识别为噪声。
五、grep 与本地调试
cd WeKnora
rg -n "parse_into_text|ExcelParser" docreader/
pytest docreader/tests/test_excel_parser.py -q
# MHTML: rg -n "mhtml|MHTML" docreader/
Docker 全栈时改 Python 后 重建 docreader 镜像 或挂载 volume,否则 Go 仍调旧服务。
六、动手清单
- 精读 case-1785 数据流 mermaid
- 本地 pytest 通过
ExcelImageFilterTest - 上传含 WPS 图片的 xlsx,确认回答无 DISPIMG
- 对照 lab/corpus/parse-pitfalls.md 写 1 条新 pytest
章末自检
- 解释
data_only=True对 WPS DISPIMG 为何无效 - 说清 docreader vs docparser 职责分界
- 设计一个「应被过滤」的 cell 样例并写测试名