8

我正在尝试使用 pdfMiner 解析 pdf 文件文本,但提取的文本被合并。我正在使用以下链接中的 pdf 文件。

PDF文件

我擅长任何类型的输出(文件/字符串)。这是将提取的文本作为字符串返回的代码,但由于某种原因,列被合并了。

from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfinterp import PDFResourceManager, process_pdf
import StringIO

def convert_pdf(filename):
    rsrcmgr = PDFResourceManager()
    retstr = StringIO()
    codec = 'utf-8'
    laparams = LAParams()
    device = TextConverter(rsrcmgr, retstr, codec=codec)

    fp = file(filename, 'rb')
    process_pdf(rsrcmgr, device, fp)
    fp.close()
    device.close()

    str = retstr.getvalue()
    retstr.close()
    return str

我也尝试过 PyPdf2,但遇到了同样的问题。这是 PyPDF2 的示例代码

from PyPDF2.pdf import PdfFileReader
import StringIO
import time

def getDataUsingPyPdf2(filename):
    pdf = PdfFileReader(open(filename, "rb"))
    content = ""

    for i in range(0, pdf.getNumPages()):
        print str(i)
        extractedText = pdf.getPage(i).extractText()
        content +=  extractedText + "\n"

    content = " ".join(content.replace("\xa0", " ").strip().split())
    return content.encode("ascii", "ignore")

我也尝试过pdf2txt.py但无法获得格式化的输出。

4

3 回答 3

17

我最近遇到了类似的问题,尽管我的 pdf 结构稍微简单一些。

PDFMiner 使用称为“设备”的类来解析 pdf 文件中的页面。基本设备类是 PDFPageAggregator 类,它只是解析文件中的文本框。转换器类,例如 TextConverter、XMLConverter 和 HTMLConverter 也将结果输出到文件中(或在您的示例中的字符串流中)并对内容进行更精细的解析。

TextConverter(和 PDFPageAggregator)的问题在于它们没有足够深地递归到文档结构以正确提取不同的列。其他两个转换器需要有关文档结构的一些信息以用于显示目的,因此它们会收集更详细的数据。在您的示例 pdf 中,两个简单的设备都只解析(大致)包含列的整个文本框,这使得正确分隔不同的行成为不可能(或至少非常困难)。我发现这个问题的解决方案效果很好,要么

在这两种情况下,您都必须使用它们的边界框 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 启发式无法将它们分成自己的元素。您可以通过调整字边距参数(命令行工具 pdf2text.py 中的 -W 标志)来解决这个问题。在任何情况下,您可能都想通读(文档很少的)PDFMiner API以及浏览 PDFMiner 的源代码,您可以从 github 获得这些源代码。(唉,我无法粘贴链接,因为我没有足够的代表点:'<,但你可以希望谷歌正确的回购)

于 2013-10-04T10:33:02.857 回答
2

我尝试了你的第一个代码块,得到了一堆看起来像这样的结果:

多个住宅阿加登复合物14945010314370至372Willowrd W多个住宅Agarden Complex 14945010314380至384Willowrd W多个居住Agarden Complect 149450103141000至1020WillowBRookrd多个居住的房屋

我猜您的位置与此答案相似,并且所有空格都用于将单词放置在适当的位置,而不是实际的可打印空格字符。您尝试使用其他 pdf 库的事实使我认为这可能是任何 pdf 库都难以解析的问题。

于 2013-04-01T06:09:28.960 回答
0

@hlindblo 提供的解决方案给出了很好的结果。为了进一步按页面和段落对提取的文本块进行分组,以下是我使用的简单命令。

from collections import OrderedDict
grouped_text = OrderedDict()
for p in range(1000): # max page nb is 1000
    grouped_text[p] = {}
for (page_nb, x_min, y_min, x_max, y_max, text) in device.rows:
    x_min = round(x_min)//10 # manipulate the level of aggregation --> x_min might be slitghly different
    try:
        grouped_text[page_nb][x_min]+= " " + text
    except:
        grouped_text[page_nb][x_min] = text
于 2020-03-09T11:22:16.827 回答