PDF 解析

PDF 里的一个金额,怎样找回它在页面上的位置

本文演示使用PyMuPDF在指定矩形内定位重复金额文本。通过解析words八元组前五字段并校验候选数量,避免碎片拼接误判。提供内存级Python示例、边界处理策略及验收表,并给出与OmniDoc文档翻译工作流衔接的实务建议。

阅读文本 / Reading text
本文使用合成样本演示处理方法。代码已通过隔离环境中的示例检查;这不代表对所有真实文件或产品效果的保证。处理重要文件前,请保留原件,并按文中的边界与验收项复核。 一张对账单里出现两次 1,200.00 :一次在应付金额栏,一次在说明文字里。全文搜索能找到它们,却不能替你决定哪一个是业务字段。直接取第一个命中,是一种很难被发现的错误——结果看起来像正确的金额,来源却已经错了。 如果这份表单的版式固定,先限定一个已知区域,再查文字,往往比继续加正则表达式更容易解释。关键不是“只找到一个框”,而是把零命中、多命中都当成明确结果处理,不让程序擅自替业务做选择。 留下词的位置,而不只留下文字 PyMuPDF 的 page.get_text("words") 返回带位置的词记录。常见字段为 (x0, y0, x1, y1, text, block_no, line_no, word_no) 。下面的示例只取前五个字段,后三个编号暂时不参与区域筛选。 这里的词是提取器给出的片段,不一定等于语言学意义上的一个词,更不保证一个金额永远是完整片段。 1,200.00 可能被拆开,也可能带上相邻标签。本文只处理完整片段的精确匹配:宁可把不满足条件的记录交出去复核,也不悄悄拼接成一个貌似合法的金额。 两个相同金额的合成页面 我们在内存中新建一页 PDF,把同一个金额分别放到纵坐标 120 与 420 附近。查询区域只覆盖上方金额。文字插入点是基线位置,不是文本框左上角;真实框的高度、宽度会受字体和字号影响,因此不能把坐标写死成“必然等于某个值”。 正确输出应该满足两个条件:候选只有一个,而且候选矩形完整包含在查询区域内。随后再把查询范围扩大到整页,应该得到两个候选,从而进入歧义分支。最后用不在页面上的字符串检查零命中。这比只演示一次成功查找更接近真实接口所需的契约。 可运行代码:把候选数量交给调用方 依赖为 pymupdf ,请在测试环境安装并锁定版本。下面不访问网络、不写文件,也不会修改原有客户文档。它构造的只有演示用 PDF。 import pymupdf def locate(page, target, query): query = pymupdf.Rect(query) candidates = [] for word in page.get_text("words"): x0, y0, x1, y1, text = word[:5] box = pymupdf.Rect(x0, y0, x1, y1) if text == target and query.contains(box): candidates.append(tuple(box)) return candidates def decide(candidates): if len(candidates) == 0: return "missing" if len(candidates) > 1: return "ambiguous" return "unique" with pymupdf.open() as doc: page = doc.new_page(width=595, height=842) page.insert_text((80, 120), "1,200.00", fontsize=11) page.insert_text((80, 420), "1,200.00", fontsize=11) query = pymupdf.Rect(72, 100, 300, 150) local = locate(page, "1,200.00", query) assert decide(local) == "unique" assert query.contains(pymupdf.Rect(local[0])) assert decide(locate(page, "1,200.00", page.rect)) == "ambiguous" assert decide(locate(page, "999.99", query)) == "missing" print("unique / ambiguous / missing cases passed") contains 要求完整包含,而不是只碰到区域边缘。如果查询框太紧,靠边的金额可能落入零命中;这不是应该自动放宽到全页的理由。先检查你测量区域时用的坐标系,再决定调整范围。若业务确实允许中心点落入即可,应该另写规则,并为边界重叠补测试,不要在同一函数里暗中改变含义。 三种结果,三种处理方式 结果 可能原因 建议动作 唯一命中 区域内只有一个完整匹配片段 保存页号、原文、矩形与规则版本 零命中 区域不对、金额被拆分、文本层缺失 标记原因候选,检查原页,不补零 多命中 同一区域有重复金额,或模板已变化 返回全部候选,结合标签或人工消歧 有框但数值不对 文本层本身错误或匹配目标不对 回到原始页面核对,不让坐标替数值背书 框看着偏移 显示坐标和提取坐标混用 明确旋转、裁切与渲染缩放的转换链 生产逻辑不应只靠 assert 拦截异常,因为优化模式可能移除断言。示例里它用于回归检查,真正接入任务队列时应使用明确的条件分支和业务状态。 这不是通用金额提取器 代码没有解析币种、千位分隔符或小数规则,也没有识别“本期应付”与“上期结余”的语义。候选唯一,不等于业务含义唯一。扫描件还需要先得到可用文字与位置;有文本层但内容错误的页面,也不能仅凭“能复制”就放行。 本文使用未旋转的合成页。真实页面有旋转时,应先说明查询框来自显示画面还是提取坐标,必要时转换;不要不分来源一律乘一个矩阵。保存来源页时也要区分从零开始的索引与面向人的页码,免得审校打开相邻页后以为证据不存在。 把关键数字留给翻译后的复核 文档翻译不只看句子通不通顺,金额、日期、数量这些关键内容也要逐项确认。 OmniDoc(产品入口) 提供多种文档的翻译入口,官网介绍的双语对照可作为复核入口;坐标定位脚本则适合保留原文证据、帮助定位问题区域。 先用有权上传的脱敏样本评估。译文变长后,内容位置可能合理变化,不能把“新旧坐标完全相同”当成翻译正确的硬条件。更有价值的检查是金额是否保留、币种是否明确、对应标签是否仍然指向同一业务含义。技术检查和人工复核分工清楚,比任何一句“完全保真”都更能帮助交付。 接下来保存一组失败样本 找出你实际模板中的零命中和多命中页面,分别记录原因,再决定是补区域规则、增加标签约束,还是转用另一条提取路线。不要用一个随意设定的百分比替所有业务决定切换条件。 接口字段参考: PyMuPDF Page.get_text 。本文合成样例不代表真实账单的准确率测试。 - 1 -
更新于 2026-10-08