有很多時候你會想用Python從PDF中提取數據,然后將其導出成其他格式。不幸的是,并沒有多少Python包可以很好的執行這部分工作。在這篇貼子中,我們將探討多個不同的Python包,并學習如何從PDF中提取某些圖片。盡管在Python中沒有一個完整的解決方案,你還是應該能夠運用這里的技能開始上手。提取出想要的數據之后,我們還將研究如何將數據導出成其他格式。
讓我們從如何提取文本開始學起!
使用PDFMiner提取文本
最被大家所熟知的可能是一個叫做PDFMiner的包。PDFMiner包大約從Python2.4版本就存在了。它的主要目的是從PDF中提取文本。實際上,PDFMiner可以告訴你某文本在分頁上具體的位置和字體信息。對于Python2.4到2.7版本,你可以參考以下網站來了解PDFMiner的更多信息:
GitHub – https://github.com/euske/pdfminer
PyPI – https://pypi.python.org/pypi/pdfminer/
Webpage – https://euske.github.io/pdfminer/
PDFMiner是不兼容于Python3的。幸運的是,PDFMiner家族的一個分支PDFMiner.six在Python3上完全能勝任同樣的功能。
你可以在以下網站上找到:
https://github.com/pdfminer/pdfminer.six
關于PDFMiner的安裝說明已經比較過時了。其實你可以用pip命令來安裝它:
python -m pip install pdfminer
如果你要在Python3上安裝PDFMiner(這也許就是你現在正在做的),你需要這樣安裝:
python -m pip install pdfminer.six
PDFMiner的相關文檔很少。你將很大可能地需要使用Google和Stack Overflow兩個查詢工具來弄清楚如何在這篇貼子的涵蓋內容之外有效地使用PDFMiner。
提取所有文本
有時你會想要提取PDF文件中的所有文本。PDFMiner包提供了一些不同的方法使你能夠做到這一點。我們先來探討一些編程的方法。讓我們試著從一個國稅局W9表單中讀取所有的文本。
你可以從這里得到表單副本:
https://www.irs.gov/pub/irs-pdf/fw9.pdf
保存完這個PDF文件之后,你可以參考以下代碼:
import iofrom pdfminer.converter import TextConverterfrom pdfminer.pdfinterp import PDFPageInterpreterfrom pdfminer.pdfinterp import PDFResourceManagerfrom pdfminer.pdfpage import PDFPagedef extract_text_from_pdf(pdf_path): resource_manager = PDFResourceManager() fake_file_handle = io.StringIO() converter = TextConverter(resource_manager, fake_file_handle) page_interpreter = PDFPageInterpreter(resource_manager, converter) with open(pdf_path, 'rb') as fh: for page in PDFPage.get_pages(fh, caching=True, check_extractable=True): page_interpreter.process_page(page) text = fake_file_handle.getvalue() # close open handles converter.close() fake_file_handle.close() if text: return textif __name__ == '__main__': print(extract_text_from_pdf('w9.pdf'))
當你直接使用PDFMiner包時,往往會有點繁瑣。這里,我們從PDFMiner的不同模塊中引入多個不同的類。由于這些類都沒有文檔說明,也沒有實現其文檔字符串屬性,我將不會深入講解它們做了什么。如果你真的好奇的話,盡管可以深入地研究它們的源代碼。無論如何,我認為我們可以大致照以上代碼行事。
我們做的第一件事就是創建一個資源管理器的實例。然后通過Python的輸入輸出(io)模塊創建一個似文件對象。如果你使用的是Python2,你應該使用StringIO模塊。接下來的步驟是創建一個轉換器。在這個例子里,我們選擇使用TextConverter,如果你想要的話,你還可以使用HTMLConverter或XMLConverter。最后,我們創建一個PDF解釋器對象,攜帶著我們的資源管理器和轉換器對象,來提取文本。
最后一步是打開PDF文件并且循環遍歷每一頁。結尾部分,我們抓取所有的文本,關閉不同的信息處理器,同時打印文本到標準輸出(stdout)。
按頁提取文本
通常我們并不需要從一個多頁文檔中抓取所有的文本。你一般會想要處理文檔的某些部分。那么,讓我們改寫代碼以便它提取文本呈分頁的格式。這將允許我們在檢查文本時,一次一頁地進行:
# miner_text_generator.pyimport iofrom pdfminer.converter import TextConverterfrom pdfminer.pdfinterp import PDFPageInterpreterfrom pdfminer.pdfinterp import PDFResourceManagerfrom pdfminer.pdfpage import PDFPagedef extract_text_by_page(pdf_path): with open(pdf_path, 'rb') as fh: for page in PDFPage.get_pages(fh, caching=True, check_extractable=True): resource_manager = PDFResourceManager() fake_file_handle = io.StringIO() converter = TextConverter(resource_manager, fake_file_handle) page_interpreter = PDFPageInterpreter(resource_manager, converter) page_interpreter.process_page(page) text = fake_file_handle.getvalue() yield text # close open handles converter.close() fake_file_handle.close()def extract_text(pdf_path): for page in extract_text_by_page(pdf_path): print(page) print()if __name__ == '__main__': print(extract_text('w9.pdf'))
在這個例子中,我們創建了一個生成器函數按頁生成(yield)了文本。extract_text函數按頁打印出文本。此處我們可以加入一些分析邏輯來得到我們想要的分析結果。或者我們可以僅是將文本(或HTML或XML)存入不同的文件中以便分析。
你可能注意到這些文本沒有按你期望的順序排列。因此你需要思考一些方法來分析出你感興趣的文本。
PDFMiner的好處就是你可以很方便地按文本、HTML或XML格式來“導出”PDF文件。
你也可以使用PDFMiner的命令行工具,pdf2txt.py和dumppdf.py,來為你執行導出工作。如果你不想試圖自己弄明白PDFMiner。根據pdf2txt.py的源代碼,它可以被用來導出PDF成純文本、HTML、XML或“標簽”格式。
通過pdf2txt.py導出文本
伴隨著PDFMiner一起的pdf2txt.py命令行工具會從一個PDF文件中提取文本并且默認將其打印至標準輸出(stdout)。它不能識別文字圖片,就像PDFMiner不支持光學字符識別(OCR)一樣。讓我們嘗試用最簡單的方法來使用它,那就是僅僅傳遞給它一個PDF文件的路徑。我們會使用w9.pdf文件。打開一個終端并且定位到你存放PDF文件的位置,或修改一下命令指向待處理文件:
pdf2txt.py w9.pdf
如果你執行這條命令,它將打印出所有的文本到標準輸出(stdout)。你也可以使pdf2txt.py 將文本寫入文件成文本、HTML、XML或“帶標簽PDF”格式。XML格式將給出關于PDF的大部分信息,因為它包含了每一個字母在文件中的位置以及字體信息。不推薦使用HTML格式,因為pdf2txt生成的標記往往會很丑。以下是教你如何生成不同格式輸出的方法:
pdf2txt.py -o w9.html w9.pdf pdf2txt.py -o w9.xml w9.pdf
第一條命令將創建一個HTML文件,而第二條將創建一個XML文件。
最終的結果看上去有點怪,但是它并不太糟糕。XML格式的輸出極其冗長,因此我不能將它完整地在這里重現,以下是一小段示例:
使用Slate提取文本
Tim McNamara覺得PDFMiner使用起來太過愚蠢和費力,因此他寫了一個圍繞它的包裝器叫做slate,以使它更簡單地從PDF中提取文本。不幸的是,它和Python3不兼容。如果你想試用,你可能需要easy_install以便于安裝distribute包,如下:
easy_install distribute
我不能使用pip 正確安裝這個包。然而一旦安裝了它,你將能夠使用pip來安裝slate:
python -m pip install slate
注意最新的版本是0.5.2,而pip未必能拿到這個版本。如果拿不到,那么你可以從GitHub上直接獲取slate安裝:
python -m pip install git+https://github.com/timClicks/slate
現在我們已經準備好寫一些代碼來從PDF中提取文本了:
# slate_text_extraction.pyimport slatedef extract_text_from_pdf(pdf_path): with open(pdf_path) as fh: document = slate.PDF(fh, password='', just_text=1) for page in document: print(page)if __name__ == '__main__': extract_text_from_pdf('w9.pdf')
正如你能看到的,讓slate分析一個PDF文件,你只需要引進slate然后創建一個它的PDF類的實例。PDF類其實是Python內置類list的一個子類,所以它僅是返回了一列/可遍歷的文本頁。如果PDF文件設有密碼,你可以傳入一個密碼參數。不管怎樣,一旦文件被分析,我們只要打印出每一頁的文本即可。
我非常喜歡slate,它用起來更簡單。不幸的是,這個包也幾乎沒有什么相關文檔。在瀏覽過它的源碼之后,它看起來只支持純文本提取。
導出你的數據
現在我們得到了一些文本,我們會花費一些時間來學習如何導出數據成各種不同的格式。具體來說,我們將學習如何以如下方法導出文本:
XML
JSON
CSV
讓我們開始吧!
導出成XML
可擴展標記語言(XML)格式是最為人所熟知的輸入輸出格式之一。它被廣泛運用于互聯網中的許多不同的事物。正如我們已經在本貼中看到的,PDFMiner也支持XML作為它的輸出之一。
話雖這么說,讓我們創建我們自己的XML生成工具。如下是一個簡單的例子:
# xml_exporter.pyimport osimport xml.etree.ElementTree as xmlfrom miner_text_generator import extract_text_by_pagefrom xml.dom import minidomdef export_as_xml(pdf_path, xml_path): filename = os.path.splitext(os.path.basename(pdf_path))[0] root = xml.Element('{filename}'.format(filename=filename)) pages = xml.Element('Pages') root.append(pages) counter = 1 for page in extract_text_by_page(pdf_path): text = xml.SubElement(pages, 'Page_{}'.format(counter)) text.text = page[0:100] counter += 1 tree = xml.ElementTree(root) xml_string = xml.tostring(root, 'utf-8') parsed_string = minidom.parseString(xml_string) pretty_string = parsed_string.toprettyxml(indent=' ') with open(xml_path, 'w') as fh: fh.write(pretty_string) #tree.write(xml_path)if __name__ == '__main__': pdf_path = 'w9.pdf' xml_path = 'w9.xml' export_as_xml(pdf_path, xml_path)
這段代碼將使用Python內置的XML庫,minidom和ElementTree。我們也引入PDFMiner生成器代碼以用于每次抓取一頁文本。在這個例子中,我們用PDF的文件名創建了我們頂層的元素。然后在它的下層增加了一個頁(Pages)元素。下一步是for循環,在此循環中我們從PDF中提取每一頁然后保存想要的信息。此處你可以加入一個特定的分析程序,其中你可以將頁分成句子或者單詞,從而分析出更有趣的信息。比如,你可能只想得到有某個特定名字或日期/時間戳的句子。你可以運用Python的正則表達式來找出這類東西,或者僅是檢查子字符串在句子中的存在。
對于這個例子,我們僅僅是提取了每一頁的前100個字符并將其存入一個XML的子元素(SubElement)中。接下來的一段代碼可以簡化成僅是寫出XML文件。然而,ElementTree不會做任何事來使得XML易讀。它最后看上去有點像壓縮的JavaScript似的一塊巨型文本。所以我們在寫入文件之前使用minidom通過空格來“美化”XML,而不是將整塊文本寫入磁盤。最終看上去像這樣:
上面是漂亮干凈的XML,同時它也是易讀的。錦上添花的是,你可以運用你在PyPDF2章節中所學到的知識從PDF中提取元數據(metadata),然后將其也加入到XML中。
導出成JSON
JavaScript對象注釋, 或者JSON, 是一種易讀易寫的輕量級的數據交換格式。Python包含一個json模塊于它的標準庫中,從而允許你用編程方式來讀寫JSON。讓我們運用從前一章節學到的內容來創建一個導出器腳本來輸出JSON而不是XML:
# json_exporter.pyimport jsonimport osfrom miner_text_generator import extract_text_by_pagedef export_as_json(pdf_path, json_path): filename = os.path.splitext(os.path.basename(pdf_path))[0] data = {'Filename': filename} data['Pages'] = [] counter = 1 for page in extract_text_by_page(pdf_path): text = page[0:100] page = {'Page_{}'.format(counter): text} data['Pages'].append(page) counter += 1 with open(json_path, 'w') as fh: json.dump(data, fh)if __name__ == '__main__': pdf_path = 'w9.pdf' json_path = 'w9.json' export_as_json(pdf_path, json_path)
這里,我們引入所需要的不同的庫,包括PDFMiner模塊。然后創建一個函數,以PDF文件的輸入路徑和JSON文件的輸出路徑為參數。在Python中JSON基本上就是一個字典,所以我們創建一對簡單的頂層的鍵:Filename和Pages。Pages鍵對應一個空的表單。接著,我們循環遍歷PDF的每一頁并且提取每一頁的前100個字符。然后創建一個字典變量以頁號作為鍵100個字符作為值并將其添加到頂層的頁表單中。最后,我們利用json模塊的dump命令生成文件。
文件的內容最終看上去像這樣:
{'Filename': 'w9', 'Pages': [{'Page_1': 'Form W-9(Rev. November 2017)Department of the Treasury Internal Revenue Service Request for Taxp'}, {'Page_2': 'Form W-9 (Rev. 11-2017)Page 2 By signing the filled-out form, you: 1. Certify that the TIN you are g'}, {'Page_3': 'Form W-9 (Rev. 11-2017)Page 3 Criminal penalty for falsifying information. Willfully falsifying cert'}, {'Page_4': 'Form W-9 (Rev. 11-2017)Page 4 The following chart shows types of payments that may be exempt from ba'}, {'Page_5': 'Form W-9 (Rev. 11-2017)Page 5 1. Interest, dividend, and barter exchange accounts opened before 1984'}, {'Page_6': 'Form W-9 (Rev. 11-2017)Page 6 The IRS does not initiate contacts with taxpayers via emails. Also, th'}]}
又一次,我們得到了易讀的輸出。你也可以通過PDF的元數據(metadata)來加強這個例子,如果你樂意的話。請注意輸出將會改變,它依賴于你想從每一頁或文檔中分析出什么樣的結果。
現在讓我們來快速看一下怎樣導出CSV文件。
導出成CSV
CSV是 **comma separated values** (逗號分隔值)的縮寫。它是一種漂亮的標準格式,并且已經存在了很長時間。CSV的優點就是Microsoft Excel和LibreOffice都能夠自動地以漂亮的電子表格的方式將它們打開。你也可以在一個文本編輯器中打開CSV文件,如果你樂意看到它的原始值的話。
Python有一個內置的csv模塊,你可以用它來讀寫CSV文件。在這里我們將用它從我們由PDF中提取的文本來創建一個CSV。讓我們看一下代碼:
# csv_exporter.pyimport csvimport osfrom miner_text_generator import extract_text_by_pagedef export_as_csv(pdf_path, csv_path): filename = os.path.splitext(os.path.basename(pdf_path))[0] counter = 1 with open(csv_path, 'w') as csv_file: writer = csv.writer(csv_file) for page in extract_text_by_page(pdf_path): text = page[0:100] words = text.split() writer.writerow(words)if __name__ == '__main__': pdf_path = 'w9.pdf' csv_path = 'w9.csv' export_as_csv(pdf_path, csv_path)
這個例子中,我們引入了Python的csv庫。除此以外,引入的庫和前一個例子相同。在函數中,我們利用CSV文件路徑創建了一個CSV文件處理器。然后用文件處理器作為唯一的參數初始化了一個CSV寫入器對象。接著像之前一樣遍歷了PDF頁。這里唯一的不同就是我們將前100個字符分割成了單個的詞。這將允許我們擁有一些真實的數據來加入到CSV中。如果不這樣做,那么每一行將只會有一個元素在其中,那就不算一個真正的CSV文件了。最后,我們將一列單詞寫入CSV文件中。
這就是得到的結果:
Form,W-9(Rev.,November,2017)Department,of,the,Treasury,Internal,Revenue,Service,Request,for,TaxpForm,W-9,(Rev.,11-2017)Page,2,By,signing,the,filled-out,"form,",you:,1.,Certify,that,the,TIN,you,are,gForm,W-9,(Rev.,11-2017)Page,3,Criminal,penalty,for,falsifying,information.,Willfully,falsifying,certForm,W-9,(Rev.,11-2017)Page,4,The,following,chart,shows,types,of,payments,that,may,be,exempt,from,baForm,W-9,(Rev.,11-2017)Page,5,1.,"Interest,","dividend,",and,barter,exchange,accounts,opened,before,1984Form,W-9,(Rev.,11-2017)Page,6,The,IRS,does,not,initiate,contacts,with,taxpayers,via,emails.,"Also,",th
我認為這個例子同JSON或XML的例子相比讀起來難了點,但是它不算太難。現在讓我們繼續來看一下怎樣才能將圖片從PDF中提取出來。
從PDF中提取圖片
不幸的是,并不存在Python包可以真正地做到從PDF中提取圖片。我找到的最接近的東西是有一個叫minecart的項目宣稱可以做到這一點,但是它只在Python2.7上有效。我沒法使其運行于我的PDF樣本。在Ned Batchelder的博客上有一篇文章談到了一點兒如何從PDF中提取JPG圖片。代碼如下:
# Extract jpg's from pdf's. Quick and dirty.import syspdf = file(sys.argv[1], "rb").read()startmark = "\xff\xd8"startfix = 0endmark = "\xff\xd9"endfix = 2i = 0njpg = 0while True: istream = pdf.find("stream", i) if istream < 0: ? ? ? ?break ? ?istart = pdf.find(startmark, istream, istream+20) ? ?if istart < 0: ? ? ? ?i = istream+20 ? ? ? ?continue ? ?iend = pdf.find("endstream", istart) ? ?if iend < 0: ? ? ? ?raise Exception("Didn't find end of stream!") ? ?iend = pdf.find(endmark, iend-20) ? ?if iend < 0: ? ? ? ?raise Exception("Didn't find end of JPG!") ? ?istart += startfix ? ?iend += endfix ? ?print("JPG %d from %d to %d" % (njpg, istart, iend)) ? ?jpg = pdf[istart:iend] ? ?jpgfile = file("jpg%d.jpg" % njpg, "wb") ? ?jpgfile.write(jpg) ? ?jpgfile.close() ? ?njpg += 1 ? ?i = iend
這同樣對我使用的PDF文件無效。有一些人在留言中宣稱代碼對他們的一些PDF文件有效,同時也有一些留言例舉了修改后的代碼。Stack Overflow網站上有關于這個的各種代碼,其中一些這樣或那樣地使用了PyPDF2。但沒有一個對我有效。
我的建議是使用一個類似于Poppler的工具來提取圖片。Poppler有一個工具叫做pdfimages,你可以同Python的subprocess模塊一起來使用。以下是你如何在沒有Python的情況下使用它:
pdfimages -all reportlab-sample.pdf images/prefix-jpg
請確保images文件夾(或你想新建的任何輸出文件夾)已經被創建,因為pdfimages不會為你創建它。
讓我們寫一個Python腳本來執行同樣的命令,請確保輸出文件夾已經存在:
# image_exporter.pyimport osimport subprocessdef image_exporter(pdf_path, output_dir): if not os.path.exists(output_dir): os.makedirs(output_dir) cmd = ['pdfimages', '-all', pdf_path, '{}/prefix'.format(output_dir)] subprocess.call(cmd) print('Images extracted:') print(os.listdir(output_dir))if __name__ == '__main__': pdf_path = 'reportlab-sample.pdf' image_exporter(pdf_path, output_dir='images')
在這個例子中,我們引入了subprocess和os模塊。如果輸出路徑不存在,我們會嘗試創建它。然后我們運用subprocess的call函數來執行pdfimages命令。使用call函數是因為它將等到pdfimages命令完全執行完才返回。你可以代之以Popen,但是那將基本上在后臺運行命令進程。最后,我們打印出輸出路徑下的細節,以確定所有的圖片都被提取進了其中。
還有一些網絡上的其它文章引用了一個叫做Wand的庫,你也許可以試一試。它是一個ImageMagick的包裝器。還有一個值得關注的是綁定了Poppler的Python叫做pypoppler,雖然我沒有能夠找到任何和這個包相關的提取圖片的例子。
總結
這篇文章網羅了很多信息。我們學習了一些可以用來從PDF中提取文本的包,如PDFMiner或Slate。我們還學習了如何運用Python的內置庫來導出文本到XML、JSON和CSV。最后,我們研究了一下從PDF中導出圖片這個棘手的問題。盡管Python目前沒有任何出色的庫可以完成這個工作,你可以采用其它工具的變通方案,例如Poppler的pdfimage工具模塊。
-
PDF
+關注
關注
1文章
168瀏覽量
33691 -
python
+關注
關注
56文章
4792瀏覽量
84627 -
資源管理器
+關注
關注
0文章
23瀏覽量
7533
原文標題:手把手教你如何用 Python 從 PDF 文件中導出數據
文章出處:【微信號:DBDevs,微信公眾號:數據分析與開發】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論