Python 实现 PDF 转图片

环境安装

PDF转IMG需要两个Python模块:PyPDF2(1.26.0)Wand(0.4.4),其中Wand安装前需要先安装软件ImageMagick,由于7.xx版本的接口改变,必须安装6.xx版本,以及GhostScriptPyPDF2Wand可以直接使用pip安装,主要两个软件以及环境配置需要根据系统环境不同进行不同操作。
由于自己开发用的Mac,生产环境覆盖了Linux和Windows,需要在这3种系统上进行环境安装,也是折腾了不少,以下简要列一下安装过程。

Mac环境

安装ImageMagick

1
brew install imagemagick@6

安装完后可以用命令convert --version来测试

软链接:

1
$ ln -s /usr/local/Cellar/imagemagick@6/6.9.9-49/lib/libMagickWand-6.Q16.dylib /usr/local/lib/libMagickWand.dylib

添加至系统环境:

1
2
echo 'export PATH="/usr/local/opt/imagemagick@6/bin:$PATH"' >> ~/.bash_profile
. ~/.bash_profile

安装GhostScript

1
brew install gs

Linux环境

安装ImageMagickGhostScript

1
yum install ImageMagick

选择安装6.xx版本,由于依赖关系,会自动安装GhostScript

Windows环境

  1. 下载ImageMagick
  2. 相关安装及配置
  3. 下载安装GhostScript

windows环境下配合Wand0.4.4使用的时候,加载Wand后,python读取的环境变量Path变成了unicode类型,导致启动webdirver时会报“TypeError: environment can only contain strings”,可以在引入Wand后,将path修改回str类型

1
2
3
import os
import wand
os.environ['path'] = str(os.environ['path'])

代码实现

考虑PDF会有多页的情况,每一页PDF会生成单独的一张图片,如有需求,可以用PIL将多张图片进行合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import io

from wand.image import Image
from wand.color import Color
from PyPDF2 import PdfFileReader, PdfFileWriter
from PIL import Image as PIL_Image


def pdf_to_img(pdf_path, resolution=200, img_suffix='jpeg'):
"""
PDF转图片
:param pdf_path: PDF路径
:param resolution: 分辨率
:param img_suffix: 图片后缀名
:return: List 图片路径列表
"""
pdf_file = PdfFileReader(pdf_path, strict=False)
pages = pdf_file.getNumPages()
img_list = []
for page in range(pages):
page_obj = pdf_file.getPage(page)
dst_pdf = PdfFileWriter()
dst_pdf.addPage(page_obj)
pdf_bytes = io.BytesIO()
dst_pdf.write(pdf_bytes)
pdf_bytes.seek(0)
img = Image(file=pdf_bytes, resolution=resolution)
img.format = img_suffix
img.compression_quality = 90 # 图片质量压缩
img.background_color = Color('white')
img_path = pdf_path.replace('.pdf', '_{}.{}'.format(page, img_suffix))\
if page > 0 else pdf_path.replace('.pdf', '.{}'.format(img_suffix))
img.save(filename=img_path)
img.destroy()
img_list.append(img_path)
if len(img_list) > 1: # 多图上下拼接
return _merge_img(img_list)
elif len(img_list) == 0: # 异常情况,无图片生成
return ''
else:
return img_list[0]


def _merge_img(img_list):
"""拼接图片"""
if img_list:
img_name = img_list[0]
color_mod = 'RGBA' if img_name.endswith('.png') else 'RGB' # jpeg格式不支持RGBA
first_img = PIL_Image.open(img_list[0])
height_size = first_img.size[1]
total_width = first_img.size[0]
total_height = height_size * len(img_list)
left = 0
right = height_size
target = PIL_Image.new(color_mod, (total_width, total_height)) # 最终拼接的图像的大小
for img in img_list:
target.paste(PIL_Image.open(img), (0, left, total_width, right))
left += height_size
right += height_size
target.save(img_name, quality=100)
return img_name
else:
return ''

注意事项

通过读PyPDF2的源码可以发现:

1
2
3
4
5
encrypt = self.trailer['/Encrypt'].getObject()
if encrypt['/Filter'] != '/Standard':
raise NotImplementedError("only Standard PDF encryption handler is available")
if not (encrypt['/V'] in (1, 2)):
raise NotImplementedError("only algorithm code 1 and 2 are supported")

如果PDF是有密码的,PyPDF2是支持输入密码的,但是仅限于其中两种密码算法,所以如果有密码需求的话,还需要测试下PyPDF2支不支持自己的PDF所用密码算法。