附录 1:文本提取的详细信息#
本章提供了关于PyMuPDF文本提取方法的背景知识。
感兴趣的信息是
他们提供什么?
它们意味着什么(处理时间/数据大小)?
文本页面的一般结构#
TextPage 是 (Py-) MuPDF 的类之一。它通常在使用 Page 文本提取方法时后台创建(和销毁),但也可以直接使用并作为持久化对象。除了它的名字所暗示的,图像也可以作为文本页的一部分:
<page>
<text block>
<line>
<span>
<char>
<image block>
<img>
A 文本页面 由块(= 大致相当于段落)组成。
A 块由行及其字符或一张图片组成。
A line 由跨度组成。
A span 由具有相同字体属性的相邻字符组成:名称、大小、标志和颜色。
纯文本#
函数 TextPage.extractText() (或 Page.get_text(“text”)) 提取页面的原始文本按原始顺序,如文档创建者所指定。
一个示例输出:
>>> print(page.get_text("text"))
Some text on first page.
注意
输出可能与习惯的“自然”阅读顺序不相等。然而,您可以通过执行 page.get_text("text", sort=True) 来请求按照“从左上到右下”的方案进行重新排序。
区块#
函数 TextPage.extractBLOCKS() (或 Page.get_text(“blocks”)) 提取页面的文本块作为项目列表,如:
(x0, y0, x1, y1, "lines in block", block_no, block_type)
前四项是块的边界框的浮点坐标。每个块中的行由换行符连接。
这是一个高速方法,默认情况下还会提取图像元信息:每个图像作为一个块出现,并包含一行文本,其中包含元信息。图像本身不显示。
与上面的简单文本输出一样,sort 参数也可以用于获取阅读顺序。
示例输出:
>>> print(page.get_text("blocks", sort=False))
[(50.0, 88.17500305175781, 166.1709747314453, 103.28900146484375,
'Some text on first page.', 0, 0)]
单词#
函数 TextPage.extractWORDS() (或 Page.get_text(“words”)) 提取页面的文本 words 作为一个项目列表,如:
(x0, y0, x1, y1, "word", block_no, line_no, word_no)
前四个条目是单词边界框的浮动坐标。最后三个整数提供了有关单词位置的更多信息。
这是一个高速方法。与之前的方法一样,参数 sort=True 将会重新排列单词。
示例输出:
>>> for word in page.get_text("words", sort=False):
print(word)
(50.0, 88.17500305175781, 78.73200225830078, 103.28900146484375,
'Some', 0, 0, 0)
(81.79000091552734, 88.17500305175781, 99.5219955444336, 103.28900146484375,
'text', 0, 0, 1)
(102.57999420166016, 88.17500305175781, 114.8119888305664, 103.28900146484375,
'on', 0, 0, 2)
(117.86998748779297, 88.17500305175781, 135.5909881591797, 103.28900146484375,
'first', 0, 0, 3)
(138.64898681640625, 88.17500305175781, 166.1709747314453, 103.28900146484375,
'page.', 0, 0, 4)
HTML#
TextPage.extractHTML()(或 Page.get_text(“html”) 的输出完全反映了页面的 TextPage 结构 – 与下面的 DICT / JSON 类似。这包括图像、字体信息和文本位置。如果用 HTML 头部和尾部代码包装,它可以很容易在互联网浏览器中显示。我们上面的例子:
>>> for line in page.get_text("html").splitlines():
print(line)
<div id="page0" style="position:relative;width:300pt;height:350pt;
background-color:white">
<p style="position:absolute;white-space:pre;margin:0;padding:0;top:88pt;
left:50pt"><span style="font-family:Helvetica,sans-serif;
font-size:11pt">Some text on first page.</span></p>
</div>
控制HTML输出的质量#
虽然在 MuPDF v1.12.0 中 HTML 输出已经有了很大改善,但它还不是完全没有错误:我们在 字体支持 和 图像定位 的领域发现了问题。
HTML文本包含原始文档中使用的字体的引用。如果浏览器不知道这些字体(这种可能性很小!),它将用其他字体替换它们;结果可能会显得很尴尬。这个问题在不同的浏览器之间变化很大——在我的Windows机器上,MS Edge工作得很好,而Firefox看起来则很糟糕。
对于结构复杂的PDF文件,图像可能未正确定位和/或调整大小。似乎在旋转页面和页面的情况下,会出现各种可能的页面边界框(bbox)变体不一致的情况(例如,MediaBox != CropBox)。我们还不知道如何解决这个问题 - 我们在MuPDF的网站上提交了一个bug。
要解决字体问题,您可以使用一个简单的实用程序脚本扫描HTML文件并替换字体引用。这里有一个小例子,替换所有字体为PDF Base 14 Fonts之一:衬线字体将变为“Times”,无衬线字体“Helvetica”,等宽字体将变为“Courier”。它们各自的“粗体”、“斜体”等变体希望能被您的浏览器正确处理:
import sys
filename = sys.argv[1]
otext = open(filename).read() # original html text string
pos1 = 0 # search start poition
font_serif = "font-family:Times" # enter ...
font_sans = "font-family:Helvetica" # ... your choices ...
font_mono = "font-family:Courier" # ... here
found_one = False # true if search successful
while True:
pos0 = otext.find("font-family:", pos1) # start of a font spec
if pos0 < 0: # none found - we are done
break
pos1 = otext.find(";", pos0) # end of font spec
test = otext[pos0 : pos1] # complete font spec string
testn = "" # the new font spec string
if test.endswith(",serif"): # font with serifs?
testn = font_serif # use Times instead
elif test.endswith(",sans-serif"): # sans serifs font?
testn = font_sans # use Helvetica
elif test.endswith(",monospace"): # monospaced font?
testn = font_mono # becomes Courier
if testn != "": # any of the above found?
otext = otext.replace(test, testn) # change the source
found_one = True
pos1 = 0 # start over
if found_one:
ofile = open(filename + ".html", "w")
ofile.write(otext)
ofile.close()
else:
print("Warning: could not find any font specs!")
字典(或 JSON)#
TextPage.extractDICT()(或 Page.get_text(“dict”, sort=False))的输出完全反映了TextPage的结构,并为每个块、行和跨度提供了图像内容和位置信息(bbox – 像素单位的边界框)。图像以bytes格式存储用于DICT输出,以base64编码的字符串形式存储用于JSON输出。
要查看字典结构的可视化,请查看 字典输出的结构。
这看起来是这样的:
{
"width": 300.0,
"height": 350.0,
"blocks": [{
"type": 0,
"bbox": (50.0, 88.17500305175781, 166.1709747314453, 103.28900146484375),
"lines": ({
"wmode": 0,
"dir": (1.0, 0.0),
"bbox": (50.0, 88.17500305175781, 166.1709747314453, 103.28900146484375),
"spans": ({
"size": 11.0,
"flags": 0,
"font": "Helvetica",
"color": 0,
"origin": (50.0, 100.0),
"text": "Some text on first page.",
"bbox": (50.0, 88.17500305175781, 166.1709747314453, 103.28900146484375)
})
}]
}]
}
原始字典 (或 原始 JSON)#
TextPage.extractRAWDICT() (或 Page.get_text(“rawdict”, sort=False)) 是一个 DICT 的信息超集,并且详细级别更深入一步。它的外观与上面完全相同,除了 “text” 项 (string) 在 spans 中被列表 “chars” 替代。每个 “chars” 条目是一个字符 dict。例如,这里是您将在上面的 “text”: “黑色文字。” 项的位置看到的内容:
"chars": [{
"origin": (50.0, 100.0),
"bbox": (50.0, 88.17500305175781, 57.336997985839844, 103.28900146484375),
"c": "S"
}, {
"origin": (57.33700180053711, 100.0),
"bbox": (57.33700180053711, 88.17500305175781, 63.4530029296875, 103.28900146484375),
"c": "o"
}, {
"origin": (63.4530029296875, 100.0),
"bbox": (63.4530029296875, 88.17500305175781, 72.61600494384766, 103.28900146484375),
"c": "m"
}, {
"origin": (72.61600494384766, 100.0),
"bbox": (72.61600494384766, 88.17500305175781, 78.73200225830078, 103.28900146484375),
"c": "e"
}, {
"origin": (78.73200225830078, 100.0),
"bbox": (78.73200225830078, 88.17500305175781, 81.79000091552734, 103.28900146484375),
"c": " "
< ... deleted ... >
}, {
"origin": (163.11297607421875, 100.0),
"bbox": (163.11297607421875, 88.17500305175781, 166.1709747314453, 103.28900146484375),
"c": "."
}],
XML#
方法 TextPage.extractXML() (或 Page.get_text(“xml”)) 提取文本 (无图像),详尽程度为 RAWDICT:
>>> for line in page.get_text("xml").splitlines():
print(line)
<page id="page0" width="300" height="350">
<block bbox="50 88.175 166.17098 103.289">
<line bbox="50 88.175 166.17098 103.289" wmode="0" dir="1 0">
<font name="Helvetica" size="11">
<char quad="50 88.175 57.336999 88.175 50 103.289 57.336999 103.289" x="50"
y="100" color="#000000" c="S"/>
<char quad="57.337 88.175 63.453004 88.175 57.337 103.289 63.453004 103.289" x="57.337"
y="100" color="#000000" c="o"/>
<char quad="63.453004 88.175 72.616008 88.175 63.453004 103.289 72.616008 103.289" x="63.453004"
y="100" color="#000000" c="m"/>
<char quad="72.616008 88.175 78.732 88.175 72.616008 103.289 78.732 103.289" x="72.616008"
y="100" color="#000000" c="e"/>
<char quad="78.732 88.175 81.79 88.175 78.732 103.289 81.79 103.289" x="78.732"
y="100" color="#000000" c=" "/>
... deleted ...
<char quad="163.11298 88.175 166.17098 88.175 163.11298 103.289 166.17098 103.289" x="163.11298"
y="100" color="#000000" c="."/>
</font>
</line>
</block>
</page>
注意
我们已经成功测试了 lxml 来解释此输出。
XHTML#
TextPage.extractXHTML() (或 Page.get_text(“xhtml”))是TEXT的一种变体,但采用HTML格式,包含纯文本和图像(“语义”输出):
<div id="page0">
<p>Some text on first page.</p>
</div>
文本提取标志默认值#
版本 1.16.2 新增:方法
Page.get_text()支持一个关键字参数 flags (int) 来控制提取数据的数量和质量。下表显示了每种提取变体的默认设置(省略或为 None 的 flags 参数)。如果您指定的 flags 的值不是 None,请注意您必须设置 所有期望的 选项。各位设置的描述可以在 文本提取标志 中找到。在v1.19.6中:下表中的默认组合现在可作为Python常量使用:
TEXTFLAGS_TEXT,TEXTFLAGS_WORDS,TEXTFLAGS_BLOCKS,TEXTFLAGS_DICT,TEXTFLAGS_RAWDICT,TEXTFLAGS_HTML,TEXTFLAGS_XHTML,TEXTFLAGS_XML, 和TEXTFLAGS_SEARCH。您现在可以轻松修改默认标志,例如:在 “块” 输出中包含图像:
flags = TEXTFLAGS_BLOCKS | TEXT_PRESERVE_IMAGES从“dict”输出中排除 图像:
flags = TEXTFLAGS_DICT & ~TEXT_PRESERVE_IMAGES在文本搜索中设置 去掉连字符:
flags = TEXTFLAGS_SEARCH & ~TEXT_DEHYPHENATE
指标 |
文本 |
html |
xhtml |
xml |
字典 |
原始字典 |
单词 |
块 |
搜索 |
|
|---|---|---|---|---|---|---|---|---|---|---|
保留连字 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
|
保留空格 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
|
保留图像 |
不适用 |
1 |
1 |
不适用 |
1 |
1 |
不适用 |
0 |
0 |
|
抑制空格 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
|
去连字符化 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
剪切到媒体框 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
|
使用 CID 而不是 U+FFFD |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
搜索 指的是文本搜索功能。
“json” 的处理方式与 “dict” 完全相同,因此被省略。
“rawjson” 的处理与“rawdict” 完全相同,因此被省略。
“n/a”规范意味着值为0,设置此位对输出没有任何影响(但对性能有不利影响)。
如果您在使用默认包含图像的输出变体时对图像不感兴趣,那么请毫不犹豫地关闭相应的位:您将体验到更好的性能和更低的空间需求。
要查看 TEXT_INHIBIT_SPACES 的效果,请查看此示例:
>>> print(page.get_text("text"))
H a l l o !
Mo r e t e x t
i s f o l l o w i n g
i n E n g l i s h
. . . l e t ' s s e e
w h a t h a p p e n s .
>>> print(page.get_text("text", flags=pymupdf.TEXT_INHIBIT_SPACES))
Hallo!
More text
is following
in English
... let's see
what happens.
>>>
性能#
文本提取方法在提供的信息和资源需求及运行时间方面有显著差异。通常,更多信息当然意味着需要更多处理,并且生成更高的数据量。
注意
特别是图像对于影响有非常显著的作用。当您不需要它们时,请确保将其排除(通过flags参数)。按照默认的标志设置处理以下提到的2700个总页面需要160秒的时间,而使用排除所有图像的情况下,所需时间不到50%(77秒)。
首先,所有方法在与市场上其他产品相比时,都是非常快的。在处理速度方面,我们不知道有比这更快的(免费)工具。即使是最详细的方法,RAWDICT,在不到5秒钟的时间内处理完全部1’310页的Adobe PDF References(简单文本在这里需要不到2秒钟)。
下表显示了平均相对速度(“RSpeed”,基线为 1.00 是文本),取自大约 1400 个以文本为主的页面和 1300 个以图像为主的页面。
方法 |
RSpeed |
评论 |
没有图片 |
|---|---|---|---|
文本 |
1.00 |
无图像,纯文本,换行 |
1.00 |
区块 |
1.00 |
图像边界框(仅),区块级别文本带边界框,换行 |
1.00 |
单词 |
1.02 |
没有图像,单词级别的文本带有边界框 |
1.02 |
XML |
2.72 |
没有图像,char 级别文本,布局和字体细节 |
2.72 |
XHTML |
3.32 |
base64 图像, span 级别文本, 无布局信息 |
1.00 |
HTML |
3.54 |
base64 图像, span 级别文本, 布局和字体细节 |
1.01 |
字典 |
3.93 |
二进制 图像, 跨度 级别文本, 布局和字体细节 |
1.04 |
原始字典 |
4.50 |
二进制 图像, 字符 级文本, 布局和字体细节 |
1.68 |
如前所述:当排除图像提取(最后一列)时,相对速度变化非常明显:除了RAWDICT和XML,其他方法几乎同样快速,而RAWDICT所需的执行时间比现在最慢的XML少40%。
有关更多性能信息,请查看章节 附录 1。