任务

现在我们有大量的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)+"\n")
        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)+"\n")
            continue

justchenhao
4 声望0 粉丝

love what you do, do what you love