pdf 是個(gè)異常坑爹的東西,有很多處理 pdf 的庫(kù),但是沒有完美的。
pdfminer3k 是 pdfminer 的 python3 版本,主要用于讀取 pdf 中的文本。
網(wǎng)上有很多 pdfminer3k 的代碼示例,看過以后,只想吐槽一下,太復(fù)雜了,有違 python 的簡(jiǎn)潔。
from pdfminer.pdfparser import PDFParser, PDFDocumentfrom pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreterfrom pdfminer.converter import PDFPageAggregatorfrom pdfminer.layout import LAParams, LTTextBoxfrom pdfminer.pdfinterp import PDFTextExtractionNotAllowedpath = "test.pdf"# 用文件對(duì)象來創(chuàng)建一個(gè)pdf文檔分析器praser = PDFParser(open(path, 'rb'))# 創(chuàng)建一個(gè)PDF文檔doc = PDFDocument()# 連接分析器 與文檔對(duì)象praser.set_document(doc)doc.set_parser(praser)# 提供初始化密碼# 如果沒有密碼 就創(chuàng)建一個(gè)空的字符串doc.initialize()# 檢測(cè)文檔是否提供txt轉(zhuǎn)換,不提供就忽略if not doc.is_extractable: raise PDFTextExtractionNotAllowedelse: # 創(chuàng)建PDf 資源管理器 來管理共享資源 rsrcmgr = PDFResourceManager() # 創(chuàng)建一個(gè)PDF設(shè)備對(duì)象 laparams = LAParams() device = PDFPageAggregator(rsrcmgr, laparams=laparams) # 創(chuàng)建一個(gè)PDF解釋器對(duì)象 interpreter = PDFPageInterpreter(rsrcmgr, device) # 循環(huán)遍歷列表,每次處理一個(gè)page的內(nèi)容 for page in doc.get_pages(): interpreter.process_page(page) # 接受該頁(yè)面的LTPage對(duì)象 layout = device.get_result() # 這里layout是一個(gè)LTPage對(duì)象,里面存放著這個(gè) page 解析出的各種對(duì)象 # 包括 LTTextBox, LTFigure, LTImage, LTTextBoxHorizontal 等 for x in layout: if isinstance(x, LTTextBox): print(x.get_text().strip())
pdfminer 對(duì)于表格的處理非常的不友好,能提取出文字,但是沒有格式:
pdf表格截圖:
代碼運(yùn)行結(jié)果:
想把這個(gè)結(jié)果還原成表格可不容易,加的規(guī)則太多必然導(dǎo)致通用性的下降。
tabula 是專門用來提取PDF表格數(shù)據(jù)的,同時(shí)支持PDF導(dǎo)出為CSV、Excel格式,但是這工具是用 java 寫的,依賴 java7/8。tabula-py 就是對(duì)它做了一層 python 的封裝,所以也依賴 java7/8。
代碼很簡(jiǎn)單:
import tabulapath = 'test.pdf'df = tabula.read_pdf(path, encoding='gbk', pages='all')for indexs in df.index: print(df.loc[indexs].values)# tabula.convert_into(path, os.path.splitext(path)[0]+'.csv', pages='all')
雖然號(hào)稱是專業(yè)處理 pdf 中的表格的,但實(shí)際效果也不咋地。還是 pdfminer 中使用的 pdf,運(yùn)行結(jié)果如下:
這結(jié)果真的很尷尬啊,表頭識(shí)別就錯(cuò)了,還有 pdf 中有兩張表,我沒發(fā)現(xiàn)怎么區(qū)分表。
pdfplumber 是按頁(yè)來處理 pdf 的,可以獲得頁(yè)面的所有文字,并且提供的單獨(dú)的方法用于提取表格。
import pdfplumberpath = 'test.pdf'pdf = pdfplumber.open(path)for page in pdf.pages: # 獲取當(dāng)前頁(yè)面的全部文本信息,包括表格中的文字 # print(page.extract_text()) for table in page.extract_tables(): # print(table) for row in table: print(row) print('---------- 分割線 ----------')pdf.close()
得到的 table 是個(gè) string 類型的二維數(shù)組,這里為了跟 tabula 比較,按行輸出顯示。
可以看到,跟 tabula 相比,首先是可以區(qū)分表格,其次,準(zhǔn)確率也提高了很多,表頭的識(shí)別完全正確。對(duì)于表格中有換行的,識(shí)別還不是很正確,但至少列的劃分沒問題,所以還是能處理的。
import pdfplumberimport repath = 'test1.pdf'pdf = pdfplumber.open(path)for page in pdf.pages: print(page.extract_text()) for pdf_table in page.extract_tables(): table = [] cells = [] for row in pdf_table: if not any(row): # 如果一行全為空,則視為一條記錄結(jié)束 if any(cells): table.append(cells) cells = [] elif all(row): # 如果一行全不為空,則本條為新行,上一條結(jié)束 if any(cells): table.append(cells) cells = [] table.append(row) else: if len(cells) == 0: cells = row else: for i in range(len(row)): if row[i] is not None: cells[i] = row[i] if cells[i] is None else cells[i] + row[i] for row in table: print([re.sub('\s+', '', cell) if cell is not None else None for cell in row]) print('---------- 分割線 ----------')pdf.close()
經(jīng)過處理后,運(yùn)行得到結(jié)果:
這結(jié)果已經(jīng)完全正確了,而用 tabula,即便是經(jīng)過處理也是無法得到這樣的結(jié)果的。當(dāng)然對(duì)于不同的 pdf,可能需要不同的處理,實(shí)際情況還是要自己分析。
pdfplumber 也有處理不準(zhǔn)確的時(shí)候,主要表現(xiàn)在缺列:
我找了另一個(gè) pdf,表格部分截圖如下:
解析結(jié)果如下:
4列變成了兩列,另外,如果表格有合并單元格的情況,也會(huì)有這種問題,我挑這個(gè)表格展示是因?yàn)楸容^特殊,沒有合并單元格也缺列了。這應(yīng)該跟 pdf 生成的時(shí)候有關(guān)。
但其實(shí)數(shù)據(jù)是獲取完整的,并沒有丟,只是被認(rèn)為是非表格了。輸出 page.extract_text() 如下:
然后,我又用 tabula 試了下,結(jié)果如下:
列是齊了,但是,表頭呢???
pdfplumber 還提供了圖形Debug功能,可以獲得PDF頁(yè)面的截圖,并且用方框框起識(shí)別到的文字或表格,幫助判斷PDF的識(shí)別情況,并且進(jìn)行配置的調(diào)整。要使用這個(gè)功能,還需要安裝ImageMagick。因?yàn)闆]有用到,所以暫時(shí)沒有去細(xì)究。
我們?cè)谧雠老x的時(shí)候,難免會(huì)遇到 pdf 需要解析,主要還是針對(duì)文本和表格的數(shù)據(jù)提取。而 python 處理 pdf 的庫(kù)實(shí)在是太多太多了,比如還有 pypdf2,網(wǎng)上資料也比較多,但是我試了,讀出來是亂碼,沒有仔細(xì)的讀源碼所以這個(gè)問題也沒有解決。
而我對(duì)比較常用的3個(gè)庫(kù)比較后覺得,還是 pdfplumber 比較好用,對(duì)表格的支持最好。
相關(guān)博文推薦:
Python:讀取 .doc、.docx 兩種 Word 文件簡(jiǎn)述及“Word 未能引發(fā)事件”錯(cuò)誤
聯(lián)系客服