网站链接: element-ui dtcms
当前位置: 首页 > 技术博文  > 技术博文

python office(4) 批量pdf读取到破解

2021/6/24 1:10:26 人评论

PDF 操作之所以很重要,是因为各种年报的公布数据都是以 PDF 格式发布,这个时候对于有大批量年报数据时, 就急需使用程序来提取这些年报中的关键数据 PDF 操作是本次自动化办公的最后一个知识点,初级的 PDF 自动化包括 PDF 文档的…

PDF 操作之所以很重要,是因为各种年报的公布数据都是以 PDF 格式发布,这个时候对于有大批量年报数据时, 就急需使用程序来提取这些年报中的关键数据

PDF 操作是本次自动化办公的最后一个知识点,初级的 PDF 自动化包括 PDF 文档的拆分、合并、提取等操作,更高级的还包括 WORD与PDF互转等

更好的读取、写入、分割、合并PDF文件Python 操作 PDF 会用到两个库,分别是:PyPDF2【更好的读取、写入、分割、合并PDF文件】 和 pdfplumber【读取 PDF 文件中内容和提取 PDF 中的表格】

PyPDF2:https://pythonhosted.org/PyPDF2/

pdfplumber:https://github.com/jsvine/pdfplumber

2. 批量拆分

将一个完整的 PDF 拆分成几个小的 PDF,因为主要涉及到 PDF 整体的操作,所以本小节需要用到 PyPDF2 这个库

拆分的大概思路如下:

  • 读取 PDF 的整体信息、总页数等
  • 遍历每一页内容,以每个 step 为间隔将 PDF 存成每一个小的文件块
  • 将小的文件块重新保存为新的 PDF 文件

需要注意的是,在拆分的过程中,可以手动设置间隔,例如:每5页保存成一个小的 PDF 文件

import os
from PyPDF2 import PdfFileReader, PdfFileWriter
def split_pdf(filename, filepath, save_dirpath, step=5):
    """
    拆分PDF为多个小的PDF文件,
    @param filename:文件名
    @param filepath:文件路径
    @param save_dirpath:保存小的PDF的文件路径
    @param step: 每step间隔的页面生成一个文件,例如step=5,表示0-4页、5-9页...为一个文件
    @return:
    """
    if not os.path.exists(save_dirpath):
        os.mkdir(save_dirpath)
    pdf_reader = PdfFileReader(filepath)
    # 读取每一页的数据
    pages = pdf_reader.getNumPages()
    for page in range(0, pages, step):
        pdf_writer = PdfFileWriter()
        # 拆分pdf,每 step 页的拆分为一个文件
        for index in range(page, page+step):
            if index < pages:
                pdf_writer.addPage(pdf_reader.getPage(index))
        # 保存拆分后的小文件
        save_path = os.path.join(save_dirpath, filename+str(int(page/step)+1)+'.pdf')
        print(save_path)
        with open(save_path, "wb") as out:
            pdf_writer.write(out)

    print("文件已成功拆分,保存路径为:"+save_dirpath)
split_pdf('1', './germany_2019.pdf', './pdf', 20)
./pdf/11.pdf
./pdf/12.pdf
./pdf/13.pdf
./pdf/14.pdf
./pdf/15.pdf
./pdf/16.pdf
./pdf/17.pdf
文件已成功拆分,保存路径为:./pdf

3. 批量合并

比起拆分来,合并的思路更加简单:

  • 确定要合并的 文件顺序
  • 循环追加到一个文件块中
  • 保存成一个新的文件
def concat_pdf(filename, read_dirpath, save_filepath):
    """
    合并多个PDF文件
    @param filename:文件名
    @param read_dirpath:要合并的PDF目录
    @param save_filepath:合并后的PDF文件路径
    @return:
    """
    pdf_writer = PdfFileWriter()
    # 对文件名进行排序
    list_filename = os.listdir(read_dirpath)
    list_filename.sort(key=lambda x: int(x[:-4].replace(filename, "")))
    for filename in list_filename:
        print(filename)
        filepath = os.path.join(read_dirpath, filename)
        # 读取文件并获取文件的页数
        pdf_reader = PdfFileReader(filepath)
        pages = pdf_reader.getNumPages()
        # 逐页添加
        for page in range(pages):
            pdf_writer.addPage(pdf_reader.getPage(page))
    # 保存合并后的文件
    with open(save_filepath, "wb") as out:
        pdf_writer.write(out)
    print("文件已成功合并,保存路径为:"+save_filepath)
concat_pdf('new.pdf', './pdf/', './')
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-43-0484669f84db> in <module>
----> 1 concat_pdf('new.pdf', './pdf/', './')


<ipython-input-42-282d143518c2> in concat_pdf(filename, read_dirpath, save_filepath)
     10     # 对文件名进行排序
     11     list_filename = os.listdir(read_dirpath)
---> 12     list_filename.sort(key=lambda x: int(x[:-4].replace(filename, "")))
     13     for filename in list_filename:
     14         print(filename)


<ipython-input-42-282d143518c2> in <lambda>(x)
     10     # 对文件名进行排序
     11     list_filename = os.listdir(read_dirpath)
---> 12     list_filename.sort(key=lambda x: int(x[:-4].replace(filename, "")))
     13     for filename in list_filename:
     14         print(filename)


ValueError: invalid literal for int() with base 10: '.DS_S'

4. 提取文字内容

涉及到具体的 PDF 内容 操作,本小节需要用到 pdfplumber 这个库

在进行文字提取的时候,主要用到 extract_text 这个函数

import pdfplumber
def extract_text_info(filepath):
    """
    提取PDF中的文字
    @param filepath:文件路径
    @return:
    """
    with pdfplumber.open(filepath) as pdf:
        # 获取第2页数据
        page = pdf.pages[1]
        print(page.extract_text())
extract_text_info('./germany_2019.pdf')
Impressum
Herausgeber
Bundesministerium für Wirtschaft und Energie (BMWi)
Öffentlichkeitsarbeit
11019 Berlin
www.bmwi.de
Stand
Juni 2020
Gestaltung
PRpetuum GmbH, 80801 München
Diese und weitere Broschüren erhalten Sie bei:
Bundesministerium für Wirtschaft und Energie
Referat Öffentlichkeitsarbeit
E-Mail: publikationen@bundesregierung.de
www.bmwi.de
Zentraler Bestellservice:
Telefon: 030 182722721
Bestellfax: 030 18102722721
Diese Publikation wird vom Bundesministerium für Wirtschaft und 
Energie im Rahmen der Öffentlichkeitsarbeit herausgegeben. Die Publi-
kation wird kostenlos abgegeben und ist nicht zum Verkauf bestimmt. 
Sie darf weder von Parteien noch von Wahlwerbern oder Wahlhelfern 
während eines Wahlkampfes zum Zwecke der Wahlwerbung verwendet 
werden. Dies gilt für Bundestags-, Landtags- und Kommunalwahlen 
sowie für Wahlen zum Europäischen Parlament.
# 提取所有页面
with pdfplumber.open(filepath) as pdf:
	# 获取全部数据
	for page in pdf.pages:
    	print(page.extract_text())
        

5. 提取表格内容【这个很有用!】

同样的,本节是对具体内容的操作,所以也需要用到 pdfplumber 这个库

和提取文字十分类似的是,提取表格内容只是将 extract_text 函数换成了 extract_table 函数

def extract_table_info(filepath):
    """
    提取PDF中的图表数据
    @param filepath:
    @return:
    """
    with pdfplumber.open(filepath) as pdf:
        # 获取第18页数据
        page = pdf.pages[17]
        # 如果一页有一个表格,设置表格的第一行为表头,其余为数据
        table_info = page.extract_table()
        df_table = pd.DataFrame(table_info[1:], columns=table_info[0])
        df_table.to_csv('dmeo.csv', index=False, encoding='gbk')

上面代码可以获取到第 18 页的第一个表格内容,并且将其保存为 csv 文件存在本地

但是,如果说第 18 页有多个表格内容呢?

因为读取的表格会被存成二维数组,而多个二维数组就组成一个三维数组

遍历这个三位数组,就可以得到该页的每一个表格数据,对应的将 extract_table 函数 改成 extract_tables 即可

# 如果一页有多个表格,对应的数据是一个三维数组
tables_info = page.extract_tables()
for index in range(len(tables_info)):
#     设置表格的第一行为表头,其余为数据
    df_table = pd.DataFrame(tables_info[index][1:], columns=tables_info[index][0])
    print(df_table)
    # df_table.to_csv('dmeo.csv', index=False, encoding='gbk')

6. 提取图片内容

提取 PDF 中的图片和将 PDF 转存为图片是不一样的(下一小节),需要区分开。

转存为图片中,需要用到一个模块叫 fitz,fitz 的最新版 1.18.13,非最新版的在部分函数名称上存在差异,代码中会标记出来

使用 fitz 需要先安装 PyMuPDF 模块,安装方式如下:

pip install PyMuPDF

提取图片的整体逻辑如下:

  • 使用 fitz 打开文档,获取文档详细数据
  • 遍历每一个元素,通过正则找到图片的索引位置
  • 使用 Pixmap 将索引对应的元素生成图片
  • 通过 size 函数过滤较小的图片
if not os.path.exists(pic_dirpath):
    os.makedirs(pic_dirpath)
# 使用正则表达式来查找图片
check_XObject = r"/Type(?= */XObject)"
check_Image = r"/Subtype(?= */Image)"
img_count = 0

"""1. 打开pdf,打印相关信息"""
pdf_info = fitz.open(filepath)
# 1.16.8版本用法 xref_len = doc._getXrefLength()
# 最新版本写法
xref_len = pdf_info.xref_length()
# 打印PDF的信息
print("文件名:{}, 页数: {}, 对象: {}".format(filepath, len(pdf_info), xref_len-1))

"""2. 遍历PDF中的对象,遇到是图像才进行下一步,不然就continue"""
for index in range(1, xref_len):
    # 1.16.8版本用法 text = doc._getXrefString(index)
    # 最新版本
    text = pdf_info.xref_object(index)
    
    is_XObject = re.search(check_XObject, text)
    is_Image = re.search(check_Image, text)
    # 如果不是对象也不是图片,则不操作
    if is_XObject or is_Image:
        img_count += 1
        # 根据索引生成图像
        pix = fitz.Pixmap(pdf_info, index)
        pic_filepath = os.path.join(pic_dirpath, 'img_' + str(img_count) + '.png')
        """pix.size 可以反映像素多少,简单的色素块该值较低,可以通过设置一个阈值过滤。以阈值 10000 为例过滤"""
        # if pix.size < 10000:
        #     continue
        
        """三、 将图像存为png格式"""
        if pix.n >= 5:
            # 先转换CMYK
            pix = fitz.Pixmap(fitz.csRGB, pix)
        # 存为PNG
        pix.writePNG(pic_filepath)

7. 转换为图片

转换为照片比较简单,就是将一页页的 PDF 转换为一张张的图片。大致过程如下:

7.1 安装 pdf2image

Mac

对于 mac 用户,需要安装 poppler for Mac,具体可以参考这个链接:http://macappstore.org/poppler/



if not os.path.exists(pic_dirpath):
    os.makedirs(pic_dirpath)

images = convert_from_bytes(open(filepath, 'rb').read())
# images = convert_from_path(filepath, dpi=200)
for image in images:
    # 保存图片
    pic_filepath = os.path.join(pic_dirpath, 'img_'+str(images.index(image))+'.png')
    image.save(pic_filepath, 'PNG')

8. 添加水印

PDF 中添加水印,首先需要一个水印PDF文件,然后依次通过 mergePage 操作将每一页的 PDF 文件合并到水印文件上,据此,每一页的 PDF 文件将是一个带有水印的 PDF 文件

最后,将每一页的水印 PDF 合并成一个 PDF 文件即可

生成水印

生成水印的方式比较多,例如在图片添加水印,然后将图片插入到 word 中,最后将 word 保存成 PDF 文件即可

生成一张 A4 纸大小的空白图片,参考这篇文章:[Python 批量加水印!轻松搞定!]

watermark = PdfFileReader(watermark_filepath)
watermark_page = watermark.getPage(0)

pdf_reader = PdfFileReader(filepath)
pdf_writer = PdfFileWriter()

for page_index in range(pdf_reader.getNumPages()):
    current_page = pdf_reader.getPage(page_index)
    # 封面页不添加水印
    if page_index == 0:
        new_page = current_page
    else:
        new_page = copy(watermark_page)
        new_page.mergePage(current_page)
    pdf_writer.addPage(new_page)
# 保存水印后的文件
with open(save_filepath, "wb") as out:
    pdf_writer.write(out)

9. 文档加密与解密【解密!!!】

·你可能在打开部分 PDF 文件的时候,会弹出下面这个界面:

pdf_reader = PdfFileReader(filepath)
pdf_writer = PdfFileWriter()

for page_index in range(pdf_reader.getNumPages()):
    pdf_writer.addPage(pdf_reader.getPage(page_index))

# 添加密码
pdf_writer.encrypt(passwd)
with open(save_filepath, "wb") as out:
    pdf_writer.write(out)b
    

根据这个思路,破解 PDF 也可以通过暴力求解实现,例如:通过本地密码本一个个去尝试,或者根据数字+字母的密码形式循环尝试,最终成功打开的密码就是破解密码

上述破解方法耗时耗力,不建议尝试

pdf_reader = PdfFileReader(filepath)
# PDF文档解密
pdf_reader.decrypt('xiaoyi')

pdf_writer = PdfFileWriter()
for page_index in range(pdf_reader.getNumPages()):
    pdf_writer.addPage(pdf_reader.getPage(page_index))

with open(save_filepath, "wb") as out:
    pdf_writer.write(out)

相关资讯

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?