python 原理 pdf_利用Python处理PDF——裁剪和生成新的PDF

安装

在cmd中输入这一句:

pip install -U pdfminer3k -i https://pypi.tuna.tsinghua.edu.cn/simple --user

注意:这里安装的是pdfminer3k而不是pdfminer。不小心安装了pdfminer(pip install pdfminer)的同学,请回到你安装包的文件夹中(类似这个文件夹:....\Python\Python37\site-packages),手动把含有PDF的包删光,再重新回到cmd输入安装代码。

2. pdfminer的使用

2.1 简要介绍PDF的结构

PDF和word、HTML均不同,因为pdf更像一个图形代表。PDF就是一群指令的集合、用来声明了在哪里放置这些图形以及文字。因此PDFminer是尝试“猜”对象的位置、进行解析结构,但不能保证一定起作用。

PDFminer采用的是懒惰的解析方式。至少需要用到两个类 PDFParser以及 PDFDocument。这两个类相互关联。PDFParser从文件中抽取数据,PDFDocument用来储存数据。同时,需要用到PDFPageInterpreter处理页面内容,以及使用PDFDevice将其转化为用户最终需要的东西。以及PDFResourceManager用来储存共享资源,例如字体和图片。图1:PDFMiner工作原理

一个懒惰的分析器返回的是PDF文件中的每一个的LTPage对象。这个对象包含了子类对象,储存了该页的所有结构。LTPage:代表是一个完整的页码,子类包含了LTTextBox, LTFigure, LTImage, LTRect, LTCurve and LTLine。LTTextBox:表示的是文本块,一般储存在矩形区域。LTTextBox中又包含了LTTextLine的列表,通过.get_text()方法能够将整个text抽出来。LTTextLine:包含了LTChar对象,表示的是一行文本,通过.get_text()将文本抽出来。LTChar:是一行中一个字。

LTFigure:是被嵌在该pdf中的区域,可以递归出现。

LTImage:代表是图片对象,可以是JEPG或者其他格式。

LTLine:表示直线,用于分隔区域。

LTRect:代表矩形,用于框住某个对象。

LTCurve:Bezier curve

2.2 PDFMiner基本语法

from pdfminer.pdfparser import PDFParser

from pdfminer.pdfdocument import PDFDocument

from pdfminer.pdfpage import PDFPage

from pdfminer.pdfpage import PDFTextExtractionNotAllowed

from pdfminer.pdfinterp import PDFResourceManager

from pdfminer.pdfinterp import PDFPageInterpreter

from pdfminer.pdfdevice import PDFDevice

# 打开pdf

path = r".../某个.pdf"

fp = open(pdf, 'rb')

# 创建和该文件对象相关的PDF分析器。

parser = PDFParser(fp)

# 通过PDFDocument创建PDF文件对象,储存该文件的结构。

document = PDFDocument()

# 连接分析器与文档对象

parser.set_document(document)

document.set_parser(parser)

# 提供初始化密码doc.initialize(password)

# 如果没有密码 就创建一个空的字符串

doc.initialize()

# 检测文档是否提供txt转化;不允许的话只能够终止转化。

if not doc.is_extractable:

raise PDFTextExtractionNotAllowed

# 如果可以txt转化

else:

# 创建pdf资源管理器,管理该pdf的共享资源

rsrcmgr = PDFResourceManager()

# 创建用于分析的参数

laparams = LAParams()

# 创建一个pdf页面整合器

device = PDFPageAggregator(rsrcmgr, laparams=laparams)

# 创建一个pdf转换对象

interpreter = PDFPageInterpreter(rsrcmgr, device)

# 循环遍历列表,每次处理一页page的内容

total_page = doc.get_pages()

for page in total_page:

#在第page页中,解析该页的所有结构

interpreter.process_page(page)

# 接受该页的整体布局layout对象

layout = device.get_result()

# layout是一个LTPage对象,里面存着这个page解析出来的各种对象,

# 包括了LTTextBox, LTFigure, LTImage, LTTextBoxHorizontal 等

for x in layout:

if isinstance(x, LTTextBox):

# 那么这里只关注是不是储存文本的box,如果是的话,那么输出该页的文本。

print(x.get_text())

在使用pdfminer的时候,往往会出现这种警告

如果介意并且不想要输出的话,找到...\Python\Python37\site-packages\pdfminer的文件夹,然后修改layout.py文件中的源代码

if len(boxes) > 100:

# Grouping this many boxes would take too long and it doesn't make much sense to do so

# considering the type of grouping (nesting 2-sized subgroups) that is done here.

logger.warning("Too many boxes (%d) to group, skipping.", len(boxes))

把">100"改成">10000",修改后如下

if len(boxes) > 10000:

# Grouping this many boxes would take too long and it doesn't make much sense to do so

# considering the type of grouping (nesting 2-sized subgroups) that is done here.

logger.warning("Too many boxes (%d) to group, skipping.", len(boxes))

2.3 一个小实验

2.3.1 安装PyPDF2包

那么,像一些较为有趣的实验,例如找出整个pdf中含有某个关键字的几页,再把包含了这几页的页面提取出来生成一个新的PDF。裁剪pdf需要用到PyPDF2包。先通过cmd安装。

pip install -U PyPDF2 -i https://pypi.tuna.tsinghua.edu.cn/simple --user

2.3.2 修改PyPDF2的源代码

在安装好了PyPDF2之后、调用PyPDF2之前, 需要修改PyPDF2的某些源代码,因为裁剪中文的PDF会出现乱码现象

找到\...\Python37\site-packages\PyPDF2的文件夹,然后修改两个.py文件——generic.py以及utils.py。

首先,定位到generic.py的第483行,原始的代码如下:

try:

return NameObject(name.decode('utf-8'))

except (UnicodeEncodeError, UnicodeDecodeError) as e:

# Name objects should represent irregular characters

# with a '#' followed by the symbol's hex number

if not pdf.strict:

warnings.warn("Illegal character in Name Object", utils.PdfReadWarning)

return NameObject(name)

else:

raise utils.PdfReadError("Illegal character in Name Object")

需要将其修改为

try:

return NameObject(name.decode('utf-8'))

except (UnicodeEncodeError, UnicodeDecodeError) as e:

try:

return NameObject(name.decode('gbk'))

except (UnicodeEncodeError, UnicodeDecodeError) as e:

# Name objects should represent irregular characters

# with a '#' followed by the symbol's hex number

if not pdf.strict:

warnings.warn("Illegal character in Name Object", utils.PdfReadWarning)

return NameObject(name)

else:

raise utils.PdfReadError("Illegal character in Name Object")

保存并退出。

然后修改第二个文件utils.py。找到第238行。原始代码如下:

r = s.encode('latin-1')

if len(s) < 2:

bc[s] = r

return r

需要将其修改为

try:

r = s.encode('latin-1')

except Exception as e:

r = s.encode('utf-8')

if len(s) < 2:

bc[s] = r

return r

保存并退出。

然后打开python编辑器。

2.3.3 完整代码

完整代码如下。随便找个pdf放在input路径下。

from PyPDF2 import PdfFileWriter, PdfFileReader

from pdfminer.pdfparser import PDFParser, PDFDocument

from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter

from pdfminer.converter import PDFPageAggregator

from pdfminer.layout import LAParams, LTTextBox

from pdfminer.pdfinterp import PDFTextExtractionNotAllowed

def get_new_pdf_by_keyword(i_path, o_path, keyword):

# i_path:是原始待处理的pdf文件所在路径

# o_path:是即将最终生成的pdf的路径

# keyword:是一个list,存放各种关键词

fp = open(i_path, 'rb')

parser = PDFParser(fp)

doc = PDFDocument()

parser.set_document(doc)

doc.set_parser(parser)

doc.initialize()

if not doc.is_extractable:

raise PDFTextExtractionNotAllowed

else:

rsrcmgr = PDFResourceManager()

laparams = LAParams()

device = PDFPageAggregator(rsrcmgr, laparams=laparams)

interpreter = PDFPageInterpreter(rsrcmgr, device)

total_page = doc.get_pages()

list_pages = []

for i, page in enumerate(total_page):

interpreter.process_page(page)

layout = device.get_result()

for x in layout:

if isinstance(x, LTTextBox):

text = x.get_text()

for kw in keyword:

if kw in text:

list_pages.append(i)

# 把页码排个序,且保留唯一页码

list_pages = sorted(set(list_pages))

# 只有在页码超过1个,才进行裁剪;否则没有页码裁剪,即为返回2的情况。

if len(list_pages) > 0:

# 使用PyPDF2进行裁剪pdf 并且输出新的pdf。

# 打开pdf文件

pdf_file = PdfFileReader(fp)

# 创建一个pdf文档,这个只是代表pdf文档的值,并没有创建实际的文档。

pdf_writer = PdfFileWriter()

for i in list_pages:

pageobj = pdf_file.getPage(i)

pdf_writer.addPage(pageobj)

try:

pdfoutputfile = open(o_path, 'wb')

pdf_writer.write(pdfoutputfile)

pdfoutputfile.close()

fp.close()

return 1

except:

fp.close()

return 0

else:

# 没有找到页码的时候,返回2。

fp.close()

return 2

传入参数:例如,我对年报中“偷税”“漏税”“重大风险”的页面感兴趣,想要提取出来生成新的pdf。

input_path = r'C:\...\年报及精准扶贫企业名单\000001.pdf'

output_path = r'C:\...\年报及精准扶贫企业名单\result.pdf'

keyword = ["偷税", "漏税", "风险"]

get_new_pdf_by_keyword(input_path, output_path, keyword)

在output_path中就有新的pdf生成。

好啦,希望可以帮到大家~~

PS:在下文科生,有不足之处,希望大家包容和提出宝贵意见哈。

参考

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值