我最近遇到了类似的问题,尽管我的pdf结构稍微简单一些.
PDFMiner使用名为“devices”的类来解析pdf文件中的页面.基本设备类是PDFPageAggregator类,它只是解析文件中的文本框.转换器类,例如TextConverter,XMLConverter和HTMLConverter也将结果输出到文件中(或在示例中的字符串流中),并对内容进行更精细的解析.
TextConverter(和PDFPageAggregator)的问题在于它们没有足够深入地处理文档结构以正确提取不同的列.另外两个转换器需要一些有关文档结构的信息以供显示,因此它们可以收集更详细的数据.在您的示例中,两个简单设备仅解析(粗略地)包含列的整个文本框,这使得不可能(或至少非常困难)正确地分隔不同的行.我发现的解决方案非常好,也就是说
>创建一个继承自PDFPageAggregator的新类,或
>使用XMLConverter并使用例如解析生成的XML文档. Beautifulsoup
在这两种情况下,您都必须使用其边界框y坐标将不同的文本段组合到行.
在新设备类的情况下(我认为,这更有说服力),您必须覆盖在渲染过程中为每个页面调用的方法receive_layout.然后,此方法递归地解析每个页面中的元素.例如,像这样的东西可能会让你开始:
from pdfminer.pdfdocument import PDFDocument, PDFNoOutlines
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LTPage, LTChar, LTAnno, LAParams, LTTextBox, LTTextLine
class PDFPageDetailedAggregator(PDFPageAggregator):
def __init__(self, rsrcmgr, pageno=1, laparams=None):
PDFPageAggregator.__init__(self, rsrcmgr, pageno=pageno, laparams=laparams)
self.rows = []
self.page_number = 0
def receive_layout(self, ltpage):
def render(item, page_number):
if isinstance(item, LTPage) or isinstance(item, LTTextBox):
for child in item:
render(child, page_number)
elif isinstance(item, LTTextLine):
child_str = ''
for child in item:
if isinstance(child, (LTChar, LTAnno)):
child_str += child.get_text()
child_str = ' '.join(child_str.split()).strip()
if child_str:
row = (page_number, item.bbox[0], item.bbox[1], item.bbox[2], item.bbox[3], child_str) # bbox == (x1, y1, x2, y2)
self.rows.append(row)
for child in item:
render(child, page_number)
return
render(ltpage, self.page_number)
self.page_number += 1
self.rows = sorted(self.rows, key = lambda x: (x[0], -x[2]))
self.result = ltpage
在上面的代码中,每个找到的LTTextLine元素都存储在一个有序的元组列表中,这些元组包含页码,边界框的坐标和该特定元素中包含的文本.然后你会做类似的事情:
from pprint import pprint
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.layout import LAParams
fp = open('pdf_doc.pdf', 'rb')
parser = PDFParser(fp)
doc = PDFDocument(parser)
doc.initialize('password') # leave empty for no password
rsrcmgr = PDFResourceManager()
laparams = LAParams()
device = PDFPageDetailedAggregator(rsrcmgr, laparams=laparams)
interpreter = PDFPageInterpreter(rsrcmgr, device)
for page in PDFPage.create_pages(doc):
interpreter.process_page(page)
# receive the LTPage object for this page
device.get_result()
pprint(device.rows)
变量device.rows包含有序列表,其中所有文本行使用其页码和y坐标排列.您可以使用相同的y坐标循环文本行和组线以形成行,存储列数据等.
我尝试使用上面的代码解析你的pdf,并且列主要是正确解析的.但是,有些列非常接近,因此默认的PDFMiner启发式方法无法将它们分离为自己的元素.您可以通过调整margin参数一词(命令行工具pdf2text.py中的-W标志)来解决这个问题.在任何情况下,您可能想要阅读(文档记录不清)PDFMiner API以及浏览PDFMiner的源代码,您可以从github获取. (唉,我无法粘贴链接,因为我没有足够的重复点:’