簡體   English   中英

使用 PdfMiner 和 PyPDF2 合並列提取文本

[英]Extract Text Using PdfMiner and PyPDF2 Merges columns

我正在嘗試使用 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但無法獲得格式化的輸出。

我最近遇到了類似的問題,盡管我的 pdf 結構稍微簡單一些。

PDFMiner 使用稱為“設備”的類來解析 pdf 文件中的頁面。 基本設備類是 PDFPageAggregator 類,它只是解析文件中的文本框。 轉換器類,例如 TextConverter、XMLConverter 和 HTMLConverter 也將結果輸出到文件中(或在您的示例中的字符串流中)並對內容進行更精細的解析。

TextConverter(和 PDFPageAggregator)的問題在於它們沒有足夠深地遞歸到文檔結構以正確提取不同的列。 其他兩個轉換器需要有關文檔結構的一些信息以用於顯示目的,因此它們會收集更詳細的數據。 在您的示例 pdf 中,兩個簡單的設備都只解析(大致)包含列的整個文本框,這使得正確分隔不同的行成為不可能(或至少非常困難)。 我發現這個問題的解決方案效果很好,要么

  • 創建一個繼承自 PDFPageAggregator 的新類,或
  • 使用 XMLConverter 並使用例如Beautifulsoup解析生成的 XML 文檔

在這兩種情況下,您都必須使用它們的邊界框 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 獲得這些源代碼。 (唉,我無法粘貼鏈接,因為我沒有足夠的代表點:'<,但你可以希望谷歌正確的回購)

我嘗試了你的第一個代碼塊,得到了一堆看起來像這樣的結果:

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

我猜您的位置與此答案相似,並且所有空格都用於將單詞放置在正確的位置,而不是實際的可打印空格字符。 您嘗試使用其他 pdf 庫的事實使我認為這可能是任何 pdf 庫都難以解析的問題。

@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

我如何遍歷文本行和。 Y坐標,所以我可以將數據分為兩列。我的pdf也是兩列,它不是表類型,只有兩列

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM