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
模块提供了walk
和listdir
两种方法获取指定目录下的文件列表。前者返回三个值,依次是当前目录路径、当前路径下所有子目录、当前路径下所有非目录子文件。它会遍历指定目录下所有子文件夹和子文件夹中的所有文件。
而在这个程序中,我们一般用到的是后者,它返回一个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
对象。(具体细节参考Python
的re
模块详解。)
# 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排序
利用list
的sort
方法可以对列表排序后再遍历以达到目的。如果文件名中的序号比较容易获得,比如在末尾,可以结合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
中的三种不同的方法。这三种不同的实现方法我写成了三个不同的方法work1
,work2
,work3
。使用时调用其中一个即可(改变调用实例work
方法中的参数即可切换方法)。
三种方法的不同可以在代码中看出来。在性能上,用PyCharm运行,合并428个文件(每个文件1页,大小在4KB~496KB间分布,加上一个1.7MB的封面)时,第1、3种方法耗时约为2.7s,而直接调用官方PdfFileMerger
类的第2种方法反而耗时长,约5s左右。
用终端运行时,第2种方法中途会报错(OSError
)。另两种方法运行正常,但是运行速度是原来的2倍多。
3 总结
这个小程序涉及到的内容: list
、dictionary
的基本使用; os
模块操作文件; re
模块正则匹配; try... except...
处理异常; PyPDF2
模块处理PDF;
……
其它(部分)参考资料:
PyPDF2 Documentation
python正则表达式从字符串中提取数字
Python3 正则表达式中group()方法获得匹配结果