GitHub

源码&使用说明:
PDF tools by PyPDF2
更新:思路不变,但后来调整了代码,已更新至GitHub。

0 需求

有时候我们需要对PDF进行分割、合并、旋转等操作。当数量较小时,现有软件如PDF Expert可以满足需求。但是如果数量较多,比如通过爬虫得到的,常规软件很难高效处理。

1 PyPDF简介

以上需求均可用Python中的PyPDF进行处理。
安装PyPDF:

pip install pypdf2

PyPDF的用法可参照:利用python处理pdf

2 用PyPDF编写PDF合并程序

PyPDF的合并操作参考上面的资料很容易学会。不过为了使程序具有一定的通用性,需要解决以下问题:

2.1 读取文件列表

os模块提供了walklistdir两种方法获取指定目录下的文件列表。前者返回三个值,依次是当前目录路径、当前路径下所有子目录、当前路径下所有非目录子文件。它会遍历指定目录下所有子文件夹和子文件夹中的所有文件。
而在这个程序中,我们一般用到的是后者,它返回一个list,只会列出指定路径下的所有文件和文件夹。

files = os.listdir(self.DIR_PATH)

2.2 按照一定顺序遍历文件

文件列表中的文件的文件名中可能有表示其序号的数字(或者其它方便编程获得的顺序特征)。仅仅通过os.listdir获得的列表的顺序不一定是我们想要的,这就需要用一种方法按序遍历。
于是解决方案分为两部分,从文件名提取序号和按序遍历。

2.2.1 正则表达式提取序号/特征

如果结合lambda表达式采用2.2.2.1中的list排序方法,这一部分是不需要的。
Python中用re模块可以实现正则表达式匹配。我这次需要合并的PDF的文件名为:

xxx(中文+标点字符串)xxx_{num1}-{num2}.pdf

{num1}{num2}分别代表两个数字。我想按照{num1}的顺序遍历文件。于是需要对文件名依次应用2个正则表达式获取{num1}。以下代码依次定义了包含这2个正则表达式的list,然后把它们编译为pattern对象。(具体细节参考Pythonre模块详解。)

# the list of regular expressions
RE_LIST = [
    '-{1,1}\d+',
    '\d+'
]

# patterns that will be generated according to RE_LIST
self.PATTERNS = []
# compile regular expressions to patterns
for regexp in RE_LIST:
self.PATTERNS.append(re.compile(regexp))

定义一个方法获得index

def __get_index(self, name):
original_name = name
# perform the regular expression matching sequentially
for regpat in self.PATTERNS:
    name = regpat.search(name)
    if name is None:    # ignore the unexpected file
        print('    find an invalid file: ', original_name)
        return self.INF_VALUE
    else:
        name = name.group()

return int(name)

其中,不是我们需要合并的PDF文件,如.DS_Store,它的名字无法匹配成功,search方法会返回None,此时该方法返回定义好的self.INF_VALUE

2.2.2 按序遍历

解决方法有两种,我这次用的是第二种。

2.2.2.1 list排序

利用listsort方法可以对列表排序后再遍历以达到目的。如果文件名中的序号比较容易获得,比如在末尾,可以结合lambda表达式使用sort方法。例如:参考资料链接

2.2.2.2 构建字典(映射)

通过构建一个从序号index到文件名的字典self.map(详见代码中的__construct_dict方法),然后遍历index得到文件名即可实现按序遍历文件名。

for i in range(self.START_INDEX, end):
try:    # support for the discontinuous index
    f_name = self.map[i]
except KeyError as err_key: # when the key doesn't exists
    print('Can\'t find key: ', err_key, ' , ignored.')
    continue

这里引入了异常处理,以便支持不连续的index。比如1,3,5,7……从1开始遍历,偶数(如2)不是字典中的key,便会引发KeyError异常,然后continue跳过此index继续遍历。

2.2.3 合并PDF

终于到了合并PDF的环节!将合并PDF的代码放入遍历文件名的循环中即可。这里有三种方式可以合并,调用的是PyPDF2中的三种不同的方法。这三种不同的实现方法我写成了三个不同的方法work1work2work3。使用时调用其中一个即可(改变调用实例work方法中的参数即可切换方法)。
三种方法的不同可以在代码中看出来。在性能上,用PyCharm运行,合并428个文件(每个文件1页,大小在4KB~496KB间分布,加上一个1.7MB的封面)时,第1、3种方法耗时约为2.7s,而直接调用官方PdfFileMerger类的第2种方法反而耗时长,约5s左右。
用终端运行时,第2种方法中途会报错(OSError)。另两种方法运行正常,但是运行速度是原来的2倍多。

3 总结

这个小程序涉及到的内容:
listdictionary的基本使用;
os模块操作文件;
re模块正则匹配;
try... except...处理异常;
PyPDF2模块处理PDF;
……


其它(部分)参考资料:
PyPDF2 Documentation
python正则表达式从字符串中提取数字
Python3 正则表达式中group()方法获得匹配结果

Last modification:May 2nd, 2020 at 01:01 am
如果觉得我的文章对你有用,请随意赞赏