基于python、百度ocr、multiprocessing多进程、selenium网页自动化 、pyqt5界面弹出,实现发票的识别与对学校财务网站的脚本自动化上传操作的项目总结

news/2024/5/12 1:31:18/文章来源:https://blog.csdn.net/AYGZC233/article/details/123074845

目录

一、项目背景

二、框架确定与技术选型

1)框架确定

2)技术选型

(1)发票识别​编辑

(2)整合到excel

(3)输入到学校的网页

3)整合技术框架

框架如下

三、代码实现

1)发票识别模块

百度api雏形代码

问题:

问题的解决:

百度ocr发票识别的最终代码

至此发票的识别功能已经结束

2)基于excel的识别与录入

一、说在前面

二、直接上代码

三、谈谈遇到的问题与解决方案

至此我们selenium部分也已经写完了

四、界面与打包安装

一、需求来了,需要界面

二、界面的技术选型

三、按键代码的映射关系

程序到这里已经大功告成了

五、打包安装与使用

一、需求又来了,需要打包成exe

二、直接利用pyinstaller打包

六、其他问题总结

1)、关于打包

2)、关于多线程与多线程

3)、关于有可能出现的情况

七、代码总结

一、代码

二、代码解释

三、程序的运行流程

 八、项目总结

1)项目的市场

2)经验心得

九、完结


一、项目背景

由于学校网站的不完善,很多功能无法实现

        watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQVlHWkMyMzM=,size_20,color_FFFFFF,t_70,g_se,x_16

学校网站如上

        首先没有对发票的识别功能,其次就算对发票识别成excel了,学校的上传excel功能也无法使用,所以对于有多发票报销要求的学生与老师,上传发票报销非常麻烦与困难。

        基于这种状况,打算做出一种能实现自动化/半自动化操作的软件

二、框架确定与技术选型

1)框架确定

          现在我们输入发票需要人工的阅读与输入

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQVlHWkMyMzM=,size_20,color_FFFFFF,t_70,g_se,x_16

人工输入模式

看起来很简单,实际上在面对多张发票的情况下,需要人工核对的内容很多,操作非常的繁琐。

现在我们确定一个模式

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQVlHWkMyMzM=,size_20,color_FFFFFF,t_70,g_se,x_16

程序实现模式 

上图这种模式看起来更加繁杂,可实际上我们全部交给计算机完成的话,能帮我们省下不少的时间与精力,我们只需要先导入发票和最后核对发票即可。

由此我们可以进行技术选型选型

2)技术选型

(1)发票识别watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQVlHWkMyMzM=,size_20,color_FFFFFF,t_70,g_se,x_16

发票模板

在我一开始的想法里,发票识别可以基于

(1)python自带的ocr

错乱百出,直接pass,最接受不了的是文字识别识别不了中文

(2)paddle的ocr文字识别

paddle是百度开源的人工智能,我一开始识别发票是运用了这一技术,可惜运行结果不圆满,

问题:

1,经常性的识别错误

2,识别出的信息无法进行对齐与匹配

3,基于问题2,很难提取出其中的信息

所以我也放弃了这一技术

(3)百度智能云的百度ocr技术

优势:

1,识别精准,对于我们的软件来说,数据的准确性是最关键的,影响了我们之后数据的处理与输入

2,确实好用,发票上传识别后可以自动分离出需要的发票代码,发票信息之类的信息

3,速度较快,这里的较比较的是我上面两门技术的速度,腾讯云阿里云好像也有类似的ocr,我没用过,不做过多评价

缺点:

1.1000张检测之后要收费,0.1一张,对于在开发阶段的我简直痛苦

2,需要调用网络资源,不过发票上传到学校网站也需要,这缺点可以忽略不计了

那对于发票识别来说我们就使用百度的ocr就行了

(2)整合到excel

这个其实不是关键性技术,直接上我们的pandas就行

(3)输入到学校的网页

(1)爬虫

一开始我想过运用爬虫来输入,可是对学校网站的发票界面post和get后得不出有效信息,最后不了了之。

(2)selenium

既然爬虫行不通,又想自动化实现操作,那我们只能勉强使用一下selenium了(开玩笑,selenium技术确实很棒)

3)整合技术框架

框架如下

取得我们的pdf=>百度ocr=>excel=>通过selenium将我们的识别内容写入学校网站

三、代码实现

1)发票识别模块

不多废话直接上代码

百度api雏形代码

import requests
import base64'''
增值税发票识别
'''
class baiduapi:def baiduapi(self,imgpath):request_url = "https://aip.baidubce.com/rest/2.0/ocr/v1/vat_invoice"# 二进制方式打开图片文件f = open(imgpath, 'rb')self.img = base64.b64encode(f.read())params = {"image": self.img}access_token = '**'request_url = request_url + "?access_token=" + access_tokenheaders = {'content-type': 'application/x-www-form-urlencoded'}response = requests.post(request_url, data=params, headers=headers)#print(response)if response:print(response.json())else:print('无法识别')res = response.json()words_result = res['words_result']ids = res['log_id']self.list = []# print(result)try:self.list.append({'发票文件名': ids,'发票种类': words_result['InvoiceType'],'发票名称': words_result['InvoiceTypeOrg'],'发票代码': words_result['InvoiceCode'],'发票号码': words_result['InvoiceNum'],'开票日期': words_result['InvoiceDate'],'校验码': words_result['CheckCode'],'货物名称': words_result['CommodityName']['word'],'规格型号': words_result['CommodityType'],'单位': words_result['CommodityUnit'],'数量': words_result['CommodityNum']['word'],'单价': words_result['CommodityPrice']['word'],'未税金额': words_result['CommodityAmount']['word'],'税率': words_result['CommodityTaxRate']['word'],'税额': words_result['CommodityTax']['word'],'合计金额': words_result['TotalAmount'],'合计税额': words_result['TotalTax'],'价税合计(小写)': words_result['AmountInFiguers'],'价税合计(大写)': words_result['AmountInWords'],'购买方名称': words_result['PurchaserName'],'购买方纳税人识别号': words_result['PurchaserRegisterNum'],'购买方地址及电话': words_result['PurchaserAddress'],'购买方银行及账户': words_result['PurchaserBank'],'销售方名称': words_result['SellerName'],'销售方纳税人识别号': words_result['SellerRegisterNum'],'销售方地址及电话': words_result['SellerAddress'],'销售方银行及账户': words_result['SellerBank'],'收款人': words_result['Payee'],'复核': words_result['Checker'],'开票人': words_result['NoteDrawer'],'备注': words_result['Remarks']})except:self.list.append({'发票文件名': ids,'发票种类': words_result['InvoiceType'],'发票名称': words_result['InvoiceTypeOrg'],'发票代码': words_result['InvoiceCode'],'发票号码': words_result['InvoiceNum'],'开票日期': words_result['InvoiceDate'],'校验码': words_result['CheckCode'],'货物名称': '','规格型号': words_result['CommodityType'],'单位': words_result['CommodityUnit'],'数量': '','单价': '','未税金额': '','税率': '','税额': '','合计金额': words_result['TotalAmount'],'合计税额': words_result['TotalTax'],'价税合计(小写)': words_result['AmountInFiguers'],'价税合计(大写)': words_result['AmountInWords'],'购买方名称': words_result['PurchaserName'],'购买方纳税人识别号': words_result['PurchaserRegisterNum'],'购买方地址及电话': words_result['PurchaserAddress'],'购买方银行及账户': words_result['PurchaserBank'],'销售方名称': words_result['SellerName'],'销售方纳税人识别号': words_result['SellerRegisterNum'],'销售方地址及电话': words_result['SellerAddress'],'销售方银行及账户': words_result['SellerBank'],'收款人': words_result['Payee'],'复核': words_result['Checker'],'开票人': words_result['NoteDrawer'],'备注': words_result['Remarks']})print(self.list)return (self.list)
if __name__ == '__main__':runapi = baiduapi()imgPath = 'api/0.png'runapi.baiduapi(imgPath)

这是基于百度api官方文档写出来的代码雏形

运行起来很不错,可是随之问题来了

问题:

一:

首先是

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQVlHWkMyMzM=,size_18,color_FFFFFF,t_70,g_se,x_16

 这些代码太多了,而我们需要的仅仅只是一小部分信息

二:

其次我们学校官网的日期格式为yyyy-mm-dd(2022-02-22),而识别出来的是2022年02月22日,不符合要求

三:

然后就是百度ocr需要的对象是png,而我们只有pdf

四:

如果有很多张发票的话,项目跑起来还是太慢了

问题的解决:

基于以上四点问题,我们可以思考一下解决方法

问题一解决方案:

信息获取的太杂,那我们就挑我们需要的信息来获取咯

问题二解决方案:

我们可以运用正则表达式将2022年02月22日提取出2022,02,22,然后通过

date = year+"-"+mouth+"-"+day

整合到date里面运用就行

问题三解决方案:
既然需要的是png,那我们可以写个方法将pdf转换成png

问题四解决方案:

解决问题四的话花费了我很多时间,首先我想的是基于多线程来解决,可是由于python的GIL大锁,一个程序只能有一个主线程,查看相关文档弄了一个晚上没弄出来

最后偶的想起python还有个多进程,有句话说得好:“python多线程屁用没有,并行还是得看多进程”,直接丢掉threading转成multiprocessing

其实多进程也有很多问题需要解决:pychram无法运行,打包在exe中无法运行等

解决的方法就不在这里赘述

下面我们来看一下最终的代码:

百度ocr发票识别的最终代码

import multiprocessing
import os
import threading
from multiprocessing import Pool
import fitz
import time
import pandas
from aip import AipOcr
import datetime
import reclass apiscan:# 以下为批量PDF转图片的代码def pdf2img(self, pdf_file):# 利用os取得我们的pdf文件pdf_file_list = os.listdir(pdf_file)self.PNG_path = 'PNG'  # PNG文件夹路径PNGfile_suffix = '.png'  # 后缀try:# 是否有这个路径if not os.path.exists(self.PNG_path):# 创建路径os.makedirs(self.PNG_path)except IOError as e:print("IOError")except Exception as e:print("Exception")print("正在检测中,请耐心等待".center(60))time.sleep(1)print('预计识别文件如下:')for i in range(len(pdf_file_list)):print(pdf_file_list[i])time.sleep(1)print('\n')print('预计识别文件数量:' + str(len(pdf_file_list)))time.sleep(1)print('\n')print('图片生成中……')print('\n')time.sleep(1)# 这里是遍历我们取得的pdffor id in pdf_file_list:pdf = fitz.open(os.path.join(pdf_file, id))# 根据pdf信息,,将他们批量转换成我们需要的图片for pg in range(pdf.pageCount):page = pdf[pg]rotate = int(0)zoom_x = 2.0zoom_y = 2.0trans = fitz.Matrix(zoom_x, zoom_y).prerotate(rotate)pm = page.get_pixmap(matrix=trans, alpha=False)filename = os.path.splitext(id)[0]pngfilename = self.PNG_path + '\\' + filename + str(pg) + PNGfile_suffixpm.save(pngfilename)print(pngfilename)print('图片生成完毕')print('\n')time.sleep(1)# 这里是拿到我们列表后转入exceldef toExcel(self, list):# 利用pandas.DataFrame整理成行列式newsdf = pandas.DataFrame(list)# 利用pandas.DataFrame(list).to_excel创建并写入excel中newsdf.to_excel(datetime.datetime.now().date().strftime('%Y%m%d') + '增值税发票信息统计' + '.xlsx')self.xslpath = datetime.datetime.now().date().strftime('%Y%m%d') + '增值税发票信息统计' + '.xlsx'print("识别完成")# 这里就是主要的百度ocrApi的调用方法,注意不要在apiscan类下面,因为影响多进程的实现
# 因为多进程中的数据不共享,我们用传参的方式将图片id,imgpath,和manager管理的lsit写入方法
def forEach(id, imgpath, list):APP_ID = '**'  # 百度api的idAPI_KEY = '***'  # 百度api的应用KeySECRET_key = '***'  # 百度api的应用密码,这些都要自己获取client = AipOcr(APP_ID, API_KEY, SECRET_key)  # 百度api调用# 加入try防止有识别不了的pdf导致软件宕机try:# 利用OS打开我们的图片地址img = open(os.path.join(imgpath, id), 'rb').read()res = client.vatInvoice(img)  # 取得我们api后返回的结果words_result = res['words_result']  # words_result是给我们返回的结果集words_result_num = res['words_result_num']print(str(id) + '' + '识别完成')print('识别信息数量:' + str(words_result_num))print('识别信息:' + str(words_result))# 接下来就是正则表达式的运用year = ''.join(re.findall('(2019)|(2020)|(2021)|(2022)', words_result['InvoiceDate'])[0])  # 取得year,只取值到2022month = ''.join(re.findall('(01)|(02)|(03)|(04)|(05)|(06)|(07)|(08)|(09)|(10)|(11)|(12)',words_result['InvoiceDate'])[1])  # 取得月份day = words_result['InvoiceDate'][-3:-1]  # 取得日期date = year + ('-') + month + ('-') + day  # 整合日期# 这里就是将识别的结果写入manager管理的list,实现进程间的数据同步与共享# 下面的各个数据是自己通过分析word_result得来list.append({'发票代码': words_result['InvoiceCode'],'发票号码': words_result['InvoiceNum'],'电子发票号码': '','开票日期': date,'校验码': words_result['CheckCode'][-6:],'开票方名称': words_result['SellerName'],'合计金额': words_result['TotalAmount'],'合计税额': words_result['TotalTax'],'价税合计(小写)': words_result['AmountInFiguers'], })except:# 如果识别不了那就输出识别不了print("图像无法识别")print("\n")if __name__ == "__main__":multiprocessing.freeze_support()  # multiprocessing的freeze支持,如果不写打包成exe的时候会出现问题pdf_path = 'pdf'  # pdf的地址toxls = apiscan()  # 实例化我们的apiscantoxls.pdf2img(pdf_path)  # 先把pdf转换成图片imgpath = toxls.PNG_path  # 图片输出的地址id_list = os.listdir(imgpath) #通过os取得我们图片文件名列表#下面是多进程部分m = multiprocessing.Manager()#实例一个Managermylist = m.list()#创建一个可以在主进程和子进程之间通信的managerlistq = m.Queue()#创建manager的队列管理,自带锁,可以保证进程间的通信安全ls = len(id_list)#获取图片名列表的长度start = time.time()#记录开始时间,主要是记录多进程的使用时间pool = Pool(4)#创建进程池,4代表最多4进程同时运行#遍历图片名for i in range(ls):id = id_list[i]#提取文件名#用apply_async方法启动进程,启动的方法名为foreach,参数我们通过args传进去pool.apply_async(func=forEach, args=(id, imgpath, mylist,), )pool.close()#进程池关闭,注意不是进程关闭,关闭进程池保证没有多的进程进入pool.join()#进程池的阻塞,保证了在子进程结束的时候主进程不往下运行end = time.time()#结束时间#还是得说明,从这里开始就继续主线程了,程序往下走print(mylist)#dbugprint(end - start)#计算多进程用时var = list(mylist)#将mylist强转成list,具体是因为manager.list创建的是manager的列表而不是python的列表#,manager的列表不能给pandas.DataFrame使用print(var)#dbug用toxls.toExcel(var)#将var传递进toExcel方法中

以上 代码因为涉及多进程的原因,每一步我都有详细备注与说明,下面我们来试一下程序的运行

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQVlHWkMyMzM=,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQVlHWkMyMzM=,size_20,color_FFFFFF,t_70,g_se,x_16

代码运行界面

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQVlHWkMyMzM=,size_20,color_FFFFFF,t_70,g_se,x_16

 导出的excel

我们已经将设想的功能实现了,无法识别的发票图片是发票的附带件,也能正确的分离开来

至此发票的识别功能已经结束

2)基于excel的识别与录入

一、说在前面

这个方法是基于我们学校网站所“定制”的网页自动化,对别的网站并不适用,只是分享出来一种思路与方法

二、直接上代码

    def auto(self, xslpath):# 一定要有相关的驱动,如果是谷歌那就driver = webdriver.chromedriver(executable_path='chromedriver.exe')# 因为并不是每个人电脑上都有chrome,我就用了edgedriver = webdriver.Edge(executable_path='msedgedriver.exe')driver.maximize_window()self.excel = openpyxl.load_workbook(xslpath)  # 通过参数获得我们之前取得的excel文件s1 = self.excel['Sheet1']  # 打开sheet1这个表格页,第一页self.rows = s1.max_row  # 取得我们的行列数self.rows += 1print(self.rows)# 设置自动化打开的浏览器访问网址url = 'http://cwsys.**.edu.cn/dlpt/Newindex.aspx'driver.get(url)  # 打开我们学校的财务网页curr_handle = driver.current_window_handledriver.maximize_window()# print("curr_handle=", curr_handle)win32api.MessageBox(0, '当到达指定界面时程序运行', '提示')# 利用while循环和try,expect,来保证我们能打开对应网页再行动while (True):# 尝试获取我们是否到达对应网页try:# 检测webdriver是否打开新的界面all_handles = driver.window_handlesfor h in all_handles:if h != curr_handle:# 跳转到h窗口driver.switch_to.window(h)# 获取到新窗口的句柄curr_handle = driver.current_window_handleprint("curr_handle=", curr_handle)# input("")driver.switch_to.frame("mainframe")time.sleep(3)# 下面是无聊的输入row = 6for i in range(2, self.rows):if i > row:driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_BT_ADD"]').click()row += 1time.sleep(2)print('第{}行输入中'.format(i - 1))name = s1['B{}'.format(i)].valuenumber = s1['C{}'.format(i)].valuefpdate = s1['E{}'.format(i)].valuepassword = s1['F{}'.format(i)].valuefpfrom = s1['G{}'.format(i)].valuemoney = s1['H{}'.format(i)].valuenomoney = s1['I{}'.format(i)].value# print(name, number, fpdate, password, fpfrom, money, nomoney)time.sleep(1)# >9和<=9具体xpath里面具体的参数不一样所以需要分层if i > 9:driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_FPDM"]'.format(i)).send_keys(name)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_FPHM"]'.format(i)).send_keys(number)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_KPRQ"]'.format(i)).send_keys(fpdate)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_YZM"]'.format(i)).send_keys(password)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_KPFMC"]'.format(i)).send_keys(fpfrom)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_JE"]'.format(i)).send_keys(money)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_SE"]'.format(i)).click()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_SE"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_SE"]'.format(i)).send_keys(nomoney)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_HJ"]'.format(i)).click()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_FPDM"]'.format(i)).click()print('第{}行输入成功'.format(i - 1))else:driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_FPDM"]'.format(i)).send_keys(name)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_FPHM"]'.format(i)).send_keys(number)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_KPRQ"]'.format(i)).send_keys(fpdate)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_YZM"]'.format(i)).send_keys(password)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_KPFMC"]'.format(i)).send_keys(fpfrom)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_JE"]'.format(i)).send_keys(money)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_SE"]'.format(i)).click()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_SE"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_SE"]'.format(i)).send_keys(nomoney)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_HJ"]'.format(i)).click()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_FPDM"]'.format(i)).click()print('第{}行输入成功'.format(i - 1))time.sleep(1)i += 1breakexcept:time.sleep(10)continueshutil.rmtree(self.PNG_path)  # 清楚图片文件夹os.remove(self.xslpath)# 弹出win提示win32api.MessageBox(0, '请检查是否填写正确', '提示')# 让时间停留1000秒,不然会直接关闭网页time.sleep(1000)

上面代码主要运用了

openxyl来对我们的excel信息的获取

selenium来进行对财务网页输入excel里面的信息

三、谈谈遇到的问题与解决方案

问题一、

根目录下必须要带有相应的webdriver文件

不然执行不了

问题二、

程序最后不写time.sleep,会导致程序运行完直接关闭我们弹出的网页

问题三、

程序必须基于他自己打开的网页运行

问题四、

雏形的代码没有用while加try的时候,在到达指定网页前必须使用其他代码阻塞一下进程

至此我们selenium部分也已经写完了

我们把总的代码整合在一个类中,,整体调用,代码如下

class finalmain:def finamain(self, pdfpath):Toxsl = apiscan()Toxsl.pdf2img(pdfpath)imgpath = Toxsl.PNG_pathid_list = os.listdir(imgpath)m = multiprocessing.Manager()mylist = m.list()q = m.Queue()ls = len(id_list)start = time.time()pool = multiprocessing.Pool(4)for i in range(ls):id = id_list[i]pool.apply_async(func=forEach, args=(id, imgpath, mylist,), )pool.close()pool.join()end = time.time()print(mylist)print(end - start)var = list(mylist)print(var)Toxsl.toExcel(var)Auto = auto2gdpu()xslpath = Toxsl.xslpathAuto.auto(xslpath)def finamin2Auo(self, xslpath):Auto = auto2gdpu()Auto.auto(xslpath)

四、界面与打包安装

一、需求来了,需要界面

我们的甲方(我的老师)觉得我们的项目功能实现的还过得去,就是每次都需要在cmd运行main.py,不得行,所以我们需要个界面来使用。

二、界面的技术选型

创建界面我们直接上pyqt5,因为我不是专业的ui设计师,秉承着能用就行的ui界面,运用pyqt5软件包中的qt Designer设计了一个不是很丑也能用的ui

qt Designer截图

设计的ui截图

设计好ui后我们利用pyuic插件来创建代码

代码如下

# -*- coding: utf-8 -*-# Form implementation generated from reading ui file 'untitled.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.from PyQt5 import QtCore, QtGui, QtWidgetsclass Ui_MainWindow(object):def setupUi(self, MainWindow):MainWindow.setObjectName("MainWindow")MainWindow.resize(800, 600)self.centralwidget = QtWidgets.QWidget(MainWindow)self.centralwidget.setObjectName("centralwidget")self.textBrowser = QtWidgets.QTextBrowser(self.centralwidget)self.textBrowser.setGeometry(QtCore.QRect(10, 330, 691, 241))font = QtGui.QFont()font.setPointSize(16)self.textBrowser.setFont(font)self.textBrowser.setObjectName("textBrowser")self.layoutWidget = QtWidgets.QWidget(self.centralwidget)self.layoutWidget.setGeometry(QtCore.QRect(10, 190, 361, 131))self.layoutWidget.setObjectName("layoutWidget")self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)self.verticalLayout.setContentsMargins(0, 0, 0, 0)self.verticalLayout.setObjectName("verticalLayout")self.horizontalLayout = QtWidgets.QHBoxLayout()self.horizontalLayout.setObjectName("horizontalLayout")self.label = QtWidgets.QLabel(self.layoutWidget)self.label.setObjectName("label")self.horizontalLayout.addWidget(self.label)self.username = QtWidgets.QLineEdit(self.layoutWidget)self.username.setObjectName("username")self.horizontalLayout.addWidget(self.username)self.verticalLayout.addLayout(self.horizontalLayout)self.horizontalLayout_2 = QtWidgets.QHBoxLayout()self.horizontalLayout_2.setObjectName("horizontalLayout_2")self.label_2 = QtWidgets.QLabel(self.layoutWidget)self.label_2.setObjectName("label_2")self.horizontalLayout_2.addWidget(self.label_2)self.password = QtWidgets.QLineEdit(self.layoutWidget)self.password.setObjectName("password")self.horizontalLayout_2.addWidget(self.password)self.verticalLayout.addLayout(self.horizontalLayout_2)self.horizontalLayout_3 = QtWidgets.QHBoxLayout()self.horizontalLayout_3.setObjectName("horizontalLayout_3")self.remberbotton = QtWidgets.QPushButton(self.layoutWidget)self.remberbotton.setObjectName("remberbotton")self.horizontalLayout_3.addWidget(self.remberbotton)self.verticalLayout.addLayout(self.horizontalLayout_3)self.splitter = QtWidgets.QSplitter(self.centralwidget)self.splitter.setGeometry(QtCore.QRect(400, 190, 381, 131))self.splitter.setOrientation(QtCore.Qt.Vertical)self.splitter.setObjectName("splitter")self.pdfbuttton = QtWidgets.QPushButton(self.splitter)self.pdfbuttton.setObjectName("pdfbuttton")self.imgbutton = QtWidgets.QPushButton(self.splitter)self.imgbutton.setObjectName("imgbutton")self.xslbutton = QtWidgets.QPushButton(self.splitter)self.xslbutton.setObjectName("xslbutton")self.label_3 = QtWidgets.QLabel(self.centralwidget)self.label_3.setGeometry(QtCore.QRect(100, 40, 611, 141))font = QtGui.QFont()font.setPointSize(76)self.label_3.setFont(font)self.label_3.setObjectName("label_3")self.label_4 = QtWidgets.QLabel(self.centralwidget)self.label_4.setGeometry(QtCore.QRect(640, 160, 151, 31))font = QtGui.QFont()font.setPointSize(13)self.label_4.setFont(font)self.label_4.setObjectName("label_4")self.splitter_2 = QtWidgets.QSplitter(self.centralwidget)self.splitter_2.setGeometry(QtCore.QRect(704, 330, 91, 241))self.splitter_2.setOrientation(QtCore.Qt.Vertical)self.splitter_2.setObjectName("splitter_2")self.stopToXslbutton = QtWidgets.QPushButton(self.splitter_2)self.stopToXslbutton.setObjectName("stopToXslbutton")self.stopToWebbutton = QtWidgets.QPushButton(self.splitter_2)self.stopToWebbutton.setObjectName("stopToWebbutton")MainWindow.setCentralWidget(self.centralwidget)self.statusbar = QtWidgets.QStatusBar(MainWindow)self.statusbar.setObjectName("statusbar")MainWindow.setStatusBar(self.statusbar)self.retranslateUi(MainWindow)QtCore.QMetaObject.connectSlotsByName(MainWindow)def retranslateUi(self, MainWindow):_translate = QtCore.QCoreApplication.translateMainWindow.setWindowTitle(_translate("MainWindow", "发票识别系统(gdpu)"))self.label.setText(_translate("MainWindow", "  账号:"))self.label_2.setText(_translate("MainWindow", "  密码: "))self.remberbotton.setText(_translate("MainWindow", "如第一次使用请输入账号密码,输入完成后请点击该按钮保存"))self.pdfbuttton.setText(_translate("MainWindow", "识别发票pdf文件夹至表格"))self.imgbutton.setText(_translate("MainWindow", "识别发票图片文件夹至表格"))self.xslbutton.setText(_translate("MainWindow", "选择表格上传到网站"))self.label_3.setText(_translate("MainWindow", "发票识别系统"))self.label_4.setText(_translate("MainWindow", "Author:nicegoose"))self.stopToXslbutton.setText(_translate("MainWindow", "停止识别"))self.stopToWebbutton.setText(_translate("MainWindow", "停止上传"))

三、按键代码的映射关系

    def __init__(self):super(ControlBoard, self).__init__()self.setupUi(self)# 下面将输出重定向到textBrowser中sys.stdout = EmittingStr(textWritten=self.outputWritten)sys.stderr = EmittingStr(textWritten=self.outputWritten)self.pdfbuttton.clicked.connect(self.openPdfFile)self.imgbutton.clicked.connect(self.openImgFile)self.xslbutton.clicked.connect(self.xslToWeb)self.remberbotton.clicked.connect(self.remember)self.stopToXslbutton.clicked.connect(self.closeThread)self.stopToWebbutton.clicked.connect(self.closeWebThread)

识别pdf的按钮中我绑定了一个获取文件夹的事件

    def openPdfFile(self):DirectoryName = QFileDialog.getExistingDirectory(self, '打开文件夹', '')if DirectoryName:print(DirectoryName)self.thread_1 = Thread_1()  # 创建线程self.thread_1._signal.connect(self.printfini)self.thread_1.threadpath = DirectoryNameself.thread_1.start()  # 开始线程

 当点击时会如上图出现一个选择文件夹的框来选择一个文件夹,实际上是将pdf文件夹的路径和名字当做参数传进程序内

而识别img的按钮功能大同小异:

    def openImgFile(self):DirectoryName = QFileDialog.getExistingDirectory(self, '打开文件夹', '')if DirectoryName:print(DirectoryName)self.thread_2 = Thread_2()  # 创建线程self.thread_2._signal.connect(self.printfini)self.thread_2.threadpath = DirectoryNameself.thread_2.start()  # 开始线程

也是通过选择文件夹传参

账号密码保存功能我是基于json来实现的

    def remember(self):try:self.username1 = self.username.text()self.password1 = self.password.text()if self.username1=="" or self.password1=="":print("账号密码为空别动会影响已保存数据的")else:parameters = {"username": self.username1,"password": self.password1}finalmain1=finalmain()finalmain1.saveJson(parameters)print("账号密码保存成功,请检查是否正确")except:print("保存失败辽,,,,")

如上图所示,当账号密码为空时会提示并且不会写入数据,当账号密码有效时,会创建一个可更改的json文件中

 

 当我们需要时就可以通过open来读取和调用

停止识别线程的按钮绑定的代码:

    def closeThread(self):try:self.thread_1.terminate()self.thread_1.wait()print("pdf转xsl线程已经停止")except:print()try:self.thread_2.terminate()self.thread_2.wait()print("img转xsl线程已经停止")except:print("好像没啥用")

和停止上传线程的按钮绑定的代码:

    def closeWebThread(self):try:self.thread_3.terminate()self.thread_3.wait()except:print("好像没啥用")

就是通过按钮控制线程的挂起和结束来结束线程,不过多赘述

而我们这个方框是用来打印控制台输出内容

 来实现操作可读化,用来给使用者以提示,具体代码实现如下

我们需要先定义一个信号

class EmittingStr(QtCore.QObject):textWritten = QtCore.pyqtSignal(str)  # 定义一个发送str的信号def write(self, text):self.textWritten.emit(str(text))

然后通过信号将我们控制台的输出重定向到我们的界面上:

sys.stdout = EmittingStr(textWritten=self.outputWritten)
sys.stderr = EmittingStr(textWritten=self.outputWritten)
    def outputWritten(self, text):cursor = self.textBrowser.textCursor()cursor.movePosition(QtGui.QTextCursor.End)cursor.insertText(text)self.textBrowser.setTextCursor(cursor)self.textBrowser.ensureCursorVisible()

功能就实现了。

还有个将xsl文件上传到网站的按钮绑定,这里不做过多赘述了,具体代码如下:

    def xslToWeb(self):xslName = QFileDialog.getOpenFileName(self, '打开文件', './')if xslName[0]:print(xslName[0])self.thread_3 = Thread_3()  # 创建线程self.thread_3._signal.connect(self.printfini)self.thread_3.threadpath = xslName[0]self.thread_3.start()  # 开始线程

界面全程通过多线程调用,以保证界面的流畅体验。

程序到这里已经大功告成了

五、打包安装与使用

一、需求又来了,需要打包成exe

我们的甲方觉得我们的程序还过得去,就是在不同电脑上用不了(因为别的电脑没有python的运行环境),而且运用起来还要打开cmd输入py main.py,非常不方便,所以我们需要将程序打包成exe文件供不同电脑使用,也方便不懂python的老师使用。

二、直接利用pyinstaller打包

在py项目文件夹的搜索栏输入cmd打开cmd界面

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQVlHWkMyMzM=,size_20,color_FFFFFF,t_70,g_se,x_16

 然后在命令行输入

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQVlHWkMyMzM=,size_20,color_FFFFFF,t_70,g_se,x_16

 就可以执行打包,这里不赘述实现方法

这样我们就得到了一份

7b0717309cf342deafde30b2f4b3ab60.png

 exe文件,点击运行就可以用了

六、其他问题总结

1)、关于打包

1.打包的文件过大,查阅了很多资料,发现是python的通病了(未解决)

2.无论是exe还是py文件,我们都需要相对应代码的webdriver来使用,可以说这两个文件是连体的,又exe或py就需要有webdriver,不然程序运行不出来

3.如果运行exe文件会弹出webdriver的驱动cmd命令行(未解决)

2)、关于多线程与多线程

1.多线程感觉一般还是运用在并行性需求不是很高的场景里面,能保证主线程的畅通与阻塞和挂起

2.多进程配置难度大,不要写在类方法中,用不了。

3.就同个项目而言,python和java多线程运用差别太大了,在GIL大锁的运行环境下,python多线程无法做到真正的多线程,转而需要资源调用率更高的多进程来使用,而在java中各种锁机制的存在以及jvm的存在,使多线程能够让程序变得尽然有序,高效运行。

3)、关于有可能出现的情况

1.如果程序用了多线程,就不要在pycharm环境中直接运行,利用cmd来运行我们的py文件

2.如果出现所有图片识别不出来的问题,首先看看是不是百度ocr欠费了

七、代码总结

一、代码

总的代码如下

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import random
import json
import sys
import datetime
import os
import shutil
import multiprocessing
import time
import pandas
import fitz
from time import sleep
from aip import AipOcr
import re
from selenium import webdriver
import openpyxl
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QApplication,  QMessageBox, QMainWindow
from PyQt5.Qt import QThread, pyqtSignal
import win32apiclass Params():def __init__(self, json_path):with open(json_path) as f:params = json.load(f)  # 将json格式数据转换为字典self.__dict__.update(params)def save(self, json_path):with open(json_path, 'w') as f:json.dump(self.__dict__, f, indent=4)  # indent缩进级别进行漂亮打印def update(self, json_path):with open(json_path) as f:params = json.load(f)self.__dict__.update(params)@property  # Python内置的@property装饰器就是负责把一个方法变成属性调用的def dict(self):"""Gives dict-like access to Params instance by `params.dict['learning_rate']"""return self.__dict__
class apiscan:def pdf2img(self, pdf_file):print("开始识别")pdf_file_list = os.listdir(pdf_file)self.PNG_path = 'PNG'  # PNG文件夹路径PNGfile_suffix = '.png'  # 后缀try:# 是否有这个路径if not os.path.exists(self.PNG_path):# 创建路径os.makedirs(self.PNG_path)else:shutil.rmtree('PNG')os.makedirs(self.PNG_path)except IOError as e:print("IOError")except Exception as e:print("Exception")print("正在检测中,请耐心等待".center(60))print('预计识别文件如下:')for i in range(len(pdf_file_list)):print(pdf_file_list[i])print('\n')print('预计识别文件数量:' + str(len(pdf_file_list)))print('\n')print('图片生成中……')print('\n')for id in pdf_file_list:pdf = fitz.open(os.path.join(pdf_file, id))for pg in range(pdf.pageCount):page = pdf[pg]rotate = int(0)zoom_x = 2.0zoom_y = 2.0trans =fitz.Matrix(zoom_x, zoom_y).prerotate(rotate)pm = page.get_pixmap(matrix=trans, alpha=False)filename = os.path.splitext(id)[0]pngfilename = self.PNG_path + '\\' + filename + str(pg) + PNGfile_suffixpm.save(pngfilename)print(pngfilename)print('图片生成完毕')print('\n')sleep(1)def toExcel(self, list):print("识别文件正在存储至xsl文件中")newsdf = pandas.DataFrame(list)self.xslpath = datetime.datetime.now().date().strftime('%Y%m%d') +"识别编码"+str(random.randint(0,100))+ '增值税发票信息统计' + '.xlsx'newsdf.to_excel(self.xslpath)print("文件已存至跟目录下的{}".format(self.xslpath))class auto2gdpu:def auto(self, xslpath):print("正在打开浏览器,请输入验证码打开到正确的界面(发票识别界面)后耐心等待")driver = webdriver.Edge(executable_path='msedgedriver.exe')driver.maximize_window()self.excel = openpyxl.load_workbook(xslpath)s1 = self.excel['Sheet1']self.rows = s1.max_rowself.rows += 1print(self.rows)# 设置自动化打开的浏览器访问网址url = 'http://cwsys.gdpu.edu.cn/dlpt/Newindex.aspx'# path = 'chromedriver.exe'driver.get(url)curr_handle = driver.current_window_handledriver.maximize_window()result = open('User.json')self.userJson = json.load(result)print(self.userJson)# print("curr_handle=", curr_handle)driver.find_element_by_xpath('//*[@id="Txt_UserName"]').send_keys(self.userJson["username"])driver.find_element_by_xpath('//*[@id="Txt_PassWord"]').send_keys(self.userJson["password"])# driver.find_element_by_xpath('//*[@id="LinkButton_wsyy"]').click()while True:try:all_handles = driver.window_handlesfor h in all_handles:if h != curr_handle:# 跳转到h窗口driver.switch_to.window(h)# 获取到新窗口的句柄curr_handle = driver.current_window_handleprint("curr_handle=", curr_handle)# input("")driver.switch_to.frame("mainframe")sleep(3)row = 6for i in range(2, self.rows):if i > row:driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_BT_ADD"]').click()row += 1sleep(2)print('第{}行输入中'.format(i - 1))name = s1['B{}'.format(i)].valuenumber = s1['C{}'.format(i)].valuefpdate = s1['E{}'.format(i)].valuepassword = s1['F{}'.format(i)].valuefpfrom = s1['G{}'.format(i)].valuemoney = s1['H{}'.format(i)].valuenomoney = s1['I{}'.format(i)].value# print(name, number, fpdate, password, fpfrom, money, nomoney)sleep(1)if i >9:driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_FPDM"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_FPHM"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_KPRQ"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_YZM"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_KPFMC"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_JE"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_HJ"]'.format(i)).clear()time.sleep(1)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_FPDM"]'.format(i)).send_keys(name)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_FPHM"]'.format(i)).send_keys(number)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_KPRQ"]'.format(i)).send_keys(fpdate)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_YZM"]'.format(i)).send_keys(password)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_KPFMC"]'.format(i)).send_keys(fpfrom)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_JE"]'.format(i)).send_keys(money)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_SE"]'.format(i)).click()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_SE"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_SE"]'.format(i)).send_keys(nomoney)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_HJ"]'.format(i)).click()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl{}_TB_FPDM"]'.format(i)).click()print('第{}行输入成功'.format(i - 1))else:driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_FPDM"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_FPHM"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_KPRQ"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_YZM"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_KPFMC"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_JE"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_SE"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_HJ"]'.format(i)).clear()time.sleep(1)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_FPDM"]'.format(i)).send_keys(name)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_FPHM"]'.format(i)).send_keys(number)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_KPRQ"]'.format(i)).send_keys(fpdate)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_YZM"]'.format(i)).send_keys(password)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_KPFMC"]'.format(i)).send_keys(fpfrom)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_JE"]'.format(i)).send_keys(money)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_SE"]'.format(i)).click()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_SE"]'.format(i)).clear()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_SE"]'.format(i)).send_keys(nomoney)driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_HJ"]'.format(i)).click()driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_GV_ZDFPPL_ctl0{}_TB_FPDM"]'.format(i)).click()print('第{}行输入成功'.format(i - 1))sleep(1)i += 1breakexcept:sleep(10)continuewin32api.MessageBox(0, '输入成功\n请人工校对', '提示')sleep(1000)class finalmain:def imgToXsl(self, imgpath):Toxsl = apiscan()id_list = os.listdir(imgpath)m = multiprocessing.Manager()mylist = m.list()q = m.Queue()ls = len(id_list)start = time.time()pool = multiprocessing.Pool(4)for i in range(ls):print("正在识别第{}张图片,请耐心等待".format(i))id = id_list[i]pool.apply_async(func=forEach, args=(id, imgpath, mylist,), )pool.close()pool.join()end = time.time()finishtime = end - startprint("识别完成用时" + str(finishtime))var = list(mylist)Toxsl.toExcel(var)def pdfToXsl(self, pdf_file):Toxsl = apiscan()Toxsl.pdf2img(pdf_file)imgpath = Toxsl.PNG_pathid_list = os.listdir(imgpath)m = multiprocessing.Manager()mylist = m.list()q = m.Queue()ls = len(id_list)start = time.time()pool = multiprocessing.Pool(4)for i in range(ls):print("正在识别第{}张图片,请耐心等待".format(i))id = id_list[i]pool.apply_async(func=forEach, args=(id, imgpath, mylist,), )pool.close()pool.join()end = time.time()finishtime = end - startprint("识别完成用时" +str(finishtime))var = list(mylist)Toxsl.toExcel(var)def finamain(self, pdfpath):Toxsl = apiscan()Toxsl.pdf2img(pdfpath)imgpath = Toxsl.PNG_pathid_list = os.listdir(imgpath)m = multiprocessing.Manager()mylist = m.list()q = m.Queue()ls = len(id_list)start = time.time()pool = multiprocessing.Pool(4)for i in range(ls):id = id_list[i]pool.apply_async(func=forEach, args=(id, imgpath, mylist,), )pool.close()pool.join()end = time.time()print("已全部识别完成")print("识别完成用时"+end - start)var = list(mylist)Toxsl.toExcel(var)Auto = auto2gdpu()xslpath = Toxsl.xslpathAuto.auto(xslpath)def finamin2Auo(self, xslpath):Auto = auto2gdpu()Auto.auto(xslpath)def saveJson(self,parameters):json_str = json.dumps(parameters, indent=4)self.userJsonname='User.json'with open(self.userJsonname, 'w') as f:  # 创建一个params.json文件f.write(json_str)  # 将json_str写到文件中params = Params(self.userJsonname)params.save(self.userJsonname)  # 将修改后的数据保存class Thread_1(QThread):  # 线程1_signal = pyqtSignal()def __init__(self):super().__init__()self.threadpath = ''def run(self):main = finalmain()main.pdfToXsl(self.threadpath)self._signal.emit()class Thread_2(QThread):  # 线程1_signal = pyqtSignal()def __init__(self):super().__init__()self.threadpath = ''def run(self):main = finalmain()main.imgToXsl(self.threadpath)self._signal.emit()class Thread_3(QThread):  # 线程1_signal = pyqtSignal()def __init__(self):super().__init__()self.threadpath = ''def run(self):main = finalmain()main.finamin2Auo(self.threadpath)self._signal.emit()class Ui_MainWindow(object):def setupUi(self, MainWindow):MainWindow.setObjectName("MainWindow")MainWindow.resize(800, 600)self.centralwidget = QtWidgets.QWidget(MainWindow)self.centralwidget.setObjectName("centralwidget")self.textBrowser = QtWidgets.QTextBrowser(self.centralwidget)self.textBrowser.setGeometry(QtCore.QRect(10, 330, 691, 241))font = QtGui.QFont()font.setPointSize(16)self.textBrowser.setFont(font)self.textBrowser.setObjectName("textBrowser")self.layoutWidget = QtWidgets.QWidget(self.centralwidget)self.layoutWidget.setGeometry(QtCore.QRect(10, 190, 361, 131))self.layoutWidget.setObjectName("layoutWidget")self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)self.verticalLayout.setContentsMargins(0, 0, 0, 0)self.verticalLayout.setObjectName("verticalLayout")self.horizontalLayout = QtWidgets.QHBoxLayout()self.horizontalLayout.setObjectName("horizontalLayout")self.label = QtWidgets.QLabel(self.layoutWidget)self.label.setObjectName("label")self.horizontalLayout.addWidget(self.label)self.username = QtWidgets.QLineEdit(self.layoutWidget)self.username.setObjectName("username")self.horizontalLayout.addWidget(self.username)self.verticalLayout.addLayout(self.horizontalLayout)self.horizontalLayout_2 = QtWidgets.QHBoxLayout()self.horizontalLayout_2.setObjectName("horizontalLayout_2")self.label_2 = QtWidgets.QLabel(self.layoutWidget)self.label_2.setObjectName("label_2")self.horizontalLayout_2.addWidget(self.label_2)self.password = QtWidgets.QLineEdit(self.layoutWidget)self.password.setObjectName("password")self.horizontalLayout_2.addWidget(self.password)self.verticalLayout.addLayout(self.horizontalLayout_2)self.horizontalLayout_3 = QtWidgets.QHBoxLayout()self.horizontalLayout_3.setObjectName("horizontalLayout_3")self.remberbotton = QtWidgets.QPushButton(self.layoutWidget)self.remberbotton.setObjectName("remberbotton")self.horizontalLayout_3.addWidget(self.remberbotton)self.verticalLayout.addLayout(self.horizontalLayout_3)self.splitter = QtWidgets.QSplitter(self.centralwidget)self.splitter.setGeometry(QtCore.QRect(400, 190, 381, 131))self.splitter.setOrientation(QtCore.Qt.Vertical)self.splitter.setObjectName("splitter")self.pdfbuttton = QtWidgets.QPushButton(self.splitter)self.pdfbuttton.setObjectName("pdfbuttton")self.imgbutton = QtWidgets.QPushButton(self.splitter)self.imgbutton.setObjectName("imgbutton")self.xslbutton = QtWidgets.QPushButton(self.splitter)self.xslbutton.setObjectName("xslbutton")self.label_3 = QtWidgets.QLabel(self.centralwidget)self.label_3.setGeometry(QtCore.QRect(100, 40, 611, 141))font = QtGui.QFont()font.setPointSize(76)self.label_3.setFont(font)self.label_3.setObjectName("label_3")self.label_4 = QtWidgets.QLabel(self.centralwidget)self.label_4.setGeometry(QtCore.QRect(640, 160, 151, 31))font = QtGui.QFont()font.setPointSize(13)self.label_4.setFont(font)self.label_4.setObjectName("label_4")self.splitter_2 = QtWidgets.QSplitter(self.centralwidget)self.splitter_2.setGeometry(QtCore.QRect(704, 330, 91, 241))self.splitter_2.setOrientation(QtCore.Qt.Vertical)self.splitter_2.setObjectName("splitter_2")self.stopToXslbutton = QtWidgets.QPushButton(self.splitter_2)self.stopToXslbutton.setObjectName("stopToXslbutton")self.stopToWebbutton = QtWidgets.QPushButton(self.splitter_2)self.stopToWebbutton.setObjectName("stopToWebbutton")MainWindow.setCentralWidget(self.centralwidget)self.statusbar = QtWidgets.QStatusBar(MainWindow)self.statusbar.setObjectName("statusbar")MainWindow.setStatusBar(self.statusbar)self.retranslateUi(MainWindow)QtCore.QMetaObject.connectSlotsByName(MainWindow)def retranslateUi(self, MainWindow):_translate = QtCore.QCoreApplication.translateMainWindow.setWindowTitle(_translate("MainWindow", "发票识别系统(gdpu)"))self.label.setText(_translate("MainWindow", "  账号:"))self.label_2.setText(_translate("MainWindow", "  密码: "))self.remberbotton.setText(_translate("MainWindow", "如第一次使用请输入账号密码,输入完成后请点击该按钮保存"))self.pdfbuttton.setText(_translate("MainWindow", "识别发票pdf文件夹至表格"))self.imgbutton.setText(_translate("MainWindow", "识别发票图片文件夹至表格"))self.xslbutton.setText(_translate("MainWindow", "选择表格上传到网站"))self.label_3.setText(_translate("MainWindow", "发票识别系统"))self.label_4.setText(_translate("MainWindow", "Author:nicegoose"))self.stopToXslbutton.setText(_translate("MainWindow", "停止识别"))self.stopToWebbutton.setText(_translate("MainWindow", "停止上传"))class EmittingStr(QtCore.QObject):textWritten = QtCore.pyqtSignal(str)  # 定义一个发送str的信号def write(self, text):self.textWritten.emit(str(text))class ControlBoard(QMainWindow, Ui_MainWindow):def __init__(self):super(ControlBoard, self).__init__()self.setupUi(self)# 下面将输出重定向到textBrowser中sys.stdout = EmittingStr(textWritten=self.outputWritten)sys.stderr = EmittingStr(textWritten=self.outputWritten)self.pdfbuttton.clicked.connect(self.openPdfFile)self.imgbutton.clicked.connect(self.openImgFile)self.xslbutton.clicked.connect(self.xslToWeb)self.remberbotton.clicked.connect(self.remember)self.stopToXslbutton.clicked.connect(self.closeThread)self.stopToWebbutton.clicked.connect(self.closeWebThread)def outputWritten(self, text):cursor = self.textBrowser.textCursor()cursor.movePosition(QtGui.QTextCursor.End)cursor.insertText(text)self.textBrowser.setTextCursor(cursor)self.textBrowser.ensureCursorVisible()def openPdfFile(self):DirectoryName = QFileDialog.getExistingDirectory(self, '打开文件夹', '')if DirectoryName:print(DirectoryName)self.thread_1 = Thread_1()  # 创建线程self.thread_1._signal.connect(self.printfini)self.thread_1.threadpath = DirectoryNameself.thread_1.start()  # 开始线程def openImgFile(self):DirectoryName = QFileDialog.getExistingDirectory(self, '打开文件夹', '')if DirectoryName:print(DirectoryName)self.thread_2 = Thread_2()  # 创建线程self.thread_2._signal.connect(self.printfini)self.thread_2.threadpath = DirectoryNameself.thread_2.start()  # 开始线程def xslToWeb(self):xslName = QFileDialog.getOpenFileName(self, '打开文件', './')if xslName[0]:print(xslName[0])self.thread_3 = Thread_3()  # 创建线程self.thread_3._signal.connect(self.printfini)self.thread_3.threadpath = xslName[0]self.thread_3.start()  # 开始线程def closeThread(self):try:self.thread_1.terminate()self.thread_1.wait()print("pdf转xsl线程已经停止")except:print()try:self.thread_2.terminate()self.thread_2.wait()print("img转xsl线程已经停止")except:print("好像没啥用")def remember(self):try:self.username1 = self.username.text()self.password1 = self.password.text()if self.username1=="" or self.password1=="":print("账号密码为空别动会影响已保存数据的")else:parameters = {"username": self.username1,"password": self.password1}finalmain1=finalmain()finalmain1.saveJson(parameters)print("账号密码保存成功,请检查是否正确")except:print("保存失败辽,,,,")def closeWebThread(self):try:self.thread_3.terminate()self.thread_3.wait()except:print("好像没啥用")def printfini(self):print("识别文件线程关闭,请检查根目录下的xsl文件后进行上传")tfini = QMessageBox.information(self, '完成', '识别成功', QMessageBox.Ok, QMessageBox.Ok)try:self.thread_1.terminate()self.thread_1.wait()except:self.thread_2.terminate()self.thread_2.wait()def forEach(id, imgpath, list):APP_ID = '25439723'API_KEY = '9n39qdU0apI0IsKlYqsbqQPH'SECRET_key = 'diIhWVLVsu2dmsVnTCwFIqrlKZHm0MNZ'client = AipOcr(APP_ID, API_KEY, SECRET_key)try:img = open(os.path.join(imgpath, id), 'rb').read()res = client.vatInvoice(img)words_result = res['words_result']words_result_num = res['words_result_num']year = ''.join(re.findall('(2017)|(2018)|(2019)|(2020)|(2021)|(2022)|(2023)|(2024)|(2025)|(2026)|(2027)|(2028)|(2029)', words_result['InvoiceDate'])[0])month = ''.join(re.findall('(01)|(02)|(03)|(04)|(05)|(06)|(07)|(08)|(09)|(10)|(11)|(12)',words_result['InvoiceDate'])[1])day = words_result['InvoiceDate'][-3:-1]date = year + ('-') + month + ('-') + daytotalTax = words_result['TotalTax']if totalTax == "***":totalTax = 0# Commodity_num = len(words_result['CommodityName'])# for i in range(1):#Commodity_numlist.append({'发票代码': words_result['InvoiceCode'],'发票号码': words_result['InvoiceNum'],'电子发票号码': '','开票日期': date,'校验码': words_result['CheckCode'][-6:],'开票方名称': words_result['SellerName'],'合计金额': words_result['TotalAmount'],'合计税额': totalTax,'价税合计(小写)': words_result['AmountInFiguers'],})except:print("图像无法识别")print("\n")if __name__ == "__main__":multiprocessing.freeze_support()app = QApplication(sys.argv)win = ControlBoard()win.show()sys.exit(app.exec_())

二、代码解释

对于百度ocr和selenium的输入的解释我都在上面的代码备注与解释了,这里主要解释一下其他的代码部分

在主要运行部分,我加入了多线程来保证程序的流畅运行

class Thread_1(QThread):  # 线程1_signal = pyqtSignal()def __init__(self):super().__init__()self.threadpath = ''def run(self):main = finalmain()main.finamain(self.threadpath)self._signal.emit()

在if __name__ == '__main__':中
   


   

multiprocessing.freeze_support()

保证了多进程的正常使用

        

    app = QApplication(argv)MainWindow1 = QMainWindow()ui = Ui_mainWindow()ui.setupUi(MainWindow1)MainWindow1.show()exit(app.exec_())

保证了pyqt5界面的正常开启与关闭

三、程序的运行流程

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQVlHWkMyMzM=,size_20,color_FFFFFF,t_70,g_se,x_16
   

 八、项目总结

1)项目的市场

总结就是没有市场,主要是为了服务学校的老师与同学

2)经验心得

这个项目让我更加熟练的掌握了python的一些技术:

1)多线程,多进程的使用

2)百度ocr的运用

3)正则表达式的运用

4)selenium网页自动化的使用与计算机网络post和get以及其他爬虫的掌握

5)pyqt5界面的创建设计与映射按钮的使用

九、完结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_895043.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

web程序设计(2)——制作表单演示程序和配置PHP动态网站

实验要求 1、利用老师给的工具phpstudy构建实验环境&#xff1b; 2、运用课程所学变现代码编写左侧控件&#xff1b; 3、在表单页面嵌入自己最喜欢的背景音乐&#xff1b; 4、修改老师给出的PHP程序&#xff0c;在控件点击OK按钮后&#xff0c;在页面上显示每一个控件用户输入…

web程序设计(10)——制作网站首页(boostrap)

实验要求 设计一张网页,网页包括下列元素,网页的实例详见下页的图片 实验目的 熟悉掌握bootstrap框架实验内容 可以到官方网站http://getbootstrap.com/ 上下载 Bootstrap 的最新版本,也可以直接链接官方网站提供的源代码<link rel="stylesheet" href="…

【新闻发布】IIS服务器发布网站

【是什么】 IIS是Internet Information Services的缩写&#xff0c;是由微软公司提供的基于运行Microsoft Windows的互联网基本服务。它包括WWW服务器、FTP服务器和SMTP服务器&#xff0c;是架设个人网站的首选。 【怎么做】 1、开启IIS服务&#xff1a;控制面板--程序--启用…

关于网站上线的那些事

小编我为了能使自己的网站上线,可谓是煞费苦心. 首先,我弄了一个腾讯云,领了一堆豆子不知咋用,可能修行不够.于是我改用了新浪云,简单好用. 首先网页我是用MD 8和editplus 3做的效果也不错. 但是一到上传代码包时候就失败. 打开新浪云 点击lenongzhuang 点击左边应用—…

一次网站遭受dos攻击解决历程

问题现象&#xff1a; 网站访问长时间等待&#xff0c;然后超时失败。 解决历程&#xff1a; 1&#xff0c;先直接查看网站应用的日志。 日志最直接的报错是“GetConnectionTimeoutException”,如果是第一次见到这个错误&#xff0c;可能会第一时间想到就是数据库连接池中的…

竹云IDaaS助力网站APP快速拉新

竹云IDaaS助力网站APP快速拉新 对于网站和APP运营来说&#xff0c;”拉新、留存、促活、转化”这些术语并不陌生&#xff0c;但如何去做&#xff0c;每家都会有不同的方式和想法。 通过官宣、公众号、软文、社交渠道、社群部落、二维码等方式的投放&#xff0c;引流成功后&am…

linux下php+nginx+mysql网站开发环境搭建

php安装 &#xff08;最好先安装好了mysql&#xff09; 下载解压文件后 #指定安装路径 开启安装fpm 指定mysql路径./configure --prefix/usr/local/php --enable-fpm --with-mysql/usr/local/mysql --with-mysqli/usr/local/mysql/bin/mysql_config --enable-pdo --with-pdo-…

给网站做301重定向

一、什么是网站301重定向 页面永久性移走&#xff08;301重定向&#xff09;是一种非常重要的“自动转向”技术。网址重定向最为可行的一种办法。当用户或搜索引擎向网站服务器发出浏览请求时&#xff0c;服务器返回的HTTP数据流中头信息(header)中的状态码的一种&#xff0c;…

大型网站架构技能图谱(Java版)

分享一下我老师大神的人工智能教程&#xff01;零基础&#xff0c;通俗易懂&#xff01;http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章。分享知识&#xff0c;造福人民&#xff0c;实现我们中华民族伟大复兴&#xff01; 在大型网站技术架构中&#xff0c;涉及到许…

大型网站架构演变史(含技术栈与价值观)

这篇文章是参考李智慧的《大型网站技术架构&#xff1a;核心原理与案例分析》和现蘑菇街CTO曽宪杰的《大型网站系统与Java中间件实践》写的一篇读书笔记。 前言 何谓大型网站&#xff1f;大型网站的特点是什么&#xff1f;大型网站架构发生演变的源动力是什么&#xff1f;大型网…

网站受到攻击会有哪些症状?

近期&#xff0c;小编我遇见了很多的网站客户反映说受到了DDoS攻击和CC攻击&#xff0c;被攻击对于开发运营维护人员&#xff0c;对此他们也表示很头疼。现阶段大多数网站使用的开发语言是PHP&#xff0c;JAVA&#xff0c;.net&#xff0c;数据库语言使用的是mysql&#xff0c;…

网站安全有哪些防护措施?

网站安全是指出于防止网站受到外来电脑入侵者对其 网站进行挂马,篡改网页等行为而做出一系列的防御工作。启动一个新网站是一个令人兴奋的项目,充满了许多重要的步骤和决定。但是,作为网站的所有者,您不仅要处理被黑客入侵的后果,还要对其页面上的内容以及人们用来与之交互…

C#:万能表单+ajax实现网站文章阅读次数限制,若达到限制次数,则需付费购买文章(单篇文章)

网站后台创建万能表单&#xff0c;字段如图 html页面判断用户是否登录状态&#xff0c;参数传递ajax异步&#xff0c;成功或失败后的业务处理。 //判断用户是否登陆 <script type"text/javascript"><%csharp%> DTcms.Model.users mucGetUserInfo(); if…

万能表单+ajax+JQuery实现网站文章包月

在单篇文章购买的基础上改进&#xff0c;修改为付费购买实现文章包月阅读。 在之前的基础上新增一个表单&#xff0c;用来控制文章购买时间及金额。 前端内容页代码块 <%csharp%>string chanid ""; <%/csharp%><%set DataTable pingjyiuDtNetWing…

浅谈网站ssl证书不可信的原因!

相信大多网友在浏览网站的过程中都遇到过“ssl证书不可信”的情况&#xff0c;大多网友遇到这种情况可能第一时间是选择离开该网站&#xff0c;有少许人会继续浏览网站。 可能很多人不知道ssl证书是什么&#xff1f;这个作者不多做介绍&#xff0c;网上关于ssl是什么网上多的是…

Ubuntu22.04安装ChineseOcr_lite

文章目录 前言 一、开源项目地址 二、使用步骤 1.安装conda命令 2.创建python3.6环境 3.下载开源项目 4.安装依赖 5.安装make与g 6.编译 7.在chineseocr_lite-master目录下启动项目 总结 前言 环境是windows下使用VirtualBox软件运行的ubuntu22.04LTS 64位带桌面版本 一、开源…

编程竞赛和逻辑谜题网站大集合

我们都知道&#xff0c;每天的编程生活可能有时候会有些乏味。 看这个&#xff1a; Joanna: Peter&#xff0c;你在哪工作呢&#xff1f; Peter: Initech. Joanna: 在哪……&#xff1f;好吧&#xff0c;你在那里做什么呢&#xff1f; Peter: 我在一个小黑屋里&#xff0c;给银…

Centos(阿里云)服务器上的多网站,多Tomcat部署详细步骤

真正阿里云真机部署全部内容有偿分享。节省时间&#xff0c;有需要的可以有偿下载。 下载地址&#xff1a; https://download.csdn.net/download/mzy8000/19821357 例子&#xff1a; ——TOMCAT安装目录&#xff1a; /usr/local/tomcatdalianborui/apache-tomcat-7.0.32 /us…

作为前端开发值得了解的网站集合

https://blog.csdn.net/weixin_43606158/article/details/91164392