Task 04 Python 操作 PDF

1. 相关介绍

Python 操作 PDF 会用到两个库,分别是:PyPDF2 和 pdfplumber

其中 PyPDF2 可以更好的读取、写入、分割、合并PDF文件,而 pdfplumber 可以更好的读取 PDF 文件中内容和提取 PDF 中的表格

对应的官网分别是
PyPDF2:https://pythonhosted.org/PyPDF2/

pdfplumber:https://github.com/jsvine/pdfplumber
由于这两个库都不是 Python 的标准库,所以在使用之前都需要单独安装

win+r 后输入 cmd 打开 command 窗口,依次输入如下命令进行安装:
pip install PyPDF2

pip install pdfplumber
安装完成后显示 success 则表示安装成功

需要注意的是:

如果你是第一次运行代码,在运行过程中,会直接报如下的错误

如果是在 Pycharm 下,直接通过报错信息,点击 utils.py 文件,定位到第 238 行原文

原文中是这样的:
r = s.encode(‘latin-1’)
if len(s) < 2:
bc[s] = r
return r
修改为:
try:
r = s.encode(‘latin-1’)
if len(s) < 2:
bc[s] = r
return r
except Exception as e:
r = s.encode(‘utf-8’)
if len(s) < 2:
bc[s] = r
return r
如果你使用的是 anaconda,对应的文件路径应该为:anaconda\Lib\site-packages\PyPDF2\utils.py,进行同样的修改操作即可

1.按【win+r】快捷键打开运行;

2.在输入框中输入cmd,点击【确定】;

3.在打开的命令提示符中执行【conda info --envs】命令即可。

2. 批量拆分

将一个完整的 PDF 拆分成几个小的 PDF,因为主要涉及到 PDF 整体的操作,所以本小节需要用到 PyPDF2 这个库

拆分的大概思路如下:

读取 PDF 的整体信息、总页数等
遍历每一页内容,以每个 step 为间隔将 PDF 存成每一个小的文件块
将小的文件块重新保存为新的 PDF 文件
需要注意的是,在拆分的过程中,可以手动设置间隔,例如:每5页保存成一个小的 PDF 文件

拆分的代码如下:

import os
from PyPDF2 import PdfFileReader, PdfFileWriter
def split_pdf(filename, filepath, save_dirpath, step=5):
    """
    拆分PDF为多个小的PDF文件,
    @param filename:文件名
    @param filepath:文件路径
    @param save_dirpath:保存小的PDF的文件路径
    @param step: 每step间隔的页面生成一个文件,例如step=5,表示0-4页、5-9页...为一个文件
    @return:
    """
    if not os.path.exists(save_dirpath):
        os.mkdir(save_dirpath)
    pdf_reader = PdfFileReader(filepath)
    # 读取每一页的数据
    pages = pdf_reader.getNumPages()
    for page in range(0, pages, step):
        pdf_writer = PdfFileWriter()
        # 拆分pdf,每 step 页的拆分为一个文件
        for index in range(page, page+step):
            if index < pages:
                pdf_writer.addPage(pdf_reader.getPage(index))
        # 保存拆分后的小文件
        save_path = os.path.join(save_dirpath, filename+str(int(page/step)+1)+'.pdf')
        print(save_path)
        with open(save_path, "wb") as out:
            pdf_writer.write(out)

    print("文件已成功拆分,保存路径为:"+save_dirpath)
save_dirpath = r"."
filename  = '易方达中小盘混合型证券投资基金2020年中期报告.pdf'
page =46 
step =  5
save_path = os.path.join(save_dirpath, filename+str(int(page/step)+1)+'.pdf')
split_pdf("易方达中小盘混合型证券投资基金2020年中期报告","易方达中小盘混合型证券投资基金2020年中期报告.pdf",'.')
.\易方达中小盘混合型证券投资基金2020年中期报告1.pdf
.\易方达中小盘混合型证券投资基金2020年中期报告2.pdf
.\易方达中小盘混合型证券投资基金2020年中期报告3.pdf
.\易方达中小盘混合型证券投资基金2020年中期报告4.pdf
.\易方达中小盘混合型证券投资基金2020年中期报告5.pdf
.\易方达中小盘混合型证券投资基金2020年中期报告6.pdf
.\易方达中小盘混合型证券投资基金2020年中期报告7.pdf
.\易方达中小盘混合型证券投资基金2020年中期报告8.pdf
.\易方达中小盘混合型证券投资基金2020年中期报告9.pdf
.\易方达中小盘混合型证券投资基金2020年中期报告10.pdf
文件已成功拆分,保存路径为:.
os.getcwd()  
'C:\\Users\\Chanjons\\Desktop\\OfficeAutomation'
os.path
<module 'ntpath' from 'C:\\ProgramData\\Anaconda3\\lib\\ntpath.py'>
os.path.exists("易方达中小盘混合型证券投资基金2020年中期报告.pdf") # 判断是否存在在当前文件夹中
True
pdf_reader  = PdfFileReader("易方达中小盘混合型证券投资基金2020年中期报告.pdf")
pdf_reader
<PyPDF2.pdf.PdfFileReader at 0x16c045b04c8>
pdf_reader.getNumPages()
46
pages = pdf_reader.getNumPages()
for page in range(0, pages, 5):
    print(page)
0
5
10
15
20
25
30
35
40
45
pdf_writer = PdfFileWriter() # 创建新的pdf
pdf_writer
<PyPDF2.pdf.PdfFileWriter at 0x16c0484c4c8>
pdf_writer.addPage(pdf_reader.getPage(0))
pdf_reader.getNumPages()
46
with open("temp.pdf", "wb") as out: # 写出PDF
    pdf_writer.write(out)
for page in range(0, pages, 5):
    print("page:",page)
    for index in range(page, page+5):
        print("index:",index)
        if index < pages:
            print("超出范围的index",index)
page: 0
index: 0
超出范围的index 0
index: 1
超出范围的index 1
index: 2
超出范围的index 2
index: 3
超出范围的index 3
index: 4
超出范围的index 4
page: 5
index: 5
超出范围的index 5
index: 6
超出范围的index 6
index: 7
超出范围的index 7
index: 8
超出范围的index 8
index: 9
超出范围的index 9
page: 10
index: 10
超出范围的index 10
index: 11
超出范围的index 11
index: 12
超出范围的index 12
index: 13
超出范围的index 13
index: 14
超出范围的index 14
page: 15
index: 15
超出范围的index 15
index: 16
超出范围的index 16
index: 17
超出范围的index 17
index: 18
超出范围的index 18
index: 19
超出范围的index 19
page: 20
index: 20
超出范围的index 20
index: 21
超出范围的index 21
index: 22
超出范围的index 22
index: 23
超出范围的index 23
index: 24
超出范围的index 24
page: 25
index: 25
超出范围的index 25
index: 26
超出范围的index 26
index: 27
超出范围的index 27
index: 28
超出范围的index 28
index: 29
超出范围的index 29
page: 30
index: 30
超出范围的index 30
index: 31
超出范围的index 31
index: 32
超出范围的index 32
index: 33
超出范围的index 33
index: 34
超出范围的index 34
page: 35
index: 35
超出范围的index 35
index: 36
超出范围的index 36
index: 37
超出范围的index 37
index: 38
超出范围的index 38
index: 39
超出范围的index 39
page: 40
index: 40
超出范围的index 40
index: 41
超出范围的index 41
index: 42
超出范围的index 42
index: 43
超出范围的index 43
index: 44
超出范围的index 44
page: 45
index: 45
超出范围的index 45
index: 46
index: 47
index: 48
index: 49

以“易方达中小盘混合型证券投资基金2020年中期报告”为例,整个 PDF 文件一共 46 页,每5页为间隔,最终生成了10个小的 PDF 文件

3. 批量合并

比起拆分来,合并的思路更加简单:

确定要合并的 文件顺序

循环追加到一个文件块中

保存成一个新的文件

对应的代码比较简单,基本不会出现问题:

def concat_pdf(filename, read_dirpath, save_filepath):
    """
    合并多个PDF文件
    @param filename:文件名
    @param read_dirpath:要合并的PDF目录
    @param save_filepath:合并后的PDF文件路径
    @return:
    """
    pdf_writer = PdfFileWriter()
    # 对文件名进行排序
    list_1 = []
    for i in os.listdir(read_dirpath):
        if i.find(filename) == 0:
            if len(i)>28:
                list_1.append(i)
    list_filename = list_1
    list_filename.sort(key=lambda x: int(x[:-4].replace(filename, "")))
    for filename in list_filename:
        print(filename)
        filepath = os.path.join(read_dirpath, filename)
        # 读取文件并获取文件的页数
        pdf_reader = PdfFileReader(filepath)
        pages = pdf_reader.getNumPages()
        # 逐页添加
        for page in range(pages):
            pdf_writer.addPage(pdf_reader.getPage(page))
    # 保存合并后的文件
    with open(save_filepath, "wb") as out:
        pdf_writer.write(out)
    print("文件已成功合并,保存路径为:"+save_filepath)
list_1 = []
for i in os.listdir("."):
    if i.find(filename) == 0:
        if len(i)>28:
            list_1.append(i)
list_1
['易方达中小盘混合型证券投资基金2020年中期报告1.pdf',
 '易方达中小盘混合型证券投资基金2020年中期报告10.pdf',
 '易方达中小盘混合型证券投资基金2020年中期报告2.pdf',
 '易方达中小盘混合型证券投资基金2020年中期报告3.pdf',
 '易方达中小盘混合型证券投资基金2020年中期报告4.pdf',
 '易方达中小盘混合型证券投资基金2020年中期报告5.pdf',
 '易方达中小盘混合型证券投资基金2020年中期报告6.pdf',
 '易方达中小盘混合型证券投资基金2020年中期报告7.pdf',
 '易方达中小盘混合型证券投资基金2020年中期报告8.pdf',
 '易方达中小盘混合型证券投资基金2020年中期报告9.pdf']
list_1.sort(key=lambda x: int(x[:-4].replace('易方达中小盘混合型证券投资基金2020年中期报告', "")))
concat_pdf("易方达中小盘混合型证券投资基金2020年中期报告", ".", r".\合并\合并报告.pdf")
易方达中小盘混合型证券投资基金2020年中期报告1.pdf
易方达中小盘混合型证券投资基金2020年中期报告2.pdf
易方达中小盘混合型证券投资基金2020年中期报告3.pdf
易方达中小盘混合型证券投资基金2020年中期报告4.pdf
易方达中小盘混合型证券投资基金2020年中期报告5.pdf
易方达中小盘混合型证券投资基金2020年中期报告6.pdf
易方达中小盘混合型证券投资基金2020年中期报告7.pdf
易方达中小盘混合型证券投资基金2020年中期报告8.pdf
易方达中小盘混合型证券投资基金2020年中期报告9.pdf
易方达中小盘混合型证券投资基金2020年中期报告10.pdf
文件已成功合并,保存路径为:.\合并\合并报告.pdf

4. 提取文字内容

涉及到具体的 PDF 内容 操作,本小节需要用到 pdfplumber 这个库

在进行文字提取的时候,主要用到 extract_text 这个函数

具体代码如下:

可以直接使用pip进行python包的安装,执行指令:
pip install pdfplumber

import pdfplumber
---------------------------------------------------------------------------

ModuleNotFoundError                       Traceback (most recent call last)

<ipython-input-64-b3d6737fd8e1> in <module>
----> 1 import pdfplumber


C:\ProgramData\Anaconda3\lib\pdfplumber\__init__.py in <module>
      8 
      9 from ._version import __version__
---> 10 from .pdf import PDF
     11 from . import utils
     12 import pdfminer


C:\ProgramData\Anaconda3\lib\pdfplumber\pdf.py in <module>
----> 1 from .container import Container
      2 from .page import Page
      3 from .utils import resolve_and_decode
      4 
      5 import logging


C:\ProgramData\Anaconda3\lib\pdfplumber\container.py in <module>
      1 from itertools import chain
----> 2 from . import utils, convert
      3 
      4 
      5 class Container(object):


C:\ProgramData\Anaconda3\lib\pdfplumber\utils.py in <module>
----> 1 from pdfminer.utils import PDFDocEncoding
      2 from pdfminer.psparser import PSLiteral
      3 from pdfminer.pdftypes import PDFObjRef
      4 from decimal import Decimal, ROUND_HALF_UP
      5 import numbers


ModuleNotFoundError: No module named 'pdfminer'
def extract_text_info(filepath):
    """
    提取PDF中的文字
    @param filepath:文件路径
    @return:
    """
    with pdfplumber.open(filepath) as pdf:
        # 获取第2页数据
        page = pdf.pages[1]
        print(page.extract_text())

可以看到,直接通过下标即可定位到相应的页码,从而通过 extract_text 函数提取该也的所有文字

而如果想要提取所有页的文字,只需要改成:

with pdfplumber.open(filepath) as pdf:
	# 获取全部数据
	for page in pdf.pages
    	print(page.extract_text())
  File "<ipython-input-65-22a8e9b5f50f>", line 3
    for page in pdf.pages
                         ^
SyntaxError: invalid syntax

例如,提取“易方达中小盘混合型证券投资基金2020年中期报告” 第一页的内容时,源文件是这样的:

运行代码后提取出来是这样的:

5. 提取表格内容

同样的,本节是对具体内容的操作,所以也需要用到 pdfplumber 这个库

和提取文字十分类似的是,提取表格内容只是将 extract_text 函数换成了 extract_table 函数

对应的代码如下:

def extract_table_info(filepath):
    """
    提取PDF中的图表数据
    @param filepath:
    @return:
    """
    with pdfplumber.open(filepath) as pdf:
        # 获取第18页数据
        page = pdf.pages[17]
        # 如果一页有一个表格,设置表格的第一行为表头,其余为数据
        table_info = page.extract_table()
        df_table = pd.DataFrame(table_info[1:], columns=table_info[0])
        df_table.to_csv('dmeo.csv', index=False, encoding='gbk')

上面代码可以获取到第 18 页的第一个表格内容,并且将其保存为 csv 文件存在本地

但是,如果说第 18 页有多个表格内容呢?

因为读取的表格会被存成二维数组,而多个二维数组就组成一个三维数组

遍历这个三位数组,就可以得到该页的每一个表格数据,对应的将 extract_table 函数 改成 extract_tables 即可

# 如果一页有多个表格,对应的数据是一个三维数组
tables_info = page.extract_tables()
for index in range(len(tables_info)):
    # 设置表格的第一行为表头,其余为数据
    df_table = pd.DataFrame(tables_info[index][1:], columns=tables_info[index][0])
    print(df_table)
    # df_table.to_csv('dmeo.csv', index=False, encoding='gbk')
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-67-a0931f0f9473> in <module>
      1 # 如果一页有多个表格,对应的数据是一个三维数组
----> 2 tables_info = page.extract_tables()
      3 for index in range(len(tables_info)):
      4     # 设置表格的第一行为表头,其余为数据
      5     df_table = pd.DataFrame(tables_info[index][1:], columns=tables_info[index][0])


AttributeError: 'int' object has no attribute 'extract_tables'

6. 提取图片内容

提取 PDF 中的图片和将 PDF 转存为图片是不一样的(下一小节),需要区分开。

提取图片:顾名思义,就是将内容中的图片都提取出来;转存为图片:则是将每一页的 PDF 内容存成一页一页的图片,下一小节会详细说明

转存为图片中,需要用到一个模块叫 fitz,fitz 的最新版 1.18.13,非最新版的在部分函数名称上存在差异,代码中会标记出来

使用 fitz 需要先安装 PyMuPDF 模块,安装方式如下:
pip install PyMuPDF
提取图片的整体逻辑如下:

使用 fitz 打开文档,获取文档详细数据

遍历每一个元素,通过正则找到图片的索引位置

使用 Pixmap 将索引对应的元素生成图片

通过 size 函数过滤较小的图片

实现的具体代码如下:

import os
import fitz
if not os.path.exists(pic_dirpath):
    os.makedirs(pic_dirpath)
# 使用正则表达式来查找图片
check_XObject = r"/Type(?= */XObject)"
check_Image = r"/Subtype(?= */Image)"
img_count = 0

"""1. 打开pdf,打印相关信息"""
pdf_info = fitz.open(filepath)
# 1.16.8版本用法 xref_len = doc._getXrefLength()
# 最新版本写法
xref_len = pdf_info.xref_length()
# 打印PDF的信息
print("文件名:{}, 页数: {}, 对象: {}".format(filepath, len(pdf_info), xref_len-1))

"""2. 遍历PDF中的对象,遇到是图像才进行下一步,不然就continue"""
for index in range(1, xref_len):
    # 1.16.8版本用法 text = doc._getXrefString(index)
    # 最新版本
    text = pdf_info.xref_object(index)
    
    is_XObject = re.search(check_XObject, text)
    is_Image = re.search(check_Image, text)
    # 如果不是对象也不是图片,则不操作
    if is_XObject or is_Image:
        img_count += 1
        # 根据索引生成图像
        pix = fitz.Pixmap(pdf_info, index)
        pic_filepath = os.path.join(pic_dirpath, 'img_' + str(img_count) + '.png')
        """pix.size 可以反映像素多少,简单的色素块该值较低,可以通过设置一个阈值过滤。以阈值 10000 为例过滤"""
        # if pix.size < 10000:
        #     continue
        
        """三、 将图像存为png格式"""
        if pix.n >= 5:
            # 先转换CMYK
            pix = fitz.Pixmap(fitz.csRGB, pix)
        # 存为PNG
        pix.writePNG(pic_filepath)
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-73-58c4e80959bf> in <module>
----> 1 if not os.path.exists(pic_dirpath):
      2     os.makedirs(pic_dirpath)
      3 # 使用正则表达式来查找图片
      4 check_XObject = r"/Type(?= */XObject)"
      5 check_Image = r"/Subtype(?= */Image)"


NameError: name 'pic_dirpath' is not defined

7. 转换为图片

转换为照片比较简单,就是将一页页的 PDF 转换为一张张的图片。大致过程如下:

7.1 安装 pdf2image
首先需要安装对应的库,最新的 pdf2image 库版本应该是 1.14.0

它的 github地址 为:https://github.com/Belval/pdf2image ,感兴趣的可以自行了解

安装方式如下:
pip install pdf2image

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值