如何从 PDF 文件中提取文本和文本坐标?
Posted
技术标签:
【中文标题】如何从 PDF 文件中提取文本和文本坐标?【英文标题】:How to extract text and text coordinates from a PDF file? 【发布时间】:2014-05-18 20:29:07 【问题描述】:我想用 PDFMiner 从 PDF 文件中提取所有文本框和文本框坐标。
许多其他 Stack Overflow 帖子都介绍了如何以有序的方式提取所有文本,但我如何执行获取文本和文本位置的中间步骤?
给定一个 PDF 文件,输出应该类似于:
489, 41, "Signature"
500, 52, "b"
630, 202, "a_g_i_r"
【问题讨论】:
另见***.com/q/25248140/1709587,这是在此之后几个月发布的一个骗子。 【参考方案1】:换行符在最终输出中转换为下划线。这是我找到的最小工作解决方案。
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfpage import PDFTextExtractionNotAllowed
from pdfminer.pdfinterp import PDFResourceManager
from pdfminer.pdfinterp import PDFPageInterpreter
from pdfminer.pdfdevice import PDFDevice
from pdfminer.layout import LAParams
from pdfminer.converter import PDFPageAggregator
import pdfminer
# Open a PDF file.
fp = open('/Users/me/Downloads/test.pdf', 'rb')
# Create a PDF parser object associated with the file object.
parser = PDFParser(fp)
# Create a PDF document object that stores the document structure.
# Password for initialization as 2nd parameter
document = PDFDocument(parser)
# Check if the document allows text extraction. If not, abort.
if not document.is_extractable:
raise PDFTextExtractionNotAllowed
# Create a PDF resource manager object that stores shared resources.
rsrcmgr = PDFResourceManager()
# Create a PDF device object.
device = PDFDevice(rsrcmgr)
# BEGIN LAYOUT ANALYSIS
# Set parameters for analysis.
laparams = LAParams()
# Create a PDF page aggregator object.
device = PDFPageAggregator(rsrcmgr, laparams=laparams)
# Create a PDF interpreter object.
interpreter = PDFPageInterpreter(rsrcmgr, device)
def parse_obj(lt_objs):
# loop over the object list
for obj in lt_objs:
# if it's a textbox, print text and location
if isinstance(obj, pdfminer.layout.LTTextBoxHorizontal):
print "%6d, %6d, %s" % (obj.bbox[0], obj.bbox[1], obj.get_text().replace('\n', '_'))
# if it's a container, recurse
elif isinstance(obj, pdfminer.layout.LTFigure):
parse_obj(obj._objs)
# loop over all pages in the document
for page in PDFPage.create_pages(document):
# read the page into a layout object
interpreter.process_page(page)
layout = device.get_result()
# extract text from this object
parse_obj(layout._objs)
【讨论】:
我留下了我自己的答案,以几种方式对此进行了调整。您在此处创建的第一个device
永远不会使用,并且可以使用 get_pages
缩短初始设置解析的过程。我特别想知道:你有没有发现递归到LTFigure
s 有效的案例?我自己的实验向我表明,其中的文本不会被 PDFMiner 分组到文本框对象中,因此您在这里对它们的递归将永远不会起作用。
任何来自 Google 的人几乎肯定会希望底部的最新答案非常优雅***.com/a/69151177/5125264【参考方案2】:
这是一个可以复制和粘贴的示例,它列出了 PDF 中每个文本块的左上角,我认为它应该适用于任何不包含具有文本的“Form XObjects”的 PDF其中:
from pdfminer.layout import LAParams, LTTextBox
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfinterp import PDFResourceManager
from pdfminer.pdfinterp import PDFPageInterpreter
from pdfminer.converter import PDFPageAggregator
fp = open('yourpdf.pdf', 'rb')
rsrcmgr = PDFResourceManager()
laparams = LAParams()
device = PDFPageAggregator(rsrcmgr, laparams=laparams)
interpreter = PDFPageInterpreter(rsrcmgr, device)
pages = PDFPage.get_pages(fp)
for page in pages:
print('Processing next page...')
interpreter.process_page(page)
layout = device.get_result()
for lobj in layout:
if isinstance(lobj, LTTextBox):
x, y, text = lobj.bbox[0], lobj.bbox[3], lobj.get_text()
print('At %r is text: %s' % ((x, y), text))
以上代码基于 PDFMiner 文档中的 Performing Layout Analysis 示例,以及 pnj (https://***.com/a/22898159/1709587) 和 Matt Swain (https://***.com/a/25262470/1709587) 的示例。我对这些之前的示例进行了一些更改:
我使用PDFPage.get_pages()
,这是创建文档的简写,检查它is_extractable
,并将它传递给PDFPage.create_pages()
我懒得处理LTFigure
s,因为 PDFMiner 目前无论如何都无法干净地处理其中的文本。
LAParams
允许您设置一些参数来控制 PDF 中的单个字符如何被 PDFMiner 神奇地分组为行和文本框。如果您对这样的分组是一件必须发生的事情感到惊讶,那么pdf2txt docs 是合理的:
在实际的 PDF 文件中,文本部分可能会在其运行过程中被分成几个块,具体取决于创作软件。因此,文本提取需要拼接文本块。
LAParams
的参数与大多数 PDFMiner 一样,是未记录的,但您可以通过in the source code 或在 Python shell 中调用 help(LAParams)
来查看它们。 一些参数的含义在https://pdfminer-docs.readthedocs.io/pdfminer_index.html#pdf2txt-py给出,因为它们也可以在命令行中作为参数传递给pdf2text
。
上面的layout
对象是一个LTPage
,它是“布局对象”的可迭代对象。这些布局对象中的每一个都可以是以下类型之一...
LTTextBox
LTFigure
LTImage
LTLine
LTRect
... 或其子类。 (特别是,您的文本框可能都是LTTextBoxHorizontal
s。)
文档中的这张图片显示了LTPage
结构的更多细节:
上述每种类型都有一个.bbox
属性,其中包含一个 (x0, y0, x1, y1) 元组,分别包含对象的左、下、右和上坐标。 y 坐标表示为到页面底部 的距离。如果您更方便地使用从上到下的 y 轴,您可以从页面的 .mediabox
的高度中减去它们:
x0, y0_orig, x1, y1_orig = some_lobj.bbox
y0 = page.mediabox[3] - y1_orig
y1 = page.mediabox[3] - y0_orig
除了bbox
,LTTextBox
es 还有一个.get_text()
方法,如上所示,将其文本内容作为字符串返回。请注意,每个LTTextBox
都是LTChar
s(由PDF 显式绘制的字符,带有bbox
)和LTAnno
s(PDFMiner 添加到基于文本框内容的字符串表示形式的额外空格)的集合字符被画得很远;这些没有bbox
)。
这个答案开头的代码示例结合了这两个属性来显示每个文本块的坐标。
最后,值得注意的是,与上面引用的其他 Stack Overflow 答案不同,我不费心递归到 LTFigure
s。尽管LTFigure
s 可以包含文本,但PDFMiner 似乎无法将该文本分组到LTTextBox
es(您可以尝试使用https://***.com/a/27104504/1709587 的示例PDF),而是生成一个直接包含@987654362 的LTFigure
@对象。原则上,您可以弄清楚如何将这些拼凑成一个字符串,但 PDFMiner(截至 20181108 版)无法为您完成。
但希望您需要解析的 PDF 不使用带有文本的 Form XObjects,因此此警告不适用于您。
【讨论】:
您好,我有一份 pdf 格式的发票。我可以给出要提取的文本的位置并提取那些文本字段吗?【参考方案3】:完全披露,我是pdfminer.six 的维护者之一。它是用于 python 3 的 pdfminer 的社区维护版本。
现在,pdfminer.six 有多个 API 可以从 PDF 中提取文本和信息。对于以编程方式提取信息,我建议使用extract_pages()
。这允许您检查页面上的所有元素,这些元素按布局算法创建的有意义的层次结构排序。
以下示例是显示层次结构中所有元素的 Python 方式。它使用 pdfminer.six 的示例目录中的simple1.pdf。
from pathlib import Path
from typing import Iterable, Any
from pdfminer.high_level import extract_pages
def show_ltitem_hierarchy(o: Any, depth=0):
"""Show location and text of LTItem and all its descendants"""
if depth == 0:
print('element x1 y1 x2 y2 text')
print('------------------------------ --- --- --- ---- -----')
print(
f'get_indented_name(o, depth):<30.30s '
f'get_optional_bbox(o) '
f'get_optional_text(o)'
)
if isinstance(o, Iterable):
for i in o:
show_ltitem_hierarchy(i, depth=depth + 1)
def get_indented_name(o: Any, depth: int) -> str:
"""Indented name of LTItem"""
return ' ' * depth + o.__class__.__name__
def get_optional_bbox(o: Any) -> str:
"""Bounding box of LTItem if available, otherwise empty string"""
if hasattr(o, 'bbox'):
return ''.join(f'i:<4.0f' for i in o.bbox)
return ''
def get_optional_text(o: Any) -> str:
"""Text of LTItem if available, otherwise empty string"""
if hasattr(o, 'get_text'):
return o.get_text().strip()
return ''
path = Path('~/Downloads/simple1.pdf').expanduser()
pages = extract_pages(path)
show_ltitem_hierarchy(pages)
输出显示层次结构中的不同元素。每个的边界框。以及该元素包含的文本。
element x1 y1 x2 y2 text
------------------------------ --- --- --- ---- -----
generator
LTPage 0 0 612 792
LTTextBoxHorizontal 100 695 161 719 Hello
LTTextLineHorizontal 100 695 161 719 Hello
LTChar 100 695 117 719 H
LTChar 117 695 131 719 e
LTChar 131 695 136 719 l
LTChar 136 695 141 719 l
LTChar 141 695 155 719 o
LTChar 155 695 161 719
LTAnno
LTTextBoxHorizontal 261 695 324 719 World
LTTextLineHorizontal 261 695 324 719 World
LTChar 261 695 284 719 W
LTChar 284 695 297 719 o
LTChar 297 695 305 719 r
LTChar 305 695 311 719 l
LTChar 311 695 324 719 d
LTAnno
LTTextBoxHorizontal 100 595 161 619 Hello
LTTextLineHorizontal 100 595 161 619 Hello
LTChar 100 595 117 619 H
LTChar 117 595 131 619 e
LTChar 131 595 136 619 l
LTChar 136 595 141 619 l
LTChar 141 595 155 619 o
LTChar 155 595 161 619
LTAnno
LTTextBoxHorizontal 261 595 324 619 World
LTTextLineHorizontal 261 595 324 619 World
LTChar 261 595 284 619 W
LTChar 284 595 297 619 o
LTChar 297 595 305 619 r
LTChar 305 595 311 619 l
LTChar 311 595 324 619 d
LTAnno
LTTextBoxHorizontal 100 495 211 519 H e l l o
LTTextLineHorizontal 100 495 211 519 H e l l o
LTChar 100 495 117 519 H
LTAnno
LTChar 127 495 141 519 e
LTAnno
LTChar 151 495 156 519 l
LTAnno
LTChar 166 495 171 519 l
LTAnno
LTChar 181 495 195 519 o
LTAnno
LTChar 205 495 211 519
LTAnno
LTTextBoxHorizontal 321 495 424 519 W o r l d
LTTextLineHorizontal 321 495 424 519 W o r l d
LTChar 321 495 344 519 W
LTAnno
LTChar 354 495 367 519 o
LTAnno
LTChar 377 495 385 519 r
LTAnno
LTChar 395 495 401 519 l
LTAnno
LTChar 411 495 424 519 d
LTAnno
LTTextBoxHorizontal 100 395 211 419 H e l l o
LTTextLineHorizontal 100 395 211 419 H e l l o
LTChar 100 395 117 419 H
LTAnno
LTChar 127 395 141 419 e
LTAnno
LTChar 151 395 156 419 l
LTAnno
LTChar 166 395 171 419 l
LTAnno
LTChar 181 395 195 419 o
LTAnno
LTChar 205 395 211 419
LTAnno
LTTextBoxHorizontal 321 395 424 419 W o r l d
LTTextLineHorizontal 321 395 424 419 W o r l d
LTChar 321 395 344 419 W
LTAnno
LTChar 354 395 367 419 o
LTAnno
LTChar 377 395 385 419 r
LTAnno
LTChar 395 395 401 419 l
LTAnno
LTChar 410 395 424 419 d
LTAnno
(类似的答案 here, here 和 here ,我会尽量让它们保持同步。)
【讨论】:
那真是太美了,太棒了! 未来pdfminer有没有机会支持将LTFigure
中的LTChar
分组到LTTextBox
es中?以上是关于如何从 PDF 文件中提取文本和文本坐标?的主要内容,如果未能解决你的问题,请参考以下文章