Python异步编程|PySimpleGUI界面读取PDF转换Excel
目录
实例要求
原始pdf文件格式
输出xls文件格式
运行界面
完整代码
代码分析
遍历表格
布局界面
控件简介
写入表格
表格排序
事件循环
异步编程
实例要求
使用PySimpleGUI做一个把单位考勤系统导出的pdf文件合并输出Excel的应用,故事出自:
https://hannyang.blog.csdn.net/article/details/135395946
当时时间紧,没有好好做界面且输出csv文件了事。今天趁周六休息,把代码做一下升级处理,使用库PySimpleGUI做了一个稍微漂亮一点的界面;又用pdfplumber直接遍历多个pdf文件,得到数据后输出Excel文件,比我原本先做合并pdf文件再去取数要快,原先的pdf文件合并操作纯粹有点多余。最后,又尝试对pdf文件读取函数的改造,使用了asyncio异步编程效果非常不错。
下面请听我慢慢道来:
原始pdf文件格式
输出xls文件格式
运行界面
完整代码
import xlwt, pyperclip, asyncio, pdfplumber
import os, time, datetime as dt
import PySimpleGUI as sg# 全局变量
table_head = '姓名,部门,应到,实到,出勤率,迟到次数,早退次数,加班(分钟)'
path, font = '', ('宋体',12)
date, data = [], []
DateFormat = ' . . - . . '
ErrMessage = '错误'
SortedType = ["出勤率排序","加班时长排序","迟到次数排序","早退次数排序"]# 定义布局
layout = [[sg.Text("昆山分行考勤表",font=('',16)),sg.Text(pad=(132,10)),sg.Text("请选择考勤文件:",font=font),sg.Input(key="-FOLDER-", enable_events=True, readonly=True,font=font,size=18),sg.FolderBrowse(button_text='...', enable_events=True, initial_folder='./')],[sg.Text("考勤日期:",font=font),sg.Text(DateFormat,key='-DATE-',font=font)],[sg.Table(values='',headings=table_head.split(','),key='-TABLE-',auto_size_columns=False,justification='left',num_rows=10)],[sg.Button("输出Excel文件",size=(12,1),pad=(15,30)),sg.Button(SortedType[0], enable_events=True,size=(10,1),pad=(15,30)),sg.Button(SortedType[1], enable_events=True,size=(10,1),pad=(15,30)),sg.Button(SortedType[2], enable_events=True,size=(10,1),pad=(15,30)),sg.Button(SortedType[3], enable_events=True,size=(10,1),pad=(15,30)),sg.Button("退出",size=(10,1),pad=(15,30))],[sg.StatusBar('',key="-BAR-",font=font,size=92)]
]# 读取pdf表格
async def read_table(file):dct = dict()with pdfplumber.open(file) as pdf:for page in pdf.pages:tables = page.extract_tables(table_settings = {})for table in tables:for lst in table:tmp = lst[1:]if not any(tmp): continuetmp = [tmp[0]]+tmp[3:8]+[tmp[-1]]tmp[0] = tmp[0].replace('\n','')tmp[0] = tmp[0].split('/')tmp[0] = tmp[0][-1]if lst[0]=='时间':dct[lst[0]] = tmp[0]else:dct[','.join([lst[0],tmp[0]])] = ','.join(tmp[1:])return dct# 写入xls文件
def write_sheet():global data, date, table_head, ErrMessageif ErrMessage[:2] in ('错误','文件'): returnmyxl = xlwt.Workbook()style = xlwt.easyxf('align: wrap yes; align: horiz center; font: bold yes;') sheet = myxl.add_sheet('考勤表')wcol = [20,40,60,30,30,40,40,40,60]for i,w in enumerate(wcol):sheet.col(i).width = w * 80sheet.write_merge(0,0,0,8,'出勤统计报表',style)style = xlwt.easyxf('borders:top thin; borders:bottom thin; borders:left thin; borders:right thin;') sheet.write_merge(1,1,0,2,'考勤日期:'+date[0])for i,head in enumerate(['序号']+table_head.split(',')):sheet.write(2,i,head,style)for i,row in enumerate(data):for j,col in enumerate([str(i+1)]+row):sheet.write(3+i,j,col,style)for i,t in enumerate(SortedType):if t in ErrMessage:tmp = SortedType[i]breakelse: tmp = ""excel_file = f'昆山分行考勤表{date[0]}({tmp}{strDateTime()}).xls'ErrMessage = f'文件输出为:{excel_file}'try:myxl.save(excel_file)except:ErrMessage = '写入excel文件失败!'finally:pyperclip.copy('\\'.join((os.getcwd(),excel_file)))window['-BAR-'].update(ErrMessage)# 获取当前时间
def strDateTime(diff=0):now = dt.datetime.now()time = now + dt.timedelta(days=diff) return f'{time.year}{time.month:02}{time.day:02}{time.hour:02}{time.minute:02}{time.second:02}'# 选择并处理文件
async def on_text_changed(event, values):global date, data, path, ErrMessagenew_path = values["-FOLDER-"]window["-FOLDER-"].update(new_path.split('/')[-1])if path==new_path: returnelse: path = new_pathpdfs = [f for f in os.listdir(path) if f.endswith('.pdf') and not f.startswith('PDFmerged')]nums = len(pdfs)if nums==0:ErrMessage = '错误:所选文件夹中没有PDF文件!'window['-BAR-'].update(ErrMessage)window['-DATE-'].update(DateFormat)window['-TABLE-'].update(values=[])returndate, data, sheet = [], [], dict()tasks = []for pdf in pdfs:tasks.append(read_table('/'.join([path,pdf])))ErrMessage = f'文件读取中(共{nums}个PDF文件)......'window['-BAR-'].update(ErrMessage)window.refresh()results = await asyncio.gather(*tasks)for r in results:dt = r.get('时间',None)if dt: date.append(dt)sheet.update(r)if date:window['-DATE-'].update(date[-1])for k,v in sheet.items():if k in ('时间','姓名,所属组织','普通班个人出勤统计报表,'): continuedata.append(','.join([k,v]).split(','))window['-TABLE-'].update(values=data)persons = len(data)departments = len(set([d[1] for d in data]))if 0:#len(set(date))!=1:data = []ErrMessage = f'错误:请检查所选文件存在多个时间段:{",".join(set(date))}'else:ErrMessage = f'考勤人数:{persons} / 部门数:{departments}'window['-BAR-'].update(ErrMessage)# 表格排序
def on_table_sorted(event, data):global ErrMessageif not data: returnslist = ['x[-4][:-1]', 'x[-1]', 'x[-3]', 'x[-2]']style = slist[SortedType.index(event)]data = sorted(data, key=lambda x: float(eval(style)), reverse=True)window['-TABLE-'].update(values=data)ErrMessage = f'已按{event}更新!'window['-BAR-'].update(ErrMessage)# 创建窗口
window = sg.Window("考勤表汇总", layout, finalize=True)# 事件循环
while True:event, values = window.read()if event == sg.WINDOW_CLOSED or event == "退出":breakelif event == "-FOLDER-":asyncio.run(on_text_changed(event, values))elif event in SortedType:on_table_sorted(event, data)elif event == "输出Excel文件":write_sheet()# 关闭窗口
window.close()
代码分析
重点代码都用彩色字体加粗标注了:
遍历表格
读取代码如下:
import pdfplumber
......
with pdfplumber.open(file) as pdf:
for page in pdf.pages:
tables = page.extract_tables(table_settings = {})
for table in tables:
for lst in table:
# 根据表格实际情况来清洗数据
return dct
布局界面
import PySimpleGUI as pg
layout = [
[sg.Text("昆山分行考勤表",font=('',16)),
sg.Text(pad=(132,10)),
sg.Text("请选择考勤文件:",font=font),
sg.Input(key="-FOLDER-", enable_events=True, readonly=True,font=font,size=18),
sg.FolderBrowse(button_text='...', enable_events=True, initial_folder='./')
],
[sg.Text("考勤日期:",font=font),
sg.Text(DateFormat,key='-DATE-',font=font)
],
[sg.Table(values='',
headings=table_head.split(','),
key='-TABLE-',
auto_size_columns=False,
justification='left',
num_rows=10)],
[sg.Button("输出Excel文件",size=(12,1),pad=(15,30)),
sg.Button(SortedType[0], enable_events=True,size=(10,1),pad=(15,30)),
sg.Button(SortedType[1], enable_events=True,size=(10,1),pad=(15,30)),
sg.Button(SortedType[2], enable_events=True,size=(10,1),pad=(15,30)),
sg.Button(SortedType[3], enable_events=True,size=(10,1),pad=(15,30)),
sg.Button("退出",size=(10,1),pad=(15,30))],
[sg.StatusBar('',key="-BAR-",font=font,size=92)]]
控件简介
除了最常用的Text, Input, Button,使用了 FolderBrowse、Table、StatsBar 三个不是最常用的控件,分别是文件夹打开框、表格和状态栏。
表格最重要的三个参数: values, headings, auto_size_columns
sg.Table(values='', headings=table_head.split(','), auto_size_columns=False)
表格数据values和表头headings都列表(分别是二维和一维的),auto_size_columns=False建议不要缺省,否则列宽不可控,各列都自动缩进紧靠在一起。
表格更新数据的方法:window['-TABLE-'].update(values=data)
写入表格
import xlwt
def write_sheet():
global data, date, table_head, ErrMessage
if ErrMessage[:2] in ('错误','输出'): return
myxl = xlwt.Workbook()
style = xlwt.easyxf('align: wrap yes; align: horiz center; font: bold yes;')
sheet = myxl.add_sheet('考勤表')
wcol = [20,40,60,30,30,40,40,40,60]
for i,w in enumerate(wcol):
sheet.col(i).width = w * 80
sheet.write_merge(0,0,0,8,'出勤统计报表',style)
style = xlwt.easyxf('borders:top thin; borders:bottom thin; borders:left thin; borders:right thin;')
sheet.write_merge(1,1,0,2,'考勤日期:'+date[0])
for i,head in enumerate(['序号']+table_head.split(',')):
sheet.write(2,i,head,style)
for i,row in enumerate(data):
for j,col in enumerate([str(i+1)]+row):
sheet.write(3+i,j,col,style)
for i,t in enumerate(SortedType):
if t in ErrMessage:
tmp = SortedType[i]
break
else: tmp = ""
excel_file = f'昆山分行考勤表{date[0]}({tmp}{strDateTime()}).xls'
ErrMessage = f'输出文件为:{excel_file}'
try:
myxl.save(excel_file)
except:
ErrMessage = '写入excel文件失败!'
注意单格和多个单元格的写入区别: sheet.write() sheet.write_merge()
表格排序
SortedType = ["出勤率排序","加班时长排序","迟到次数排序","早退次数排序"]
def on_table_sorted(event, data):
global ErrMessage
if not data: return
slist = ['x[-4][:-1]', 'x[-1]', 'x[-3]', 'x[-2]']
style = slist[SortedType.index(event)]
data = sorted(data, key=lambda x: float(eval(style)), reverse=True)
window['-TABLE-'].update(values=data)
ErrMessage = f'已按{event}更新!'
window['-BAR-'].update(ErrMessage)
虽然经常有人诟病eval()函数的安全性,但这里还是用eval()简化表格排序事件,否则要多写很多代码。
事件循环
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED or event == "退出":
break
elif event == "-FOLDER-":
asyncio.run(on_text_changed(event, values))
elif event in SortedType:
on_table_sorted(event, data)
elif event == "输出Excel文件":
write_sheet()
异步编程
此时,请出本篇的主角“异步编程”,什么是异步编程呢?就是有点多任务操作的意思。
异步编程是一种编程范式,它允许某些操作在等待结果时不阻塞整个程序。在传统的同步编程中,程序会按照顺序执行,一旦遇到需要等待的操作(如文件I/O或网络请求),整个程序就会被阻塞,等待操作完成。而在异步编程中,程序并不会因为某个耗时的IO操作而停下其他所有任务,而是将这个任务交给系统处理,自身继续执行后续的操作,等到IO操作完成后,系统会通知程序进行下一步的处理。
asyncio
在上一段代码中,响应"-FOLDER-"时使用了asyncio.run()函数:
import asyncio
....... ......
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED or event == "退出":
break
elif event == "-FOLDER-":
asyncio.run(on_text_changed(event, values))
asyncio.run运行的这个是异步编程的主函数,需要用async def来定义:
async def
async def on_text_changed(event, values):
......其它代码略......
tasks = []
for pdf in pdfs:
tasks.append(read_table('/'.join([path,pdf])))
ErrMessage = f'文件读取中(共{nums}个PDF文件)......'
window['-BAR-'].update(ErrMessage)
window.refresh()
results = await asyncio.gather(*tasks)
for r in results:
......遍历取回的被调异步函数返回值的列表......
await
异步主函数中使用 await asyncio.gather(*tasks) 取回被函数的返回结果,返回结果是多个任务的返回值组成的列表;而主函数的任务呢就,是被调函数组成的列表:asks.append(read_table())
同样的,被调函数也需要用async def来定义,它一般都是文件I/O或网络请求等比较耗时的操作:
async def read_table(file):
dct = dict()
with pdfplumber.open(file) as pdf:
# 读取pdf文件 I/O操作
return dct
源码和2个例表已绑定上传资源,欢迎下载测试。
相关文章:

Python异步编程|PySimpleGUI界面读取PDF转换Excel
目录 实例要求 原始pdf文件格式 输出xls文件格式 运行界面 完整代码 代码分析 遍历表格 布局界面 控件简介 写入表格 表格排序 事件循环 异步编程 实例要求 使用PySimpleGUI做一个把单位考勤系统导出的pdf文件合并输出Excel的应用,故事出自࿱…...

制造领域 基础概念快速入门介绍
1、基本背景知识 本定义结合国家标准文件有所发挥,仅供参考。 产品:是生产企业向用户或市场以商品形式提供的制成品; 成套设备:在生产企业一般不用装配工序连接,但用于完成相互联系的使用功能的两个或两个以上的产…...
小程序的完整开发流程?
小程序的完整开发流程可以分为以下几个步骤: 需求分析和设计:明确小程序的功能需求和设计思路,包括页面结构、交互逻辑等。 环境搭建:安装并配置开发工具,如微信开发者工具或其他小程序开发工具。 项目初始化&#x…...

【LV13 DAY16 轮询与中断】
轮询实现按键实验 #include "exynos_4412.h"int main() {//GPX1_1设置为输入模式//GPX1.CONGPX1.CON & (~ (0XF<<4));while(1){if(!(GPX1.DAT&(1<<1))){printf("key pressed\n");while(!(GPX1.DAT&(1<<1)));}else{}}return…...

Swoft - Bean
一、Bean 在 Swoft 中,一个 Bean 就是一个类的一个对象实例。 它(Bean)是通过容器来存放和管理整个生命周期的。 最直观的感受就是省去了频繁new的过程,节省了资源的开销。 二、Bean的使用 1、创建Bean 在【gateway/app/Http/Controller】下新建一个名为…...

【产品人卫朋】硬件产品经理:从入门到精通
目录 本文目录 1. 前言说明 2. 内容说明 3. 资料包说明 作者简介 本文目录 1. 前言说明 2. 内容说明 3. 资料包说明 1. 前言说明 本篇内容节选自实体书《硬件产品经理:从入门到精通》。 2. 内容说明 鉴于硬件产品的特殊性,不同产品阶段的时间间…...

swing快速入门(四十)JList、JComboBox实现列表框
注释很详细,直接上代码 上一篇 新增内容 🧧1.列表的属性设置与选项监听器 🧧2.下拉框的属性设置与选项监听器 🧧3.Box中组件填充情况不符合预期的处理方法 🧧4.LIst向Vector的转化方法 源码: package swing…...

React Native 原生组件回调JS层方法和 JS 层调用原生组件的事件方法
一、原生组件回调 JS 层提供的事件方法 比如 TextInput 组件 onChangeText 属性,输入事件是发生在原生层的但是需要通知 JS 层发生了变化,并执行 JS 层的方法。 1、给原生组件添加一个按钮用于触发原生事件方法 在 XML 中添加一个按钮 为了方便让 Inf…...

Go-安装与基础语法
TOC 1. Go 安装与环境变量 1.1 下载 需要从Go语言的官方网站下载适合你操作系统的Go语言安装包。Go语言支持多种操作系统,包括Windows、Linux和Mac OS。 对于Windows用户,下载.msi文件,然后双击该文件,按照提示进行安装即可。…...

【同济子豪兄斯坦福CS224W中文精讲】NetworkX代码学习笔记
文章目录 安装配置创建图可视化图图数据挖掘参考资料 安装配置 matplotlib中文字体设置 import networkx as nx import matplotlib.pyplot as plt # 魔法指令,设置后在jupyter notebook中绘制的图形会显示在输出单元格中,而不是弹出一个新窗口 %matplo…...

java+ssm+vue代码视频学习讲解
一、ssm 1.项目文件结构 2.数据库连接信息 3.其他配置信息 4.java代码文件目录介绍 5.entity层代码 6.controller,service,dao,entity层之间的关系 7.controller层代码 8.登陆拦截功能实现 AuthorizationInterceptor.java 9.文件上传功能 …...

[计算机提升] 创建FTP共享
4.7 创建FTP共享 4.7.1 FTP介绍 在Windows系统中,FTP共享是一种用于在网络上进行文件传输的标准协议。它可以让用户通过FTP客户端程序访问并下载或上传文件,实现文件共享。 FTP共享的用途非常广泛,例如可以让多个用户共享文件、进行文件备份…...

R语言将list转变为dataframe(常用)
在R语言使用中常常遇到list文件需要转变为dataframe格式文件处理。这是需要写循环来进行转换。IOBR查看其收录的相关基因集(自备)_iobr_deg-CSDN博客 示例文件 list文件: 循环转换为dataframe data <- signature_tme dat <- as.data.frame(t(sapply(data, …...

【JAVA】OPENGL+TIFF格式图片,不同阈值旋转效果
有些科学研究领域会用到一些TIFF格式图片,由于是多张图片相互渐变,看起来比较有意思: import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.*;/*** 可以自已定义日志打印格式…...

Linux系统中使用ln命令创建软连接
大家应该和我一样,第一次听到软连接这个词时感觉好高级啊,但其实也就那么回事,你完全可以将他类比为Windows系统中的快捷方式。 链接只是一个指向,并不是物理移动,类似Windows系统的快捷方式 1.功能和语法 功能&…...

Spark---RDD(Key-Value类型转换算子)
文章目录 1.RDD Key-Value类型1.1 partitionBy1.2 reduceByKey1.3 groupByKeyreduceByKey和groupByKey的区别分区间和分区内 1.4 aggregateByKey获取相同key的value的平均值 1.5 foldByKey1.6 combineByKey1.7 sortByKey1.8 join1.9 leftOuterJoin1.10 cogroup 1.RDD Key-Value…...
后台代码New出来DataGridTextColumn 动态添加到DataGrain 设置 Margin属性
在 WPF 中给 DataGridTextColumn 设置 MarginProperty 可以通过自定义 DataGridTemplateColumn 来实现。以下是一个示例代码: <DataGrid><DataGrid.Columns><DataGridTemplateColumn><DataGridTemplateColumn.CellTemplate><DataTempla…...
MySQL面试题(下)
09)查询学过「张三」老师授课的同学的信息 SELECTs.*,c.cname,t.tnameFROMt_mysql_teacher t,t_mysql_student s,t_mysql_course c,t_mysql_score scWHEREt.tidc.tid and c.cidsc.cid and sc.sids.sid and tname 张三 10)查询没有学全所有课程的同学的…...
【Linux】如何检查Linux用户是否具有sudo权限
问题背景或前提知识 在Linux系统中,sudo(superuser do)是一个重要的命令,它允许普通用户以系统管理员的身份执行命令。了解用户是否拥有sudo权限对于系统管理和安全性来说是非常重要的。 技术名词解释 sudo:一种程序…...
2024.1.13 Kafka六大机制和Structured Streaming
目录 一 . Kafka中生产者数据分发策略 二. Kafka消费者的负载均衡机制 三 . 数据不丢失机制 生产者端是如何保证数据不丢失的呢? Broker端如何保证数据不丢失 消费端如何保证数据不丢失 Kafka中消费者如何对数据仅且只消费一次 四 . 启动Kafka eagle命令 数…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...

OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...