python不可以处理pdf文件_使用 Python 处理 pdf

使用 Python 处理 pdf

背景

最近老婆工作中碰到一些困难,总是跟我抱怨工作好烦,不开心。

主要是是因为要处理一些报告,这些 pdf 格式的样本报告比较多,基本都是人工操作比较容易出错,也比较琐碎,好心情都被磨没了。

然后我说要么写个小程序吧,帮你处理这些琐碎的工作,然后就大概梳理了一下主要需求:

内部报告:

首先需要从系统出导出一个大的 pdf,包含很多小的 pdf

每个小的 pdf 报告中包含一些信息,比如 报告标题、姓名、编号、医院、总页数

拿到这些信息之后,分割大的 pdf,将小的 pdf 报告剥离处理

然后重命名这些报告,格式为 姓名-编号.pdf

将这些报告移动到对应医院的目录下,然后将这些医院目录压缩

外部报告:

外部会发过来压缩包,其中包含单个的 pdf 报告

解压缩后将报告拿出来,提取其中的信息,信息基本同内部报告

重命名

移动到对应的医院目录下,并压缩

实现

人生苦短,我用python。

嗯,python大法好,处理这些琐碎的事情写个 python 脚本跑一下不就好了吗。

python 版本使用的python 3.x。

梳理了下输球,首先需要解析大 pdf 中的一些关键信息,转化为我们需要的报告信息,首先写个Model叫做 Sample,包含 title、number、name、total_page、hospital 等等,然后依次解析 pdf 生成组成 samples 列表,最后根据报告列表分割处理pdf 就好了。

解析 pdf

有个 python 库叫做 pdfminer,这个库已经不支持python 3.x 版本了,该项目注明了可以使用 pdfminer.six来支持3.x的版本。

基本语法如下:

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

44def parse_samples():

fp = open(get_origin_report_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的内容

for page in doc.get_pages(): # doc.get_pages() 获取page列表

number_index = 0

name_index = 0

sample = Sample()

interpreter.process_page(page)

# 接受该页面的LTPage对象

layout = device.get_result()

# 这里layout是一个LTPage对象 里面存放着 这个page解析出的各种对象 一般包括LTTextBox, LTFigure, LTImage, LTTextBoxHorizontal 等等 想要获取文本就获得对象的text属性,

for index, out in enumerate(layout):

# if hasattr(out, "get_text"):

# if (isinstance(out, LTTextBoxHorizontal)):

if (isinstance(out, LTTextBox)):

with open(get_log_path(), 'a') as outfile:

results = out.get_text().replace(u'\xa0', u' ')

parse()

...

....

官网给出的 layout 布局

pdfminer_layout.png

我们主要拿其中的文本信息,所以拿到每一页 pdf 的LTPage后,遍历其中的包含 text 属性的控件就好了,拿到文本信息,根据正则匹配到关心的文本信息,赋值给新建的sample,依次循环,最后组成 samples 报告列表给后面使用。

解析完了后就需要处理 pdf 了。

分割 pdf

首先要做的是将大的 pdf 分割成小的 pdf,搜了下分割 pdf 有个python 库叫做 PyPDF2,参考API 写了一个工具方法用来分割 pdf。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19def split_pdf(infn, outfn, start_page, end_page):

"""

分割pdf文档,如果需要的话

:param infn: 源报告目录

:param outfn: 分割后输出文档目录

:param start_page:

:param end_page:

:return:

"""

pdf_output = PdfFileWriter()

pdf_input = PdfFileReader(open(infn, 'rb'))

# 获取 pdf 共用多少页

page_count = pdf_input.getNumPages()

# 将 pdf 的分割页面,输出到一个新的文件

for i in range(start_page, end_page):

pdf_output.addPage(pdf_input.getPage(i))

pdf_output.write(open(outfn, 'wb'))

print('---分割pdf完毕--')

根据第一步得出的 samples,循环一下拿出其中的total_page,依次分割就好了。

最后就是重命名、移动、压缩,不写了。

打包成 exe 可执行文件

老婆使用的是 Windowns,所以最后要打包成 .exe 可执行文件,搜了下 pyinstaller 可以解决,但是还是需要在对应的平台才能打出对应平台的可执行文件,可就是说想要打出 .exe 必须在 Windows 下打包,这就有点蛋疼了。然后弄了一下,发现 Windows 用来做开发真的是难用无比。

最后也没打 .exe,直接在 Windows 下装了python,双击 .py 文件好了,有bug的话替换一下文件好了,不用重新打包。

碰到的问题

项目交付老婆使用后,极其的好用,大幅提升了工作效率,减少出错概率,老婆甚至发了个朋友圈夸奖了一番,感觉美滋滋。

美了不到两天就出 bug 了。

bug是这样的,比如有个编号编号:G201802020108 正常解析出来 sample.number = G201802020108 才对,但是结果可能是这样的G20180202,后面少了几位。拿到原始的大 pdf 跑了一遍发现确实是这样,每个 pdf 在解析时我都把文本信息存了下来以便查问题用,这时派上了用场,打开看了下文本信息被这样处理了:

1

2编号:G20180202

0108

本来在一行或者说在一个 LTTextBox 中的内容被分割成了两个,所以出现了内容少的问题。

找到了问题原因就好处理了。

首先想到了一个方案,每个 LTxx 控件在当页 pdf 中的位置由坐标表示,左上角坐标是 {x0:0,y0:0, x1:0,y1:0},只要框住了编号信息所在的坐标范围,只要在该范围内的,所有文本信息 append 一下然后再正则匹配就好了。

1

2

3

4

5

6

7

8

9

10

11

12

13

14def in_number_area(text_box):

# x0,y0,x1,y1

scope = (370, 700, 560, 740)

if in_area(text_box.bbox, scope):

print('样本编号匹配!坐标点:', text_box.bbox)

return True

return False

# 判断TextBox坐标范围是否在规定范围内

def in_area(bbox, scope):

if bbox[0] > scope[0] and bbox[1] > scope[1] and bbox[2] < scope[2] and bbox[3] < scope[3]:

return True

return False

给定固定坐标范围,找到在该坐标范围内的控件,然后再去匹配。

跑了一下确实生效了,但是问题依然没有解决,为什么呢?因为报告种类不一,导致格式就不一样,坐标范围也不一样,也就是说不能动态的找到坐标范围。该方案失败。

于是又想到了第二个方案,直接看代码吧

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17# 解析编号

if not utils.number_legal_for_internal(sample.number):

num = utils.parse_sample_num(results)

if num:

number_index = index

sample.number = num

continue

# 解决编号被分成两段的问题

if not utils.number_legal_for_internal(sample.number) and number_index and (

index == number_index + 1 or index == number_index + 2):

print('修正后的编号:')

num = utils.parse_sample_num('样本编号:' + sample.number + results)

sample.number = num

if utils.number_legal_for_internal(num):

number_index = 0

continue

...

基本解决。

其实第二个方案也不太完美,为什么呢?编号还好说,一般都是字母加数字,如果出现这个bug一般都能比较好判断是否合法,比如编号长度 >=11,但是,如果是名字呢,就没有很好的合法规则来判断,比如名字:张三丰,丰字被分割了,而合法判断是名字长度 >=2,那最后结果就是张三而不是张三丰。

所以,其实还有一个优化方案,在方案一和方案二的基础上,第一步先匹配关键字,比如姓名:,匹配到了以后再框定坐标范围,一般名字都是6字以内,编号是20位以内,划定坐标范围后将范围内的匹配控件文本信息都拿出来再一起解析。基本做到了动态判断坐标范围。

外部报告跟内部报告基本是一样的,区别是首先需要解压一下,然后解析、处理,不写了。

还有哪些可以做

前面写了怎么解析处理pdf,其实还有一部分工作也可以用脚本来处理,比如,大的pdf 下载可以通过脚本处理;处理好的 pdf 报告要通过邮件法发送给客户,也可以通过脚本处理;有时还需要把解析出的信息填到表格中,也是可以用脚本处理的。

总结下来一共有这几个功能:

爬虫,从系统中导出大的 pdf 报告

pdf 解析和分割

归类,压缩

生成表格

发送邮件

目前只做了2 和3 ,其余的有时间慢慢写。

最后

为什么写这篇文章呢?

其实用到的东西很简单,看几分钟 python 入门然后再找两个开源库就搞定了,也没必要记录什么技术知识点。

我真正想说的是一些感悟,程序员碰到这种问题一般都会想办法写一些脚本处理,没什么稀奇的。但是对于不会写程序的人来说,有这个程序和没有这个程序简直就是不一样的工作,就拿这件事来说,用了这个程序之后,我老婆的工作效率明显提升,每天能节省出1-2小时时间做别的事情,从交付那天起,基本没听过老婆再抱怨工作了。简直开心。

还有就是以前总感觉程序员这个工作对生活的帮助简直就是没啥用,比如,你看谁谁现在是医生,去医院都不用挂号,直接就能看病;谁谁是老师,孩子上学的事情根本不用愁等等诸如此类,职业除了能养活自己之外能够反哺生活,在生活中提供一些价值。反观程序员对生活上的帮助简直就是0,甚至可以说是负数。

现在是高速发展的信息时代,干什么工作都离不开手机、电脑,甚至人工智能在某些方面都开始取代人力去做一些工作,所以我觉得越来越能体现出程序员的价值,可以控制机器取代人工去做一些重复性的、机械性的工作,所以在有能力的前提下学习一些开发知识会大有裨益。就像每个人都学英语一样,在未来,编程也有可能是一门基础学科,每个人都应该掌握。

回到现在,简单的用一些简单的脚本提升生活幸福感,把这些烦人的工作交给机器去做,人类最擅长的是浪费时间,恩。

machine_time1.jpg

machine_time2.jpg

版权声明:本文为 w4lle 原创文章,可以随意转载,但必须在明确位置注明出处!

本文链接: http://w4lle.com/2018/02/02/python-pdf/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值