我最近在類似的問題上掙扎,雖然我的pdf結構稍微簡單一些。
PDFMiner使用稱爲「設備」的類來解析PDF文件中的頁面。基本設備類是PDFPageAggregator類,它簡單地解析文件中的文本框。轉換器類,例如TextConverter,XMLConverter和HTMLConverter也會將結果輸出到一個文件中(或者像在你的例子中那樣在一個字符串流中)並且對內容做更精細的解析。
TextConverter(和PDFPageAggregator)的問題在於,它們不會遞歸足夠深的文檔結構以正確提取不同的列。另外兩個轉換器需要一些關於文檔結構的信息用於顯示,以便收集更詳細的數據。在你的例子pdf中,這兩個簡單的設備只能粗略地解析包含列的整個文本框,這使得不可能(或者至少非常困難)正確分離不同的行。這個,我發現非常有效的解決方案,是要麼
- 創建一個新的類,從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以及瀏覽可從github獲得的PDFMiner的源代碼。 (唉,我不能粘貼鏈接,因爲我沒有足夠的代表點:'<,但你可以希望谷歌正確的回購)
應該第一個代碼塊讀取'retstr = StringIO.StringIO()'? – Stedy
從pdf中閱讀多列是非常痛苦的。取決於你想要什麼[k2pdfopt](http://www.willus.com/k2pdfopt/)從每個頁面製作圖像。 – bobrobbob