任務
現在我們有大量的pdf檔案,我們想要擷取每個檔案中感興趣的一部分,比如,我們下載了3500份上市公司的年度報告,我們想要找到包含“關鍵審計事項”部分內容,將pdf相關頁儲存為新的pdf檔案。
python環境:
anaconda3
pdfminer3k
pypdf2
解析pdf檔案
PDFMiner
PDFMiner是一個從PDF文件中提取資訊的工具。與其他PDF相關的工具不同,它只用於獲取和分析文字資料。PDFMiner能獲取頁面中文字的準確位置,以及字型或行等其他資訊。它還有一個PDF轉換器,可以將PDF檔案轉換成其他文字格式(如HTML)。還有一個可擴充套件的解析器PDF,可以用於文字分析以外的其他用途。
- 安裝pdfminer3k
pip install pdfminder3k
- 解析pdf,匹配關鍵字,返回其所在頁碼
from pdfminer.pdfparser import PDFParser, PDFDocument
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LTTextBoxHorizontal, LAParams
from pdfminer.pdfinterp import PDFTextExtractionNotAllowed
path = r`./report/603999讀者傳媒2017年年度報告.pdf`
def parse(path):
"""
#解析pdf檔案,並將文字內容更儲存在文字中
返回“關鍵字”所在的頁碼
"""
fp = open(path, `rb`) # 以二進位制讀模式開啟
# 用檔案物件來建立一個pdf文件分析器
praser = PDFParser(fp)
# 建立一個PDF文件
doc = PDFDocument()
# 連線分析器 與文件物件
praser.set_document(doc)
doc.set_parser(praser)
# 提供初始化密碼
# 如果沒有密碼 就建立一個空的字串
doc.initialize()
# 檢測文件是否提供txt轉換,不提供就忽略
if not doc.is_extractable:
raise PDFTextExtractionNotAllowed
else:
# 建立PDf 資源管理器 來管理共享資源
rsrcmgr = PDFResourceManager()
# 建立一個PDF裝置物件
laparams = LAParams()
device = PDFPageAggregator(rsrcmgr, laparams=laparams)
# 建立一個PDF直譯器物件
interpreter = PDFPageInterpreter(rsrcmgr, device)
# 迴圈遍歷列表,每次處理一個page的內容
page_num=0
key_flag=False
for page in doc.get_pages(): # doc.get_pages() 獲取page列表
if key_flag: #如果找到第一個關鍵字,則退出解析
break
page_num=page_num+1
interpreter.process_page(page)
# 接受該頁面的LTPage物件
layout = device.get_result()
# 這裡layout是一個LTPage物件 裡面存放著 這個page解析出的各種物件 一般包括LTTextBox, LTFigure, LTImage, LTTextBoxHorizontal 等等 想要獲取文字就獲得物件的text屬性,
for x in layout:
if (isinstance(x, LTTextBoxHorizontal)):
results = x.get_text()
if "關鍵審計事項" in results: # 匹配到關鍵字,則退出該頁的迴圈
key_flag=True
break
return page_num
裁剪pdf檔案
PyPDF2
PyPDF2是一個python PDF庫,能夠分割、合併、裁剪和轉換PDF檔案的頁面。它還可以向PDF檔案中新增自定義資料、檢視選項和密碼。它可以從PDF檢索文字和後設資料,還可以將整個檔案合併在一起。
- 安裝pypdf2:
pip install pypdf2
- 修改pypdf2的原始碼
利用pypdf2擷取pdf中的某幾頁,如果pdf的中文字編碼為ANSI編碼,則無法解析。對於pypdf2對於gbk不支援的現象,需要對以下兩處進行修改。參考
tips:
ANSI是一種字元程式碼,為使計算機支援更多語言。ANSI編碼表示英文字元時用一個位元組,表示中文用兩個或四個位元組。在簡體中文Windows作業系統中,ANSI 編碼代表 GBK 編碼。
GBK是在國家標準GB2312基礎上擴容後相容GB2312的標準。
其一:
在檔案*Miniconda3libsite-packagesPyPDF2generic.py”中第488行,改為
此處是為了適應含有‘gbk’的編碼的中文字元,提供對其的解碼能力。
try:
return NameObject(name.decode(`utf-8`))
except (UnicodeEncodeError, UnicodeDecodeError) as e:
# Name objects should represent irregular characters
# with a `#` followed by the symbol`s hex number
ret=name.decode(`gbk`)
return NameObject(ret)
其二:
在檔案*Miniconda3libsite-packagesPyPDF2utils.py中,第238-241行,改為:
此處是為了適應‘utf-8’的編碼情況;
try:
r = s.encode(`latin-1`)
if len(s) < 2:
bc[s] = r
return r
except Exception as e:
print(s)
r = s.encode(`utf-8`)
if len(s) < 2:
bc[s] = r
return r
- 擷取pdf特定頁
from PyPDF2 import PdfFileWriter, PdfFileReader
def pdfCrap(path,start_page,save_path):
"""
從pdf檔案中擷取幾頁,並儲存在對應pdf檔案中
"""
# 開始頁
start_page = start_page - 1
# 截止頁
end_page = start_page + 5 # 這裡設定擷取5頁
output = PdfFileWriter()
pdf_file = PdfFileReader(open(path, "rb"), strict=False)
pdf_pages_len = pdf_file.getNumPages()
for i in range(start_page, end_page):
output.addPage(pdf_file.getPage(i)) # 在輸出流中新增頁
outputStream = open(save_path, "wb")
output.write(outputStream)
遍歷資料夾內所有檔案
以上已經可以實現對單一pdf檔案的解析以及提取特定頁了,剩下的就是將整個流程串聯起來,實現批量pdf檔案的擷取。
import os
def file_name(file_dir):
"""
獲取某資料夾下,特定副檔名的檔名,
返回特定副檔名檔案列表
"""
L=[]
for root, dirs, files in os.walk(file_dir):
for file in files:
if os.path.splitext(file)[1] == `.pdf`: #os.path.splitext()函式將路徑拆分為檔名+副檔名
L.append(file)
return L
出錯怎麼辦
現在,我們已經能夠進行批量pdf檔案的裁剪了,但在實際操作中,會發生許多意外,使得程式被中斷,為了避免中斷影響批處理的程式,現在新增異常處理功能。
file_path = `./report/` # 輸入檔案所在的資料夾
result_path = `./result/` # 輸出檔案所在資料夾
def getAll():
"""
批量裁剪pdf檔案,新增程式log,記錄檔名,關鍵字起始頁碼,並且對異常情況進行記錄;
"""
files=file_name(file_path)
for file in files:
try:
path = file_path + file
page=parse(path)
save_path=result_path+file
pdfCrap(path,page,save_path)
if page==0: #如果識別出起始頁碼為0,說明關鍵字未被找到,需要記錄。
raise Exception("page_num=0")
with open(r`./log.txt`, `a`, encoding=`utf-8`) as f:
f.write(file+` start_page: `+str(page)+"
")
except Exception as e: # 捕獲錯誤
print(`error: `, e)
with open(r`./error_log.txt`, `a`, encoding=`utf-8`) as f:
f.write(file+` start_page: `+str(page)+"
")
continue