上干货!教你用Python GUI框架生成图片提取器!WORD、PDF都能用上它!

前言:

大家好,今天教大家做一个综合性较强的Python实战练习:用Python提取PDF与Word中的图片

要想实现我们今天的主题,首先不得不涉及到Python中一个非常常见的框架内容GUI,以前我们在文章中也有讲过一些涉及 python GUI 框架的开发案例;其次是利用Python进行excel/word/pdf 等文档的读写,这一内容在以前的文章中也有讲解。那么今天我们需要做的,便是将这两个内容灵活的结合起来,做出分析,并结合GUI框架PysimpleGUI,做一个多文件图片提取软件。
如图所示:

本文主要将分为以下部分讲解:

  • PDF、Word 文件图片提取
  • 构造图片提取器GUI框架
  • 整合代码并打包


主要涉及的Python模块有:

  • PIL
  • PySimpleGUI
  • re
  • win32
  • os
  • zipfile
  • fitz

模块安装

首先使用pip安装相关依赖模块

pip install pillow   #这是对模块PTL的安装
pip install pypiwin32    #这是对win32的安装
pip install os 
pip install zipfile
pip install PyMuPDF  #这是引用fitz对PDF操作的包
pip install PySimpleGui

一、提取各文件内嵌图片

读取Excel有两种方法。一种是将后缀名改成.zip格式进行提取,一种是通过Pillow模块对Excel进行图片复制与保存。Word提取图片方法和通过.zip提取方法类似,PDF提取图片方法要用到专门的模块。这篇文章里只对PDF和Word的提取方法展开讲解,Excel 的读取方法类似,可参见代码。

1.1 提取Word图片

我们先看代码

path = values["lujing"]  
count = 1
for file in os.listdir(path):
    new_file = file.replace(".docx",".zip")
    os.rename(os.path.join(path,file),os.path.join(path,new_file))
    count+=1      
    number = 0
    craterDir = values["lujing"] + '/'  # 存放zip文件的文件夹路径
    saveDir = values["lujing"] + '/' # 存放图片的路径
    list_dir = os.listdir(craterDir) # 获取所有的zip文件名
    for i in range(len(list_dir)):
        if 'zip' not in list_dir[i]:
            list_dir[i] = ''
            while '' in list_dir:
                list_dir.remove('')                            
                for zip_name in list_dir:
                    # 默认模式r,读
                    azip = zipfile.ZipFile(craterDir + zip_name)
                    # 返回所有文件夹和文件
                    namelist = (azip.namelist())
                    for idx in range(0,len(namelist)):
                        if namelist[idx][:11] == 'word/media/':#图片是在这个路径下
                            img_name = saveDir + str(number)+'.jpg'
                            f = azip.open(namelist[idx])
                            img = Image.open(f)
                            img = img.convert("RGB")
                            img.save(img_name,"JPEG")
                            number += 1
                            azip.close()  #关闭文件,必须有,释放内存

这里的代码和通过.zip方式提取Excel图片的代码思路是一样的。

path = values["lujing"]这里是读取GUI中键为“lujing”的值,也即文件存储位置,用于os模块读取与操作。

new_file = file.replace(".docx",".zip")是替换后缀名,如果是Excel的话,就把.docx改成.xlsxxls

craterDir = values["lujing"] + '/' 这是存放zip文件的文件夹路径,注意这里读取到的键为“lujing”的值后要在后面添加/

saveDir = values["lujing"] + '/' 这是存放图片的路径,同理,和上面一样加个/号。

最后说一下与Excel提取相比,最大的不同是下面的代码

if namelist[idx][:11] == 'word/media/':

中括号里面的值不一样。很好理解,我们可以打印namelist[idx],可以发现在索引0到10是'word/media/'所在位置。而在Excel中是前9位。

1.2 提取PDF图片

先来看看相关代码:

def pdf2pic(path, pic_path):
    doc = fitz.open(path)
    nums = doc._getXrefLength()
    imgcount = 0 
    for i in range(1, nums):
        text = doc._getXrefString(i)
        if ('Width 2550' in text) and ('Height 3300' in text) or ('thumbnail' in text):
            continue
            checkXO = r"/Type(?= */XObject)"
            checkIM = r"/Subtype(?= */Image)"
            isXObject = re.search(checkXO, text)
            isImage = re.search(checkIM, text)
            if not isXObject or not isImage:
                continue
                imgcount += 1
                pix = fitz.Pixmap(doc, i)
                img_name = "img{}.png".format(imgcount)
                if pix.n < 5:
                    try:
                        pix.writePNG(os.path.join(pic_path, img_name))
                        pix = None
                        except:
                            pix0 = fitz.Pixmap(fitz.csRGB, pix)
                            pix0.writePNG(os.path.join(pic_path, img_name))
                            pix0 = None
if __name__ == '__main__':
    path = values["lujing"]+ '/' + values["wenjian"]
    pic_path = values["lujing"]
    pdf2pic(path, pic_path)

先说一下这段代码的思路吧,由于PDF不能像Excel和Word一样改后缀名进行提取,故这里运用python的一个模块PyMuPDF,过程如下

  • 读取PDF并遍历每一页
  • 筛选无用的元素并用正则表达式获取图片
  • 生成并保存图片

fitz.open(path)是打开PDF文件夹,这里的path是需要在GUI界面中获取用户的文件存放路径与文件名的。

for i in range(1, nums):
    text = doc._getXrefString(i)

这是我们的第一步读取并遍历,将读取到的字符串内容放入到text中

if ('Width 2550' in text) and ('Height 3300' in text) or ('thumbnail' in text):
 continue

由于PDF每一页的背景就是一张图片,故我们可以通过宽高来寻找这些背景图片并用continue把他们剔除。

checkXO = r"/Type(?= */XObject)"
checkIM = r"/Subtype(?= */Image)"
isXObject = re.search(checkXO, text)
isImage = re.search(checkIM, text)
if not isXObject or not isImage:
    continue

我们通过实验发现图片所对应的字符串在checkxocheckIM这两个中。故用正则表达式在text中进行索引提取,用到了re模块的search函数。如果不是这两个字符串就用continue剔除。

pix = fitz.Pixmap(doc, i)
img_name = "img{}.png".format(imgcount)
if pix.n < 5:
 try:
  pix.writePNG(os.path.join(pic_path, img_name))
  pix = None
 except:
  pix0 = fitz.Pixmap(fitz.csRGB, pix)
  pix0.writePNG(os.path.join(pic_path, img_name))
  pix0 = None

这是最后一步生成与保存图片

pix = fitz.Pixmap(doc, i)是生成图像语句,doc代表PDF文件,i代表遍历每个图片对象的索引值。

img_name = "img{}.png".format(imgcount)是设置图像名语句,用提取到的图片的序列号作为命名格式。

而后if嵌套try那几行代码是保存图像语句。如果pix.n<5,可以直接存为PNG,否者将进行RGB变换在保存。

最后,传递参数,调用函数。

完成了图片的提取,接下来就是 GUI 的构建了。

二、GUI框架的构建

先看完整代码:

import PySimpleGUI as sg
sg.ChangeLookAndFeel('GreenTan')   #更换主题
menu_def = [['&使用说明', ['&注意']]]
layout = [
    [sg.Menu(menu_def, tearoff=True)],
    [sg.Frame(layout=[
    [sg.Radio('Excel1', "RADIO1",size=(10,1),key="Excel1"),  sg.Radio('Word', "RADIO1",default=True,key="Word")],
    [sg.Radio('Excel2', "RADIO1", enable_events=True, size=(10,1),key="Excel2"), sg.Radio('PDF', "RADIO1",key="PDF")]], title='选项',title_color='red', relief=sg.RELIEF_SUNKEN, tooltip='Use these to set flags')],
    [sg.Text('文件位置', size=(8, 1), auto_size_text=False, justification='right'),
     sg.InputText(enable_events=True,key="lujing"), sg.FolderBrowse()],
    [sg.Text('文件名字', size=(8, 1), justification='right'),
     sg.InputText(enable_events=True,key="wenjian")],
    [sg.Submit(tooltip='文件'), sg.Cancel()]]

window = sg.Window('图片提取器', layout, default_element_size=(40, 1), grab_anywhere=False)
while True:
    event, values = window.read()
    if event == "Submit":
        if values["Excel2"] == True:
   '''
   事件绑定
   '''
            sg.Popup("提取成功")

        if values["Excel1"] == True:
   '''
   事件绑定
   '''
            sg.Popup("提取成功")
        if values["Word"] == True:
   '''
   事件绑定
   '''
            sg.Popup("提取成功") 
        if values["PDF"] == True:
   '''
   事件绑定
   '''
            sg.Popup("提取成功")
    if event == "Cancel" or event == sg.WIN_CLOSED:
        break    
    if event == "注意":
        sg.Popup("作用讲解:",
                 "Excel1 :解析选定位置中所有的Excel文件,无需在文件名处填写",
                 "Excel2 :解析选定位置中单个指定的Excel文件,需在文件名处填写",
                 "Word :  解析选定位置中单个指定的docx结尾的文件,无需在文件名处填写",
                 "PDF :   解析选定位置中单个指定的PDF文件,需在文件名处填写")
window.close()           

效果呈现:

代码解析:做PySimpleGUI还是原来那个步骤:

Import ☞ Create some widgets ☞ Create the window ☞ Create the event loop

当然,做GUI之前就是先想清楚自己的图形交互页面长什么样,像我就是现在纸上画一个大概,这样更有益于制作页面

第一步:引用模块

第二步:添加元素(小部件)到容器(layout)中,这里先介绍一下用到的部件:

Menu:顾名思义,这是菜单栏,每个GUI都必带一个菜单栏来提示使用者该如何做,我们这里用了menu_def这个布局来完成菜单的设置。

Frame:这个跟layout布局完全相同,工作方式也一样,他们都是容器元素。可以看到“选项”那里是一个凹槽的正方形,里面装有四个选项,作用就是这个。注意,&这个符号的作用是创建相同类型的菜单,这里只有注意事项这一个菜单,故可以不用管,读者如果想添加同样的菜单的话必须添加一个&tearoff=True这个参数是菜单栏中每个子选项上面加虚线。

Radio:单选按钮。我们只可以在同样的id上选择一个选项。id就是指代码中的“ra-dio1”。其中每个radio函数的第一个参数是文本内容,这里就是我们要进行提取的4个文件格式。而“size”就是位置,每行的第一个设同样的参数(10,1)。最后就是我们进行事件绑定的键,其中“enable_events”可以不写因为我们只是调用它而不用去对它产生事件。

Text:之前有讲是不能改的正文内容。同样这里设置的位置参数(8,1),justification='right'有点类似我们平常用word那个向右对齐。

InputText:需要用户输入的正文内容。这里有两个需要我们填写的地方:文件位置和文件名。这里需要设置键,因为在后面事件绑定中我们需要调用文件存储路径和文件名,在文中上半部分有提到过。

FolderBrowse:简易的打开文件路径操作,不用你去复制路径。

Submit:确定按钮,这里绑定为执行提取文档图片事件

Cancel:退出主程序按钮。

第三步:创建窗口来容纳这些元素布置。

第四步:创建事件循环,可以看到代码,都是一样的套路:当用户按下submit按钮时系统将进行判断你按的是哪个单选按钮,进而进行相对应的事件执行。当你按下cancel或者×时,就是退出主程序。当你按菜单中的注意时,就会弹出一个对话框告诉你这个系统怎么用。

在事件循环中,我们用values[]的布尔值来判断我们选的是哪个单选按钮,有读者疑问为什么不用event=,因为我们在第一个if当中用了event所以第二个if当中需要换一个判断方法。

至此,GUI部分就搞定了!感兴趣的读者可以继续在上面添加功能。

三、整合代码并打包

我们将完整代码整合在一起,后安装pyinstaller模块

pip install pyinstaller

如果你的上述项目代码文件命名为:photo.py。那么你要用下面命令进行打包

pyinstaller photo.py

最后打包成功之后,你会在py文件所在文件夹看到一个dist的子文件夹。进去之后,找到pachong.exe文件并运行它即可。

注意,文件夹里附带了很多文件,你可以删除它。当然,如果嫌麻烦就直接从photo.py文件用Python执行。

写在最后

今天的内容就到这里啦,希望可以对你有所帮助。如果有需要本文源码的同学可以私信我关键字:PY GUI,我会将源码分享给有需要的人。

也欢迎各路大神在评论区提出问题与建议,希望能与大家共同进步。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容