要研究pdf文件的頁碼,首先要考慮這個文件的種類。pdf可能是一本書的電子版,可能是一份簡歷、可能是由Word、PPT或其他文檔導出的……如果不是一本書,通常頁面內容里是沒有頁碼的;如果是一本書,雖然有頁碼,但是封面、前言、目錄、章節的封面很可能不會標上頁碼,而正文的頁碼和該pdf文件本身的頁碼對不上,如頁面里的第1頁會是pdf的第5頁,頁面的第2頁是pdf的第6頁……為了統一,我將頁碼“定義”為pdf的文件的頁碼。
下面來試試如何通過Python找到詞匯出現過的頁碼,首先我使用的是PyPDF2這個庫,這個庫使用起來非常簡單,但是只能提取pdf中的文本信息,是可以用來尋找詞匯的。我首先利用一篇英文文獻試了一下:
1 import PyPDF2 2 3 pdf_File=open('C:\\Users\\user\\Desktop\\2016.pdf','rb') 4 pdf_Obj=PyPDF2.PdfFileReader(pdf_File) 5 pages=pdf_Obj.getNumPages() 6 7 word_list=['predictedvalues','warfarin','NONMEM','atrialfibrillation','results'] 8 9 for w in word_list: 10 page_list=[] 11 for p in range(0,pages): 12 text=pdf_Obj.getPage(p).extractText() 13 if w in text: 14 page_list.append(p+1) 15 16 print(w,page_list)
我要尋找的詞匯是:predictive values, warfarin, NONMEM, atrial fibrillation, results,由於PyPDF2處理文本時,所有的空格都會消失,所以“predictive values”和“atrial fibrillation”寫成了“predictivevalues”和“atrialfibrillation”。輸出的結果如下:
可以發現,已經打印出了這些詞匯出的頁碼。然而當我在一份中文pdf上再次應用時,卻失敗了。我首先想到是編碼的問題,於是在第一行加上:
encoding=utf-8
去發現無濟於事,那么會不會是這個庫並不支持中文文本的提取呢?我在翻了一下這個項目的地址,發現早在2016年,已經有人發現了這個問題:
可惜的是這個問題到現在還是沒有解決,所以PyPDF2這個庫無法解決問題,只能另想辦法了。
於是我嘗試了另外一個庫pdfminer,它比PyPDF2使用起來要復雜地多,不過相應地功能也更加強大,不僅可以提取文本,也可以提取圖片、表格等,並且支持中文。但是這個庫是為Python2打造的,萬幸它有支持Python3的版本pdfminer.six,下面就開始干活吧。
首先導入要使用的模塊:
1 from pdfminer.pdfparser import PDFParser,PDFDocument 2 from pdfminer.pdfinterp import PDFResourceManager,PDFPageInterpreter 3 from pdfminer.converter import PDFPageAggregator 4 from pdfminer.layout import LTTextBoxHorizontal,LAParams 5 from pdfminer.pdfinterp import PDFTextExtractionNotAllowed
在這一步我出錯了好久,最后重啟了IDE才順利導入。
模塊導入之后就是提取文本了。這一部分的代碼我主要是借鑒了別人的,自己根據需求做了一些改動:
1 def parsePDFtoTXT(pdf_path): 2 fp = open(pdf_path, 'rb') 3 # 創建一個pdf文檔分析器 4 parser = PDFParser(fp) 5 # 創建一個PDF文檔對象存儲文檔結構 6 document= PDFDocument() 7 # 連接分析器與文檔對象 8 parser.set_document(document) 9 document.set_parser(parser) 10 # 提供初始化密碼 11 document.initialize() 12 # 檢查文件是否允許文本提取 13 if not document.is_extractable: 14 raise PDFTextExtractionNotAllowed 15 else: 16 # 創建一個PDF資源管理器對象來存儲共賞資源 17 rsrcmgr=PDFResourceManager() 18 # 設定參數進行分析 19 laparams=LAParams() 20 # 創建一個PDF設備對象 21 device=PDFPageAggregator(rsrcmgr,laparams=laparams) 22 # 創建一個PDF解釋器對象 23 interpreter=PDFPageInterpreter(rsrcmgr,device) 24 # 處理每一頁 25 for page in document.get_pages(): 26 # 解析頁面 27 interpreter.process_page(page) 28 # 接受該頁面的LTPage對象 29 layout=device.get_result() 30 print(layout) 31 output=str(layout) 32 for x in layout: 33 if (isinstance(x,LTTextBoxHorizontal)): 34 text=x.get_text() 35 output+=text 36 with open('C:\\Users\\user\\Desktop\\pdfoutput.txt','a',encoding='utf-8') as f: 37 f.write(output)
這段代碼功能的是提取目標pdf中的文本,將其保存為桌面的pdfoutout.txt這個文件。這一步對於提取文本來說沒有問題,但是這樣一來,所有關於頁碼的信息就盪然無存了。document.get_pages()是一個生成器(generator)對象,在解析頁面時,對它進行遍歷,從而一頁一頁地進行處理,解析的結果(包括文本、圖片、表格等)存儲在layout中,它是LTPage類型的對象,每一個頁面都對應了一個layout。我們需要文本信息,就要判斷其中的內容是否為橫向文本框(LTTextBoxHorizontal),如果是,則獲取其文本。不幸的是,我們雖然可以一頁一頁地解析頁面,但是卻無法一頁一頁輸出解析的結果,換句話說,輸出的txt文件失去了頁面信息,無法用來獲得詞匯所出現的頁面了。
我想到了一個解決辦法。雖然無法逐頁輸出結果,但是我們可以逐頁解析結果,那么在解析結果時,能否“留一手”,把頁面信息也加上去呢?於是我加上第31行和第35行,在每頁的結果前面加上“<LTPage(i) 0.000,0.000,595.200,841.800 rotate=0>”,i代表第i頁,這樣在所輸出的文本里,就有了用於區分頁碼的依據。第30行的代碼沒有什么實際的作用,我寫上去是為了充當進度條,每處理完一頁就會打印一行,在pdf頁數比較多時可以知道進行到哪一頁了。
得到了帶有頁碼信息的文本之后,就是查詢目標詞匯以及返回它所出現的頁碼了:
1 def get_word_page(word_list): 2 f=open('C:\\Users\\user\\Desktop\\pdfoutput.txt',encoding='utf-8') 3 text_list=f.read().split('<LTPage') 4 n=len(text_list) 5 for w in word_list: 6 page_list=[] 7 for i in range(1,n): 8 if w in text_list[i]: 9 page_list.append(i) 10 with open('C:\\Users\\user\\Desktop\\result.txt','a',encoding='utf-8') as f: 11 f.write(w+str(page_list)+'\n')
我的處理方法是根據每頁前的“<LTPage(i) 0.000,0.000,595.200,841.800 rotate=0>”把全部的文本進行切分,它們的每一部分都被作為元素存儲於1個列表中。pdf有n頁,列表中就會有n+1個元素(索引為0到n),其中第0個元素是我們不需要的,剩下的n個元素,其索引i就代表pdf的第i頁。判斷詞匯是否存在於文本時,我用的是最簡單的方法,這種方法無法區分大小,如“happy”、“Happy”、“HAPPY”,它們雖然大小寫不一致,但實際上是同一個詞,上述代碼就無法通過“happy”找到后2個詞,可以考慮利用正則表達式進行匹配來解決這個問題。最終的結果保存在桌面的result.txt文件中。
下面用一篇中文文獻來試驗一下,完整的代碼如下:
1 # -*- coding: utf-8 -*- 2 3 from pdfminer.pdfparser import PDFParser,PDFDocument 4 from pdfminer.pdfinterp import PDFResourceManager,PDFPageInterpreter 5 from pdfminer.converter import PDFPageAggregator 6 from pdfminer.layout import LTTextBoxHorizontal,LAParams 7 from pdfminer.pdfinterp import PDFTextExtractionNotAllowed 8 9 def parsePDFtoTXT(pdf_path): 10 fp = open(pdf_path, 'rb') 11 parser = PDFParser(fp) 12 document= PDFDocument() 13 parser.set_document(document) 14 document.set_parser(parser) 15 document.initialize() 16 if not document.is_extractable: 17 raise PDFTextExtractionNotAllowed 18 else: 19 rsrcmgr=PDFResourceManager() 20 laparams=LAParams() 21 device=PDFPageAggregator(rsrcmgr,laparams=laparams) 22 interpreter=PDFPageInterpreter(rsrcmgr,device) 23 for page in document.get_pages(): 24 interpreter.process_page(page) 25 layout=device.get_result() 26 print(layout) 27 output=str(layout) 28 for x in layout: 29 if (isinstance(x,LTTextBoxHorizontal)): 30 text=x.get_text() 31 output+=text 32 with open('C:\\Users\\user\\Desktop\\pdfoutput.txt','a',encoding='utf-8') as f: 33 f.write(output) 34 35 def get_word_page(word_list): 36 f=open('C:\\Users\\user\\Desktop\\pdfoutput.txt',encoding='utf-8') 37 text_list=f.read().split('<LTPage') 38 n=len(text_list) 39 for w in word_list: 40 page_list=[] 41 for i in range(1,n): 42 if w in text_list[i]: 43 page_list.append(i) 44 with open('C:\\Users\\user\\Desktop\\result.txt','a',encoding='utf-8') as f: 45 f.write(w+str(page_list)+'\n') 46 47 if __name__=='__main__': 48 parsePDFtoTXT('C:\\Users\\user\\Desktop\\群體葯動學原理建立卡馬西平和丙戊酸的定時定量給葯模型及臨床應用_林瑋瑋.pdf') 49 get_word_page(['群體葯動學','服葯時間','知情同意書','NONMEM','貝葉斯反饋'])
運行后生成的pdfoutput.txt的內容如下:
結果文件result.txt如下:
可以看到已經得到了目標詞匯所出現過的頁碼。