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

目录

一、项目背景

二、框架确定与技术选型

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_token
        headers = {'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 re


class 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)
        # 这里是遍历我们取得的pdf
        for 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.0
                zoom_y = 2.0
                trans = 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_suffix
                pm.save(pngfilename)
                print(pngfilename)

        print('图片生成完毕')
        print('\n')
        time.sleep(1)

    # 这里是拿到我们列表后转入excel
    def 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的id
    API_KEY = '***'  # 百度api的应用Key
    SECRET_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,只取值到2022
        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 + ('-') + 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()  # 实例化我们的apiscan
    toxls.pdf2img(pdf_path)  # 先把pdf转换成图片
    imgpath = toxls.PNG_path  # 图片输出的地址
    id_list = os.listdir(imgpath) #通过os取得我们图片文件名列表
    #下面是多进程部分
    m = multiprocessing.Manager()#实例一个Manager
    mylist = m.list()#创建一个可以在主进程和子进程之间通信的managerlist
    q = 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)#dbug
    print(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,我就用了edge
        driver = 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 += 1
        print(self.rows)
        # 设置自动化打开的浏览器访问网址
        url = 'http://cwsys.**.edu.cn/dlpt/Newindex.aspx'
        driver.get(url)  # 打开我们学校的财务网页
        curr_handle = driver.current_window_handle
        driver.maximize_window()
        # print("curr_handle=", curr_handle)
        win32api.MessageBox(0, '当到达指定界面时程序运行', '提示')
        # 利用while循环和try,expect,来保证我们能打开对应网页再行动
        while (True):
            # 尝试获取我们是否到达对应网页
            try:
                # 检测webdriver是否打开新的界面
                all_handles = driver.window_handles
                for h in all_handles:
                    if h != curr_handle:
                        # 跳转到h窗口
                        driver.switch_to.window(h)
                        # 获取到新窗口的句柄
                        curr_handle = driver.current_window_handle
                        print("curr_handle=", curr_handle)
                # input("")

                driver.switch_to.frame("mainframe")
                time.sleep(3)
                # 下面是无聊的输入
                row = 6
                for i in range(2, self.rows):
                    if i > row:
                        driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_BT_ADD"]').click()
                        row += 1
                        time.sleep(2)
                    print('第{}行输入中'.format(i - 1))
                    name = s1['B{}'.format(i)].value
                    number = s1['C{}'.format(i)].value
                    fpdate = s1['E{}'.format(i)].value
                    password = s1['F{}'.format(i)].value
                    fpfrom = s1['G{}'.format(i)].value
                    money = s1['H{}'.format(i)].value
                    nomoney = 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 += 1
                break
            except:
                time.sleep(10)
                continue

        shutil.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_path
        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):
            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.xslpath
        Auto.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, QtWidgets


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.translate
        MainWindow.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 = DirectoryName
            self.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 = DirectoryName
            self.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 win32api


class 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.0
                zoom_y = 2.0
                trans =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_suffix
                pm.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_row
        self.rows += 1
        print(self.rows)
        # 设置自动化打开的浏览器访问网址
        url = 'http://cwsys.gdpu.edu.cn/dlpt/Newindex.aspx'
        # path = 'chromedriver.exe'
        driver.get(url)
        curr_handle = driver.current_window_handle
        driver.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_handles
                for h in all_handles:
                    if h != curr_handle:
                        # 跳转到h窗口
                        driver.switch_to.window(h)
                        # 获取到新窗口的句柄
                        curr_handle = driver.current_window_handle
                        print("curr_handle=", curr_handle)
                # input("")

                driver.switch_to.frame("mainframe")
                sleep(3)
                row = 6
                for i in range(2, self.rows):
                    if i > row:
                        driver.find_element_by_xpath('//*[@id="ctl00_ContentPlaceHolder1_dzfp_BT_ADD"]').click()
                        row += 1
                        sleep(2)
                    print('第{}行输入中'.format(i - 1))
                    name = s1['B{}'.format(i)].value
                    number = s1['C{}'.format(i)].value
                    fpdate = s1['E{}'.format(i)].value
                    password = s1['F{}'.format(i)].value
                    fpfrom = s1['G{}'.format(i)].value
                    money = s1['H{}'.format(i)].value
                    nomoney = 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 += 1
                break
            except:
                sleep(10)
                continue
        win32api.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 - start
        print("识别完成用时" + str(finishtime))
        var = list(mylist)
        Toxsl.toExcel(var)


    def pdfToXsl(self, pdf_file):
        Toxsl = apiscan()
        Toxsl.pdf2img(pdf_file)
        imgpath = Toxsl.PNG_path
        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 - start
        print("识别完成用时" +str(finishtime))
        var = list(mylist)
        Toxsl.toExcel(var)

    def finamain(self, pdfpath):
        Toxsl = apiscan()
        Toxsl.pdf2img(pdfpath)
        imgpath = Toxsl.PNG_path
        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):
            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.xslpath
        Auto.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.translate
        MainWindow.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 = DirectoryName
            self.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 = DirectoryName
            self.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 + ('-') + day

        totalTax = words_result['TotalTax']
        if totalTax == "***":
            totalTax = 0
        # Commodity_num = len(words_result['CommodityName'])
        # for i in range(1):#Commodity_num
        list.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界面的创建设计与映射按钮的使用

九、完结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值