PDF翻译时页眉页脚总在捣乱?跨页重复文本块的检测与过滤实测

前言

做 PDF 批量翻译流水线时,有个问题几乎必然遇到:页眉页脚。一份 300 页的文档,页眉"XXX Annual Report"重复 300 次,每页还带一个页码。这些文字喂给翻译引擎,轻则浪费 token 和额度,重则污染译文------页码被翻成奇怪的字、版权行被断句错乱,回填后版面还会溢出。所以翻译前识别并过滤页眉页脚,是流水线的标配步骤。

本文在 3 份真实 PDF 上实测两条技术路线:固定 y 阈值过滤 vs 跨页重复内容识别,并把识别算法从 v1 迭代到 v3 的全过程(包括两次漏检)完整记录下来。

在展开之前,先说清楚这个问题的代价结构。页眉页脚对翻译流水线的影响是三个层面的:成本层面 ,每页重复一次的固定文字会成倍放大翻译调用量,实测中文报告里这部分占全文 14.76%,300 页的文档就是几十页的无效翻译;质量层面 ,页码、日期、版权符号没有上下文,机翻结果往往诡异,回填后和原文混在一起很难清理;排版层面,译文长度天然膨胀,页眉页脚回填后极容易溢出边距,把好端端的版式挤乱。所以这个步骤值得认真做,而不是随手用个正则糊弄。

实测样本与环境

  • Python 3.13.12,PyMuPDF 1.28.2(pip install pymupdf)
  • 样本 1:中文行业调研报告,30 页,正文 17486 字符
  • 样本 2:英文打印机快速安装手册,36 页,正文 38427 字符
  • 样本 3:合成年度报告,300 页,正文 248811 字符

思路:用 page.get_text("dict") 拿到每页文本块的坐标和内容,再做判定。

方法A:固定 y 阈值------简单但会误伤

最直觉的方案:页面顶部 8% 和底部 8% 带内的文本块,一律当页眉页脚删掉。实测结果:

样本 标记块数 误伤正文块 误伤文字占标记文字
中文报告(30页) 61 3 1.7%
英文手册(36页) 73 40 95.6%
合成报告(300页) 300 0 0.0%

英文手册翻车翻得很彻底:它的版式顶部是"步骤编号 + 安装步骤"的表格区,大量正文块就落在顶部带里。固定 y 阈值对"页眉页脚固定在边带"的文档有效,但无法区分"边带里的页眉"和"边带里的正文"------版式一变就崩。

方法B:跨页重复识别------三个版本的迭代

v1 严格版:页码页脚漏检

核心思想:把每页文本块做归一化(去空白、数字掩码为 #),统计每个归一化文本覆盖的页数,覆盖 ≥60% 页数且每次出现都在边带的,判为页眉页脚。

中文报告上表现完美:检出 2 条------页眉"中国人工智能框架市场调研报告 01"(29 次)和版权页脚"© 2022 Omdia..."(29 次),两者重复文字占全文 14.76%。300 页合成报告也检出页眉"Section 1: Annual Report Extract"(300 次,占 4.06%)。

但英文手册检出 0 条。它的页脚明明是每页一个页码"1"~"36"。

坑:正文数字与页码撞车

诊断发现:页码"1"、"2"..."36" 归一化后全是 #,而正文里的孤立数字块(步骤编号"1"、"4"、"23")归一化后也是 # 。统计 # 的全部出现:53 次,其中 33 次在底部边带(y0/页高=0.958,位置纹丝不动),20 次散在正文里。v1 要求"每次出现都在边带",20 次正文出现直接把页码页脚否决了。

v2 多数表决:仍不够

改成"≥90% 的出现在边带即可":33/53 = 0.623 < 0.9,还是漏。多数表决的阈值难选------放宽到 0.6,页码勉强过线,但别的文档里正文数字占比更高时又会误判。

v3 位置聚类:最终方案

换一个更强的信号:页眉页脚不仅文本重复,位置也逐页固定。把文本块按(归一化文本,y0 坐标按 2pt 取整)聚类,只要有一个簇满足"出现次数 ≥ 60% 页数"且"该簇位置在边带",就判为页眉页脚。

v3 在三份样本上全部检出:中文报告 2 条、英文手册页码页脚(33 次 @ y0/h=0.958)、合成报告页眉(300 次)。正文的孤立数字因为 y 坐标散乱,聚不成大簇,天然被排除。

几个阈值参数的选取值得单独说明。聚类容差取 2pt :实测中同一页眉逐页的 y 坐标抖动通常在 0.5pt 以内(浮点渲染误差),2pt 足以吸收抖动又不会把上下相邻的两个不同元素合并;如果文档经过旋转或裁切处理,可以适当放大。覆盖阈值取 60% 页数 :封面、目录、章节扉页通常不带页眉页脚,要求 100% 覆盖会漏检,60% 留足了余量;但对 3~5 页的短文档,这个阈值的统计意义会变弱,样本量太小时建议降阈值或人工确认。120 字符上限是防误伤护栏:正文里偶尔有跨页重复的引用语或免责声明块,这类内容通常较长且不固定在边带,长度和位置双重约束基本能把它们挡在判定之外。

消融实验:数字掩码有多重要

把数字掩码关掉重跑:中文报告只能检出 1 条(页码"01"变化的部分聚不上),英文手册 0 条,合成报告 0 条。不做数字掩码,一切含页码的页眉页脚都识别不出来。这一步是整个方案成立的前提。

警示:过滤时要按"文本+位置"匹配

v3 还有个衍生坑:判定出页脚 # 之后,如果按文本全局过滤 (把所有归一化为 # 的块都删掉),正文里的步骤编号"1"、"4"、"23"会被一起删掉------这就是把方法 A 的误伤换了个形式又引进来。正确做法是删除时匹配(文本,位置簇)这个二元组:只删落在 y0≈页脚位置的那 33 个块,正文数字一个不动。

核心代码

python 复制代码
import pymupdf, re
from collections import defaultdict

def norm(t):                      # 归一化: 去空白 + 数字掩码
    return re.sub(r"\d+", "#", re.sub(r"\s+", "", t))

def detect_header_footer(pdf_path, top=0.08, bottom=0.08,
                         rep=0.6, tol=2.0):
    doc = pymupdf.open(pdf_path)
    n, clusters = len(doc), defaultdict(list)
    for pno, page in enumerate(doc):
        h = page.rect.height
        for b in page.get_text("dict")["blocks"]:
            if b["type"] != 0: continue
            txt = "".join(s["text"] for l in b["lines"]
                          for s in l["spans"]).strip()
            if not txt: continue
            key = (norm(txt), round(b["bbox"][1] / tol))   # 文本+位置聚类
            clusters[key].append((b["bbox"][1], h, txt))
    hf = {}
    for (nt, _), occ in clusters.items():
        y0, h, raw = occ[0]
        if (len(occ) >= rep * n and len(raw) < 120 and
                (y0 <= h * top or y0 >= h * (1 - bottom))):
            hf[nt] = (len(occ), raw)
    doc.close()
    return hf

# 删除时按(文本+位置)匹配, 不要按文本全局匹配

实测中该函数处理 300 页文档耗时在亚秒级,对流水线性能无压力。

总结

方案 中文报告 英文手册 300页报告 结论
A 固定y阈值 可用(误伤1.7%) 误伤95.6% 可用 版式敏感
B v1 严格边带 2条/14.76% 0条(漏) 1条 页码撞车
B v2 多数表决0.9 2条 0条(漏) 1条 阈值难选
B v3 位置聚类 2条 1条 1条 推荐

三条经验:① 页眉页脚的本质是"内容重复 + 位置固定",两个信号都要用;② 数字掩码是页码识别的前提;③ 过滤时按(文本+位置)匹配,防止正文数字被误删。这套方案可直接嵌入 PDF 翻译预处理流水线,也适用于其他需要清洗整页重复文字的场景。

标签:PDF翻译、PyMuPDF、Python、文本提取、效率工具

相关推荐
徐健峰1 小时前
JEV 1.13 接入教程:Python 调用 Decisions API,实现分类、评分与 Agent 路由
人工智能
CubeSandbox1 小时前
沙箱是选项,不是标配:花椒 Agent 平台的架构思考与 Cube 实践
大数据·人工智能·架构
晴空蓝天1 小时前
MDC traceId 全链路日志追踪:Spring Boot 3.5 里把日志串成一条线
java·spring boot·后端·python
Rocky Ding*1 小时前
一文读懂Qwen-Audio-3.1核心基础知识:从语音识别到可控声景与实时Agent
论文阅读·人工智能·深度学习·机器学习·aigc·ai-native·qwen-audio
桃西西呀1 小时前
Laya 源码级原理拆解之四:路由检测与服务部署
人工智能·llm·ai编程
努力努力再努力wz1 小时前
【边缘计算入门系列】从“在哪里算”到“怎么算”:一文建立边缘计算、算子、计算图与 Tensor 的底层心智模型
人工智能·docker·边缘计算
黄啊码1 小时前
【黄啊码】一个陪伴类的 Agent 产品,凭什么我觉得它做得好?
人工智能
leoZ2311 小时前
第 34 篇 Copilot 与嵌入式 AI:把能力缝进工作流
人工智能·大模型·copilot·agent
L@ncor1 小时前
第六章可能出现的问题:依赖冲突与成本乘法
python·依赖管理·langgraph·agentscope·避坑