批量擷取pdf檔案

justchenhao發表於2019-02-16

任務

現在我們有大量的pdf檔案,我們想要擷取每個檔案中感興趣的一部分,比如,我們下載了3500份上市公司的年度報告,我們想要找到包含“關鍵審計事項”部分內容,將pdf相關頁儲存為新的pdf檔案。
python環境:

anaconda3
pdfminer3k
pypdf2

解析pdf檔案

PDFMiner

PDFMiner是一個從PDF文件中提取資訊的工具。與其他PDF相關的工具不同,它只用於獲取和分析文字資料。PDFMiner能獲取頁面中文字的準確位置,以及字型或行等其他資訊。它還有一個PDF轉換器,可以將PDF檔案轉換成其他文字格式(如HTML)。還有一個可擴充套件的解析器PDF,可以用於文字分析以外的其他用途。

  1. 安裝pdfminer3k
pip install pdfminder3k
  1. 解析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檢索文字和後設資料,還可以將整個檔案合併在一起。

  1. 安裝pypdf2:
pip install pypdf2
  1. 修改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
  1. 擷取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

相關文章