python 使用PdfMiner和PyPDF2提取文本合并列

ecbunoof  于 2023-01-16  发布在  Python
关注(0)|答案(3)|浏览(232)

我尝试使用pdfMiner解析pdf文件文本,但提取的文本被合并。我使用的pdf文件来自以下链接[edit:链接已断开/指向潜在恶意软件]
我擅长任何类型的输出(文件/字符串)。下面是为我返回提取的文本作为字符串的代码,但由于某种原因,列被合并。

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

我也尝试过PyPdf 2,但是遇到了同样的问题。

from PyPDF2 import PdfReader
import StringIO

def get_data_using_pypdf2(filename):
    reader = PdfReader(filename)
    content = ""

    for page in reader.pages:
        extracted_text = page.extract_text()
        content +=  extracted_text + "\n"
    
    content = " ".join(content.replace("\xa0", " ").strip().split())
    return content.encode("ascii", "ignore")

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

rkttyhzu

rkttyhzu1#

我最近也遇到了类似的问题,尽管我的pdf格式结构稍微简单一些。
PDFMiner使用称为“devices”的类来解析pdf文件中的页面。基本的设备类是PDFPageAggregator类,它只解析文件中的文本框。转换器类,例如TextConverter、XMLConverter和HTMLDonverter也会将结果输出到文件中(或者像您的示例中那样输出到字符串流中),并对内容进行更精细的解析。
文本转换器的问题(和PDFPageAggregator)的问题在于,它们没有递归到文档结构的足够深度,无法正确地提取不同的列。另外两个转换器需要一些关于文档结构的信息以用于显示目的,因此它们收集更详细的数据。在示例pdf中,这两个过于简单的设备都只解析(大致)包含列的整个文本框,这使得不可能(或至少非常困难)正确地分隔不同行。我发现这个问题的解决方案相当有效,要么

  • 创建从PDFPageAggregator继承的新类,或者
  • 使用XMLConverter并使用Beautifulsoup等解析生成的XML文档

在这两种情况下,您都必须使用边界框的y坐标将不同的文本段组合到行中。
如果是一个新的设备类(我认为这更有说服力),你必须重写方法receive_layout,get在渲染过程中为每个页面调用该方法,然后该方法递归地解析每个页面中的元素,例如,类似下面的内容可能会让你开始:

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启发式无法将它们分离成自己的元素。你可以通过调整单词边距参数(命令行工具www.example.com中的-W标志)来解决这个pdf2text.py问题。无论如何,您可能需要通读PDFMiner API(文档很差),并浏览PDFMiner的源代码,您可以从github获得它。(唉,我不能粘贴链接,因为我没有足够的rep点数:'〈,但您可以希望谷歌正确的repo)

3vpjnl9f

3vpjnl9f2#

我尝试了你的第一个代码块,得到了一堆结果,看起来像这样:
多用途住宅花园综合大楼14945010314370至372威洛道西多用途住宅花园综合大楼14945010314380至384威洛道西多用途住宅花园综合大楼149450103141000至1020威洛布鲁克多用途住宅住宅
我猜你和这个answer的位置差不多,所有的空格都是用来把单词放在合适的位置,而不是作为实际的可打印空格字符。事实上,你已经尝试过其他pdf库,这让我认为这可能是一个任何pdf库都难以解析的问题。

ql3eal8s

ql3eal8s3#

@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

相关问题