起因:

  因为个人原因, 这些天了解了一下Python处理PDF的方法.

  首先是PDF转txt, 这个方法比较多, 这里就不再赘述, 主要聊一下PDF中的图片获取.

  这里用我自己的例子, 不过具体情况还得具体分析.

工具:  pdfminer, pillow, fitz, re

思路:

  1.  使用pdfminer解析PDF, 通过当前页的LTpage对象, 获取关键词的position与当前LTpage的size.

  2.  使用fitz将当前页的PDF转换为PNG

  3.  使用pillow, 通过第一步得到的参数来从第二步得到的PNG中截取目标图表

关键词:  "[图表]*\s\d+[::]", "来源[::]"

代码:

  1. from pdfminer.pdfparser import PDFParser, PDFDocument
  2. from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
  3. from pdfminer.converter import PDFPageAggregator
  4. from pdfminer.layout import LAParams
  5. from pdfminer.pdfinterp import PDFTextExtractionNotAllowed
  6. from PIL import Image
  7. import fitz
  8. import re
  9. import os
  10.  
  11. class GetPic:
  12. def __init__(self, filename, password=''):
  13. """
  14. 初始化
  15. :param filename: pdf路径
  16. :param password: 密码
  17. """
  18. with open(filename, 'rb') as file:
  19. # 创建文档分析器
  20. self.parser = PDFParser(file)
  21. # 创建文档
  22. self.doc = PDFDocument()
  23. # 连接文档与文档分析器
  24. self.parser.set_document(self.doc)
  25. self.doc.set_parser(self.parser)
  26. # 初始化, 提供初始密码, 若无则为空字符串
  27. self.doc.initialize(password)
  28. # 检测文档是否提供txt转换, 不提供就忽略, 抛出异常
  29. if not self.doc.is_extractable:
  30. raise PDFTextExtractionNotAllowed
  31. else:
  32. # 创建PDF资源管理器, 管理共享资源
  33. self.resource_manager = PDFResourceManager()
  34. # 创建一个PDF设备对象
  35. self.laparams = LAParams()
  36. self.device = PDFPageAggregator(self.resource_manager, laparams=self.laparams)
  37. # 创建一个PDF解释器对象
  38. self.interpreter = PDFPageInterpreter(self.resource_manager, self.device)
  39. # pdf的page对象列表
  40. self.doc_pdfs = list(self.doc.get_pages())
  41. # 打开PDF文件, 生成一个包含图片doc对象的可迭代对象
  42. self.doc_pics = fitz.open(filename)
  43.  
  44. def to_pic(self, doc, zoom, pg, pic_path):
  45. """
  46. 将单页pdf转换为pic
  47. :param doc: 图片的doc对象
  48. :param zoom: 图片缩放比例, type int, 数值越大分辨率越高
  49. :param pg: 对象在doc_pics中的索引
  50. :param pic_path: 图片保存路径
  51. :return: 图片的路径
  52. """
  53. rotate = int(0)
  54. trans = fitz.Matrix(zoom, zoom).preRotate(rotate)
  55. pm = doc.getPixmap(matrix=trans, alpha=False)
  56. path = os.path.join(pic_path, str(pg)) + '.png'
  57. pm.writePNG(path)
  58. return path
  59.  
  60. def get_pic_loc(self, doc):
  61. """
  62. 获取单页中图片的位置
  63. :param doc: pdf的doc对象
  64. :return: 返回一个list, 元素为图片名称和上下y坐标元组组成的tuple. 当前页的尺寸
  65. """
  66. self.interpreter.process_page(doc)
  67. layout = self.device.get_result()
  68. # pdf的尺寸, tuple, (width, height)
  69. canvas_size = layout.bbox
  70. # 图片名称坐标
  71. loc_top = []
  72. # 来源坐标
  73. loc_bottom = []
  74. # 图片名称与应截取的区域y1, y2坐标
  75. loc_named_pic = []
  76. # 遍历单页的所有LT对象
  77. for i in layout:
  78. if hasattr(i, 'get_text'):
  79. text = i.get_text().strip()
  80. # 匹配关键词
  81. if re.search(r'图表*\s\d+[::]', text):
  82. loc_top.append((i.bbox, text))
  83. elif re.search(r'来源[::]', text):
  84. loc_bottom.append((i.bbox, text))
  85. zip_loc = zip(loc_top, loc_bottom)
  86. for i in zip_loc:
  87. y1 = i[1][0][1]
  88. y2 = i[0][0][3]
  89. name = i[0][1]
  90. loc_named_pic.append((name, (y1, y2)))
  91. return loc_named_pic, canvas_size
  92.  
  93. def get_crops(self, pic_path, canvas_size, position, cropped_pic_name, cropped_pic_path):
  94. """
  95. 按给定位置截取图片
  96. :param pic_path: 被截取的图片的路径
  97. :param canvas_size: 图片为pdf时的尺寸, tuple, (0, 0, width, height)
  98. :param position: 要截取的位置, tuple, (y1, y2)
  99. :param cropped_pic_name: 截取的图片名称
  100. :param cropped_pic_path: 截取的图片保存路径
  101. :return:
  102. """
  103. img = Image.open(pic_path)
  104. # 当前图片的尺寸 tuple(width, height)
  105. pic_size = img.size
  106. # 截图的范围扩大值
  107. size_increase = 10
  108. x1 = 0
  109. x2 = pic_size[0]
  110. y1 = pic_size[1] * (1 - (position[1] + size_increase)/canvas_size[3])
  111. y2 = pic_size[1] * (1 - (position[0] - size_increase)/canvas_size[3])
  112. cropped_img = img.crop((x1, y1, x2, y2))
  113. # 保存截图文件的路径
  114. path = os.path.join(cropped_pic_path, cropped_pic_name) + '.png'
  115. cropped_img.save(path)
  116. print('成功截取图片:', cropped_pic_name)
  117.  
  118. def main(self, pic_path, cropped_pic_path, pgn=None):
  119. """
  120. 主函数
  121. :param pic_path: 被截取的图片路径
  122. :param cropped_pic_path: 图片的截图的保存路径
  123. :param pgn: 指定获取截图的对象的索引
  124. :return:
  125. """
  126. if pgn is not None:
  127. # 获取当前页的doc
  128. doc_pdf = self.doc_pdfs[pgn]
  129. doc_pic = self.doc_pics[pgn]
  130. # 将当前页转换为PNG, 返回值为图片路径
  131. path = self.to_pic(doc_pic, 2, pgn, pic_path)
  132. loc_name_pic, canvas_size = self.get_pic_loc(doc_pdf)
  133. if loc_name_pic:
  134. for i in loc_name_pic:
  135. position = i[1]
  136. cropped_pic_name = re.sub('/', '_', i[0])
  137. self.get_crops(path, canvas_size, position, cropped_pic_name, cropped_pic_path)
  138.  
  139. if __name__ == '__main__':
  140. pdf_path = '要处理的PDF的路径'
  141. test = GetPic(pdf_path)
  142. pic_path = 'PNG的保存路径'
  143. cropped_pic_path = '截图的保存路径'
  144. page_count = test.doc_pics.pageCount
  145. for i in range(page_count):
  146. test.main(pic_path, cropped_pic_path, pgn=i)

本例局限:

  1.  目标PDF需要可以用pdfminer里的LTPage对象解析出文字.

  2.  PDF中没有跳页的图表.

  3.  截图的时候只用了y轴截图, x轴上可能出现多个图表

局限解决方案:

  1.  目前没有去尝试, 或许PyPDF2可以试一试?

  2.  这里的函数都是处理单页的, 所有在处理连页图片时会出现问题, 不过解决方法也很简单. 就是将 loc_top、loc_bottom设置为全局变量并且加上页码的索引, 这样loc_top和loc_bottm中的元素就能够一一对应. 再加上一个判断, top的y轴坐标比bottom小的话, 就截取两张图片, top的y轴坐标至页尾和bottom的y轴坐标至页头. 有兴趣的可以自己尝试一下.

  3.  这个问题的话, 一是可以后期通过其它库再按照一定的方法截取一次; 二是可以在一次截取的时候加上x轴的左坐标来确定目标位置, 因为如果同一y轴范围内只有一个图表的话, x轴右坐标就无关紧要类, 如果同一y轴范围内有两个图标的话, 通过x轴左坐标也能化界, 如果有两个以上的图标时候就需要加上x轴的右坐标了.

结语: 

  这里只是提供了一种思路, 方法其实还是很不完善的, 很多小细节都没有去解决.

  还有一种思路是将PDF转换为PNG之后直接识别其中的关键词左边来获取截图, 这个的话大家也可以去了解一下, 用tesserocr库应该可以解决.

Python处理PDF-通过关键词定位-截取PDF中的图表的更多相关文章

  1. 参考分享《Python深度学习》高清中文版pdf+高清英文版pdf+源代码

    学习深度学习时,我想<Python深度学习>应该是大多数机器学习爱好者必读的书.书最大的优点是框架性,能提供一个"整体视角",在脑中建立一个完整的地图,知道哪些常用哪些 ...

  2. Python 3网络爬虫开发实战中文PDF+源代码+书籍软件包(免费赠送)+崔庆才

    Python 3网络爬虫开发实战中文PDF+源代码+书籍软件包+崔庆才 下载: 链接:https://pan.baidu.com/s/1H-VrvrT7wE9-CW2Dy2p0qA 提取码:35go ...

  3. python爬虫处理在线预览的pdf文档

    引言 最近在爬一个网站,然后爬到详情页的时候发现,目标内容是用pdf在线预览的 比如如下网站: https://camelot-py.readthedocs.io/en/master/_static/ ...

  4. Python程序设计(第3版)PDF高清完整版免费下载|百度网盘

    百度网盘:Python程序设计(第3版)PDF高清完整版免费下载 提取码:48u4 内容简介 本书是面向大学计算机科学专业第一门程的教材.本书以Python语言为工具,采用相当传统的方法,强调解决问题 ...

  5. 《Python金融大数据分析》高清PDF版|百度网盘免费下载|Python数据分析

    <Python金融大数据分析>高清PDF版|百度网盘免费下载|Python数据分析 提取码:mfku 内容简介 唯一一本详细讲解使用Python分析处理金融大数据的专业图书:金融应用开发领 ...

  6. 付费?是不可能的!20行Python代码实现一款永久免费PDF编辑工具

    PDF(Portable Document Format),中文名称便携文档格式是我们经常会接触到的一种文件格式,文献.文档…很多都是PDF格式.它以格式稳定的优势,使得我们在打印.分享.传输过程中能 ...

  7. python数据处理(三)之处理pdf文件

    代码以及资料 https://github.com/jackiekazil/data-wrangling 1.前言 尽可能地寻找可以替代pdf格式的数据 2.解析pdf的编程方法 安装slate pi ...

  8. C# 复制PDF页面到另一个PDF文档

    C# 复制PDF页面到另一个PDF文档 有时候我们可能有这样一个需求,那就是把PDF页面从一个PDF文档复制到另一个PDF文档中.由于PDF文档并不像word文档那样好编辑,因此复制也相对没有那么容易 ...

  9. selenium python (七)层级定位(二次定位)

    #!/usr/bin/python# -*- coding: utf-8 -*-__author__ = 'zuoanvip' #在实际测试过程中,一个页面可能有多个属性基本相同的元素,如果要定位到其 ...

随机推荐

  1. 笔记-JavaWeb学习之旅

    junit单元测试 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值 白盒测试:需要些代码,关注程序具体的执行流程 Junit使用: 白盒测试 ​ 步骤: 定义一个测试类(测试用例) 定义 ...

  2. Luogu P2326 AKN's PPAP【按位贪心】

    题目描述 “I have a pen,I have an apple.Eh,Apple-Pen!. I have a pen,I have pineapple.En,Pineapple-Pen! Ap ...

  3. plsqldeveloper永久注册码

    注册码:Product Code:4t46t6vydkvsxekkvf3fjnpzy5wbuhphqzserial Number:601769 password:xs374ca

  4. 分布式集群环境下,如何实现session共享三(环境搭建)

    这是分布式集群环境下,如何实现session共享系列的第三篇.在上一篇:分布式集群环境下,如何实现session共享二(项目开发)中,准备好了一个通过原生态的servlet操作session的案例.本 ...

  5. EditPlus 3:设置自动换行

    打开软件,菜单栏点击Document,再在下拉栏中点击Permanent Settings,然后在弹出的设置框中找到Word Wrap点击,最后在弹出的小框中勾选第一个Enable word wrap ...

  6. Codeforces Round #418 (Div. 2) B

    Description Sengoku still remembers the mysterious "colourful meteoroids" she discovered w ...

  7. System.Web.Mvc 和 using System.Net.Http 的 Filter

    在尝试给webapi增加 ExceptionFilter时,出现了错误,经查询区别如下: System.Web.Mvc.Filters 是给mvc用的 System.Web.Http.Filters ...

  8. iphone6,键盘收起,H5页面下面出现空白

  9. OkHttp下载文件中途断网报Can't create handler inside thread that has not called Looper.prepare()异常的解决办法

    最近做项目时出现个问题. 在一个基类中,创建一个Handler对象用于主线程向子线程发送数据,代码如下: this.mThirdHandler = new Handler(){ @Override p ...

  10. Joystick

    Joystick相当于5个按键的集合,向上.下.左.右.中间5个方向接通,经常用于游戏场合.