pypdf 文本提取完全指南:extract_text 参数详解、visitor 回调与布局模式实战
pypdf 文本提取完全指南extract_text 参数详解、visitor 回调与布局模式实战【免费下载链接】pypdfA pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files项目地址: https://gitcode.com/GitHub_Trending/py/pypdf导读本文以 pypdf 官方文档 docs/user/extract-text.md 为核心骨架系统讲解从 PDF 页面提取文本的完整技术路径从最基础的PageObject.extract_text()调用到按文本方向过滤、visitor 回调函数精准定位与提取、layout 布局模式还原版面再到为什么 PDF 文本提取如此困难的底层原理剖析。读完本文你将掌握 pypdf 文本提取的全部参数与回调机制能够编写忽略页眉页脚导出 SVG 结构等实战脚本并理解 OCR 与文本提取的边界。一、快速上手从页面中提取文本pypdf 将文本提取能力封装在PageObject.extract_text()中最基础的用法只需三步打开 PDF、取页面、提取文本。from pypdf import PdfReader reader PdfReader(test Orient.pdf) page reader.pages[0] print(page.extract_text())这段代码输出的就是页面中所有文本的拼接结果。从源码看extract_text的实现位于 pypdf/_page.py其核心逻辑是定位内容流中所有文本绘制命令并按其出现顺序提取文本。需要注意的是官方文档明确提醒不要依赖输出文本的顺序因为该函数后续可能变得更复杂而改变顺序。它还特别说明阿拉伯语和希伯来语会以正确顺序RTL被提取出来。按文本方向过滤提取PDF 页面中的文字可以有四种方向0°正立、90°左转、180°倒置、270°右转。extract_text的第一个位置参数orientations用来声明只提取哪些方向的文字默认值为(0, 90, 180, 270)即全部提取# 只提取正立方向的文字 print(page.extract_text(0)) # 提取正立 左转 90° 的文字 print(page.extract_text((0, 90)))在示例输出中可以清楚看到这种过滤效果只提取方向 0 时输出里只剩下顶部 (T) 和底部 (B) 的横排文字加入方向 90 后(L) 的竖排文字也出现了。从源码 pypdf/_page.py 可以看出传入单个整数会被自动包装成元组再进入内部逻辑所以extract_text(0)与extract_text((0,))等价。其余核心参数同样定义在 pypdf/_page.py还包括space_width当字体中无法提取空格宽度时强制使用的默认空格宽度默认200.0extraction_modeplain传统模式默认或layout布局模式visitor_operand_before/visitor_operand_after/visitor_text三个 visitor 回调详见下文。二、layout 布局模式还原 PDF 版面的固定宽度文本普通模式输出的是一段连续的文本流丢失了原始排版。若希望输出尽可能贴近 PDF 渲染版面的结果可以使用extraction_modelayout# 固定宽度格式输出高度还原源 PDF 的渲染布局 print(page.extract_text(extraction_modelayout)) # 保留水平位置但移除空白行与仅空白行 print(page.extract_text(extraction_modelayout, layout_mode_space_verticallyFalse)) # 调整水平间距加权平均字符宽度的缩放系数 print(page.extract_text(extraction_modelayout, layout_mode_scale_weight1.0)) # 默认排除相对页面旋转的文字设为 False 则包含它们 print(page.extract_text(extraction_modelayout, layout_mode_strip_rotatedFalse))layout 模式是extract_text通过**kwargs透传的可用的全部参数及其默认值如下见 pypdf/_page.pykwargs 参数类型默认值作用layout_mode_space_verticallyboolTrue根据 y 坐标距离 字体高度推断空行并插入设为False会移除空白行与仅含空白的行layout_mode_scale_weightfloat1.25计算加权平均字符宽度时对字符串长度的乘数值越大水平间距越宽layout_mode_strip_rotatedboolTruelayout 模式本身不支持旋转文本设为False会强行包含旋转文本但版面会退化并产生警告layout_mode_debug_pathPath | NoneNone指向一个目录用于输出fonts.json、tjs.json、bts.json、bt_groups.json四个调试文件见下layout_mode_font_height_weightfloat1推断空行时对字体高度的乘数从源码结构看layout 模式由独立模块实现入口在 pypdf/_page.py 的_layout_mode_text()核心算法位于 pypdf/_text_extraction/_layout_mode/ 下的三个文件中——_fixed_width_page.py把文本按 y 坐标分组为行、计算固定字符宽度并输出等宽页面、_text_state_manager.py管理q/Q与BT/ET的变换栈与_text_state_params.py文本状态参数。其大致流水线是收集页面资源中的字体_layout_mode_fonts沿/Parent链向上遍历见 pypdf/_page.py解析内容流把Tj/TJ//等文本显示操作按BT/ET分组为行text_show_operations按渲染出的 y 坐标把行归组y_coordinate_groups计算加权平均字符宽度fixed_char_width生成等宽输出fixed_width_page。上述步骤的常量与边界处理如WHITESPACE_LIMIT 10_000、NEWLINE_LIMIT 1_000定义在 pypdf/_text_extraction/_layout_mode/_fixed_width_page.py。关于 layout 模式的两个重要限制忽略普通模式的参数在 layout 模式下orientations、space_width以及三个 visitor 参数会被静默忽略若传入了 visitor会输出一条 warning Argument xxx is ignored in layout mode见 pypdf/_page.py不支持旋转文本默认layout_mode_strip_rotatedTrue会丢弃旋转文本以保证版面整洁如果你需要包含旋转文字请显式设为False并接受版面退化。layout 模式的行为在测试中有充分验证例如 tests/test_text_extraction.py 中的test_layout_mode_uncommon_operators覆盖了Tc、Tz、Ts、、、TD、TL、Tw等非常用操作符test_layout_mode_character_spacing_per_glyph则验证了字符间距Tc是按每个字形推进文本矩阵而非按整个字符串。内存与性能提示官方文档给出了一条重要提醒提取一页文本需要解析整个内容流这可能消耗大量内存——曾观察到约 300 MB 的未压缩内容流需要高达 10 GB RAM虽然这种情况不常发生。因此在大型文档应用中建议先用len(page.get_contents().get_data())检查内容流大小再决定是否提取以避免 OOM内存溢出错误。可选的调试输出如果你在开发中需要排查 layout 模式的行分组或字符宽度问题可以传入layout_mode_debug_path指向一个目录pypdf 会生成四个 JSON 文件见 pypdf/_page.pyfonts.json_layout_mode_fonts的输出tjs.json每个文本显示操作及其对应的变换矩阵bts.json按BT/ET分组、左对齐后的文本显示操作bt_groups.json按渲染 y 坐标即行分组后的BT/ET操作。三、visitor 回调按需处理文本片段与操作符visitor 机制的用途是控制你究竟要处理页面的哪一部分pypdf 会在解析内容流时为每个操作符或每个文本片段回调你提供的函数。visitor_text处理每个文本片段visitor_text回调在每次提取到一段文本时被调用它有五个参数见 pypdf/_page.pytext当前文本尽可能长最多可到一整行user_matrix源码参数名cm从用户坐标空间又称 CTMCurrent Transformation Matrix变换的当前矩阵tm_matrix源码参数名tm文本坐标空间的当前矩阵font_dictionary完整字体字典未知字体时为None非空时可能包含如/BaseFont→/Arial,Bold这样的键值font_size文本坐标空间中的字号原始字号会受user_matrix影响。关于矩阵需要理解两点矩阵存储 6 个参数前四个构成旋转/缩放矩阵最后两个是平移量水平/垂直推荐使用user_matrix即cm因为它已综合了所有变换。PDF 规范PDF 1.7 / PDF 2.0 的 §8.3.3规定用户矩阵同时适用于文本空间、图像空间、表单空间与图案空间。若你想得到文本 → 用户空间的完整变换可以直接调用 pypdf 提供的矩阵乘法函数mult(tm, cm)得到txt2user——该函数实现在 pypdf/_text_extraction/init.py就是标准的 3×3 仿射矩阵乘法6 参数紧凑表示。注意Caveat在复杂文档中例如从多个表单移动到页面用户空间计算出的位置可能难以确定。visitor_operand_before处理每个操作符visitor_operand_before回调在每个操作符执行前被调用有四个参数operator操作符字节串、operand-arguments操作数、当前变换矩阵和文本矩阵。它常被用来捕捉re矩形之类的图形操作以实现版面结构分析。示例 1忽略页眉和页脚下面的示例读取GeoBase_NHNC1_Data_Model_UML_EN.pdf的第 4 页但忽略页眉y 720和页脚y 50且保留换行y 0from pypdf import PdfReader reader PdfReader(GeoBase_NHNC1_Data_Model_UML_EN.pdf) page reader.pages[3] parts [] def visitor_body(text, cm, tm, font_dict, font_size): y tm[5] if 50 y 720 or y 0: parts.append(text) page.extract_text(visitor_textvisitor_body) text_body .join(parts) print(text_body)这里的关键是文本矩阵tm的第 6 个元素索引 5就是垂直平移量即当前文本的 y 坐标。通过简单的坐标范围判断就能精准过滤掉页眉页脚输出干净的目录正文。示例 2把矩形与文本导出为 SVG下面的示例将GeoBase_NHNC1_Data_Model_UML_EN.pdf的第 3 页转换为 SVG 文件用visitor_operand_before捕捉re矩形操作符、用visitor_text捕捉文本位置from pypdf import PdfReader import svgwrite reader PdfReader(GeoBase_NHNC1_Data_Model_UML_EN.pdf) page reader.pages[2] dwg svgwrite.Drawing(GeoBase_test.svg, profiletiny) def visitor_svg_rect(op, args, cm, tm): if op bre: (x, y, w, h) (args[i].as_numeric() for i in range(4)) dwg.add(dwg.rect((x, y), (w, h), strokered, fill_opacity0.05)) def visitor_svg_text(text, cm, tm, font_dict, font_size): (x, y) (cm[4], cm[5]) dwg.add(dwg.text(text, insert(x, y), fillblue)) page.extract_text( visitor_operand_beforevisitor_svg_rect, visitor_textvisitor_svg_text ) dwg.save()两点说明生成的 SVG 是**自底向上bottom-up**的因为 PDF 与 SVG 的坐标系方向不同在复杂 PDF 文档中visitor 函数拿到的坐标可能不正确这一点官方文档也坦诚承认。注意visitor 参数在 layout 模式下会被忽略见上文且该示例依赖第三方库svgwrite属于锦上添花的扩展用法。四、为什么文本提取如此困难4.1 目标本身不明确提取 PDF 文本难的第一个层面是在很多场景下根本没有唯一正确的答案。官方文档列举了 14 类典型争议段落段落换行应保留原 PDF 的断行位置还是合并为一段连续文本页码是否应包含在提取结果中页眉页脚同页码一样是否提取大纲/书签Outlines是否应该被提取格式粗体、斜体信息是否要体现在输出中表格跳过表格只提取文字用类 Markdown 的方式呈现边框还是输出为 HTML 表格结构合并单元格如何处理题注图片和表格的题注是否包含连字LigaturesUnicode 符号 UFB00 是一个表示两个小写 f 的单一符号ff应解析为 ff 还是两个 ASCII 字符 ffSVG 图片中的文字是否提取数学公式公式带上下标、嵌套分式如何提取空白字符3 cm 垂直空白应输出几个换行3 cm 水平空白应输出几个空格何时用 Tab、何时用空格脚注跨页提取时脚注应出现在哪里超链接与元数据是否提取放在什么位置、用什么格式线性化段落中间插入浮动图时是先完成段落还是把图的文字插在中间4.2 缺少语义层PDF 文件格式的设计目标是在打印时产生预期的视觉效果而不是为了机器解析。PDF 文件中没有语义层没有任何信息标明哪部分是页眉、页脚、页码、表格或段落。视觉呈现是存在的人们可以设计启发式规则去做有根据的猜测但永远无法百分之百确定。这是 PDF 文件格式本身的缺陷而非 pypdf 的缺陷。官方文档同时指出可以对 PDF 应用机器学习来构造更好的启发式规则但这不会成为 pypdf 的一部分不过 pypdf 完全可以作为数据源为这样的机器学习系统提供相关输入信息。4.3 空白字符的绝对定位问题PDF 面向打印其中的文本是绝对定位的——理论上每个字符都可以被独立定位在页面任意坐标上。官方文档举了一个生动的例子一行文本This is a test document by Ethan Nelson.在 PDF 内容流中可能被存储为[(This is a )9(te)-3(st)9( do)-4(cu)13(m)-4(en)12(t )-3(b)3(y)-3( )9(Et)-2(h)3(an)4( Nels)13(o)-5(n)3(.)] TJ其中夹杂的数字是对垂直/水平间距的微调。这种存储方式使得保证正确的空白字符变得极其困难。五、OCR 与文本提取的边界5.1 三种 PDF 类型按内容来源PDF 文档大致可分三类数字化原生 PDFDigitally born在计算机上直接创建的 PDF可包含图片、文字、链接、书签大纲、JavaScript 等放大很多倍后文字依然清晰。扫描版 PDFScanned整页扫描图装入 PDF文件只是图片的容器无法复制文字没有链接、书签、JavaScript。经 OCR 的 PDFOCRed扫描仪运行 OCR 软件把识别出的文字放到图片背景层可以复制文字但外观仍是扫描件放大后能看到像素。5.2 pypdf 不是 OCR 软件需要明确pypdf 不是 OCR 软件它永远不会从图片中提取文字。如果页面只是一张扫描图例如扫描件提取结果可能为空或近乎为空此时应改用 OCR 软件如 Tesseract OCR从图像中识别文字。对扫描后经 OCR 处理的 PDFpypdf 可以提取出扫描仪 OCR 软件放入背景的文字层但官方文档建议这类文件直接使用 OCR 软件处理因为错误会累积OCR 软件识别未必完美随后它又以并非为文本提取而设计的格式存储文字pypdf 解析时还可能再犯错。5.3 能否一律用 OCR既然 OCR 如此普及是否可以对所有 PDF 一律走 OCR官方文档给出的建议是不推荐。理由在于文本提取软件如 pypdf能利用的信息远多于一张渲染图——它能了解字体、编码、典型字符间距等。因此面对易混淆字符如oO0öpypdf 永远不会混淆字符它只是忠实读取文件中的内容OCR 则可能认错。面对罕见字符如表情符号 OCR 往往无法正确识别而 pypdf 可以直接读取文件中的 Unicode 值。六、防止文本提取的常见手段如果 PDF 发布者想阻止他人提取文本常见手段有两种把 PDF 内容存成图片——这是最彻底的方式因为 pypdf 无法从纯图片提取文字使用打乱字体scrambled font——通过字体映射把显示字符与内部编码错开。但官方文档也明确指出只要文档仍需要被阅读文本提取就不可能被完全阻止。最极端情况下人们可以截图、打印、再扫描最后对图像运行 OCR。七、参考资料与延伸阅读围绕本文主题可在当前仓库中继续深入官方文档原文docs/user/extract-text.md含完整的testsetup与预期输出可作为可运行示例验证API 参考PageObject.extract_text实现源码extract_text本体见 pypdf/_page.pylayout 模式见 pypdf/_text_extraction/_layout_mode/_fixed_width_page.py矩阵乘法工具函数mult与 RTL 自定义字符范围set_custom_rtl用于自定义阿拉伯/希伯来等 RTL 文字范围pypdf/_text_extraction/init.py测试用例layout 模式操作符覆盖与回归测试见 tests/test_text_extraction.py可用于学习各参数的预期行为。如果你在文本提取中遇到 pypdf 的 bug官方文档呼吁把出问题的 PDF 分享给项目维护者以便持续改进——毕竟忠实而健壮地从为打印而设计的 PDF 中提取文本是一项仍在不断打磨的工作。【免费下载链接】pypdfA pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files项目地址: https://gitcode.com/GitHub_Trending/py/pypdf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考