从 PDF 中提取文本¶
你可以从 PDF 中提取文本:
from pypdf import PdfReader
reader = PdfReader("example.pdf")
page = reader.pages[0]
print(page.extract_text())
# 仅提取正向文本
print(page.extract_text(0))
# 提取正向和左旋 90 度的文本
print(page.extract_text((0, 90)))
# 以固定宽度格式提取文本,尽量贴合源 PDF 的渲染布局
print(page.extract_text(extraction_mode="layout"))
# 提取文本时保留水平位置,但去除多余的垂直空白行(删除空行和“仅空白”行)
print(page.extract_text(extraction_mode="layout", layout_mode_space_vertically=False))
# 调整水平间距
print(page.extract_text(extraction_mode="layout", layout_mode_scale_weight=1.0))
# 默认排除旋转文本,也可以包括旋转文本(如下所示)
print(page.extract_text(extraction_mode="layout", layout_mode_strip_rotated=False))
参考 extract_text()
了解更多细节。
使用访问器¶
你可以使用访问器函数控制需要处理和提取页面的哪个部分。访问器函数将在每个操作符或文本片段时被调用。
extract_text
方法中 visitor_text
参数接受的函数有以下五个参数:
text:当前文本(尽可能长,可能是完整的一行)。
user_matrix:用户坐标空间转换矩阵(也称为 CTM)。
tm_matrix:文本坐标空间的当前矩阵。
font-dictionary:字体字典的完整内容。
font-size:文本的字体大小(基于文本坐标空间)。
矩阵有六个参数:前四个参数定义旋转/缩放,后两个参数定义水平/垂直平移。
推荐使用 user_matrix
,因为它考虑了所有变换。
注意事项¶
根据 PDF 1.7 或 PDF 2.0 规范 §8.3.3,
user_matrix
适用于文本空间、图像空间、表单空间和模式空间。如果需要完整的从文本到用户空间的转换,可以使用函数
mult()
:txt2user = mult(tm, cm)
字体大小是原始文本大小,受到
user_matrix
的影响。
字体字典在字体未知的情况下可能为 None
。如果不为 None
,可能包含键 "/BaseFont",值为 "/Arial,Bold"。
注意:在复杂文档中,计算的位置信息可能较难处理(例如在多表单转换到页面用户空间的情况下)。
visitor_operand_before
参数接收的函数有以下四个参数:
操作符
操作数
当前变换矩阵
文本矩阵
示例 1:忽略页眉和页脚¶
以下示例读取此 PDF 文档的第四页的正文,忽略页眉(y > 720)和页脚(y < 50)。
from 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 = cm[5]
if 50 < y < 720:
parts.append(text)
page.extract_text(visitor_text=visitor_body)
text_body = "".join(parts)
print(text_body)
示例 2:将矩形和文本提取到 SVG 文件中¶
这种 SVG 导出可以帮助理解页面的布局和内容。
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", profile="tiny")
def visitor_svg_rect(op, args, cm, tm):
if op == b"re":
(x, y, w, h) = (args[i].as_numeric() for i in range(4))
dwg.add(dwg.rect((x, y), (w, h), stroke="red", fill_opacity=0.05))
def visitor_svg_text(text, cm, tm, fontDict, fontSize):
(x, y) = (cm[4], cm[5])
dwg.add(dwg.text(text, insert=(x, y), fill="blue"))
page.extract_text(
visitor_operand_before=visitor_svg_rect, visitor_text=visitor_svg_text
)
dwg.save()
生成的 SVG 坐标系是从底部开始的,因为 PDF 和 SVG 的坐标系不同。
注意:在复杂的 PDF 文档中,访问器函数提供的坐标可能会不准确。
为什么文本提取如此困难¶
不清晰的目标¶
从 PDF 中提取文本可能非常棘手。在许多情况下,如何定义期望的结果并不明确:
段落:提取的段落是否应该保留原 PDF 的换行,还是作为一个整体?
页码:是否应将页码包含在提取的文本中?
页眉和页脚:类似于页码——是否需要提取?
大纲:是否需要提取大纲?
格式:如果文本是 加粗 或 斜体,是否需要保留格式?
表格:是否应跳过表格提取?仅提取文本,还是以 Markdown 或 HTML 表格形式保留结构?如何处理合并单元格?
说明文字:是否需要提取图表或表格的说明文字?
连字:例如,Unicode 符号 U+FB00 表示两个小写字母 "f" 的单个符号“ff”。提取时应显示为单个符号还是分开为 "ff"?
SVG 图像:是否提取 SVG 图像中的文本部分?
数学公式:是否需要提取公式,包括下标和嵌套分数?
空白字符:3 厘米的垂直空白应提取为多少行?3 厘米的水平空白应提取为多少空格?是否需要提取制表符?
脚注:跨页提取文本时,脚注应显示在什么位置?
超链接和元数据:是否需要提取?应该以何种格式和位置呈现?
线性化:如果段落中间有浮动图形,是先完成段落还是将图形文本插入段落中间?
实现难度¶
有些问题在逻辑上可行,但由于 PDF 的存储方式,实际上很难实现:
表格:通常表格只是绝对定位的文本,最糟情况下每个字符都可能是绝对定位的,这使得识别列和行变得困难。
图像:有些 PDF 的文本是以图像形式存储的,这时无法复制文本。还有一些 PDF 包含图像和背景文本层,这通常发生在扫描文档时。尽管 OCR 技术已经相当先进,但仍有可能失败。pypdf 不是 OCR 软件,无法检测这些失败或从图像中提取文本。
对于 pypdf 能处理的文本提取问题,如果发现 Bug,请分享相关 PDF,以便改进!
缺失的语义层¶
PDF 文件格式的核心目标是实现预期的打印视觉效果,而非解析内容。PDF 文件中没有语义层。
具体而言,PDF 文件中不包含有关页眉、页脚、页码、表格和段落的信息。虽然人类可以通过启发式方法进行合理推测,但无法确定。
这是 PDF 文件格式的局限性,而非 pypdf 的问题。
可以通过对 PDF 文档应用机器学习来优化启发式方法,但这不是 pypdf 的职责。然而,pypdf 可以作为数据源,提取相关信息以供机器学习系统使用。
空白字符¶
PDF 格式是为打印设计的,而非机器可读的格式。PDF 文档中的文本是绝对定位的,这意味着每个字符都可能在页面上有自己的位置。
例如,文本:
This is a test document by Ethan Nelson.
可以表示为:
[(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
其中的数字表示垂直空间的调整。这种 PDF 内部表示方式使得正确解析空白字符变得非常困难。
更多信息:
Mark Stephens: 理解 PDF 文本对象,2010 年。
OCR 与文本提取¶
光学字符识别 (OCR) 是从图像中提取文本的过程。执行此任务的软件称为 OCR 软件。
tesseract OCR 引擎 是最著名的开源 OCR 软件之一。
pypdf 并非 OCR 软件。
数字生成与扫描的 PDF 文件¶
PDF 文档可以包含图像和文本。PDF 文件不会以语义相关的方式存储文本,而是以便于屏幕显示或打印的方式存储。这使得从 PDF 中提取文本变得困难。
如果扫描一个文档,生成的 PDF 通常包含扫描图像。扫描仪会运行 OCR 软件,并将识别的文本置于图像的背景中。这些由扫描仪 OCR 生成的文本可以通过 pypdf 提取。然而,建议直接使用 OCR 软件,因为错误可能会累积:
OCR 软件可能无法完美识别文本。
它将文本存储在不适合文本提取的格式中。
pypdf 解析时可能会出错。
因此,可以将 PDF 文件分为以下三种类型:
数字生成的 PDF 文件
由计算机生成,可能包含图像、文本、链接、大纲(即书签)、JavaScript 等。
放大时文本依然清晰。
扫描的 PDF 文件
包含扫描的页面,存储为 PDF 文件,实际上只是这些图像的容器。
无法复制文本,不包含链接、大纲或 JavaScript。
OCR 处理的 PDF 文件
扫描仪运行了 OCR 软件,将识别的文本置于图像背景中。
可以复制文本,但看起来仍然是扫描件。放大时,可以看到像素化的痕迹。
我们是否可以始终使用 OCR?¶
或许你会想,是否总是使用 OCR 软件更有意义?毕竟,如果 PDF 文件是数字生成的,可以直接将其渲染为图像。
我并不建议这样做。
像 pypdf 这样的文本提取软件可以利用 PDF 中比图像更多的信息,例如字体、编码、字符间的典型距离等。
这意味着,在容易混淆的字符(如 oO0ö
)方面,pypdf 具有明显优势。
pypdf 不会混淆字符——它只读取文件中存储的内容。
此外,在处理罕见字符(如 🤰)时,pypdf 也更具优势。OCR 软件通常无法正确识别表情符号。
防止文本提取的尝试¶
如果分享 PDF 的人希望防止文本提取,有以下几种方法可以实现:
将 PDF 内容存储为图像。
然而,如果人们仍然能够阅读文档,则无法完全阻止文本提取。
在最坏的情况下,人们可以截屏、打印、扫描,然后通过 OCR 软件识别文本。