最近工作上有个发票查验真伪的需求,由于发票数量不多,我都是让业务员把发票拍照然后通过系统把照片上传上来,然后人工到国税发票查验平台输入发票信息进行查验,但是现在发票数量越来越多,人工查验的效率太低,实在是处理不过来了。后来在网上看到一个大佬写的自动发票查验的文章和提供的测试地址: 发票查验测试链接,我觉得他的这种方法很不错,后来又进一步了解了一下他的大概实现逻辑,再加上我的一些想法和实践总结如下,分享出来供大家参考一下,希望有这方面经验的大佬多多指教。
言归正传,发票查验其实就是在国家发票查验平台上输入发票代码、发票号码、开票日期、开票金额或校验码后六位来查询发票的详情。这里面还有一个验证码,验证码是用数字、字母和汉字组成的,说实在的有时候我都看不清楚验证码是什么,这个输入验证码的动作非常耗时,有时需要输入几遍才能通过。如果能够自动识别出验证码是什么,那就完全可以通过RPA机器人流程自动化处理来模拟人工输入,然后再直接抓取发票查验结果网页里面Element的value值获取所有的发票票面信息。当然我这种想法还不是最快的方式,刚才说的那位大佬他都不需要加载出网页,直接通过后台代码就能进行发票验证码的识别和post验证数据到国税平台,并解析出国税平台response回来的发票详细数据。他的这种方式我还没试验成功,但他提供的测试页面发票查询速度是真的快。
验证码识别第一步我是先去掉多余的颜色,因为验证码是需要我输入红色、蓝色、黑色、黄色期中一种颜色的字符,我先把它要求颜色以外的颜色去掉,去色的代码我写的如下:
img = Image.open(fileFullPath) img = img.convert("RGB") pixdata = img.load() for y in range(img.size[1]): for x in range(img.size[0]): pix = pixdata[x, y] if colorType == '2': # 255, 0, 0 红色 if pix[0] >= 128 and pix[1] < 128 and pix[2] < 128: pixdata[x, y] = (0, 0, 0) else: pixdata[x, y] = (255, 255, 255) elif colorType == '3': # 255, 255, 0 黄色 if pix[0] > 128 and pix[1] > 128 and pix[2] < 128: pixdata[x, y] = (0, 0, 0) else: pixdata[x, y] = (255, 255, 255) elif colorType == '1': # 0, 0, 255 蓝色 if pix[0] < 128 and pix[1] < 128 and pix[2] > 128: pixdata[x, y] = (0, 0, 0) else: pixdata[x, y] = (255, 255, 255) else: # 0, 0, 0 # 黑色 if pix[0] < 128 and pix[1] < 128 and pix[2] < 128: pixdata[x, y] = (0, 0, 0) else: pixdata[x, y] = (255, 255, 255)
去色之后只留下了要识别的字符。然后我用的是百度飞浆的PaddleOCR来实现图片的识别,我试验了它的大、中、小三个模型,发现ch_PP-OCRv4这套模型的识别速度和稳定性最好,PaddleOCR的本地话部署网上有不少教程,部署没有什么难度,识别率能到60%,识别失败的情况多是字符中含有中文造成的。
有了识别好的验证码下一步就是RPA了,我开始时是写RPA的脚本,后来发现这个过程太痛苦了,不同发票类型的查验结果界面还不一样,后来我发现用edge浏览器的webview2控件直接打开国税发票查询页面,这样就可以通过Selenium.WebDriver来操作浏览器,并可以通过命令行直接获得界面上Element的值,代码示例如图所示
JDCFPObject = JDCFP() JDCFPObject.skph = getElementText('jqbm_jdcfp') JDCFPObject.ghdw = getElementText('ghdw_jdcfp') JDCFPObject.sfzhm = getElementText('gfsbh_jdcfp') JDCFPObject.xhdwmc = getElementText('xhdwmc_jdcfp') JDCFPObject.nsrsbh = getElementText('nsrsbh_jdcfp') def getElementText(elementId): try: element = driver.find_element(By.ID, elementId) textStr = element.text return textStr except Exception as e1: #err = repr(e1) #raise Exception('获取id为:' + elementId + ' 的text出错(' + err + ')') return ''
这种方法获取到发票查验结果后就可以直接写代码和现有的系统做集成了。我这套方案查验一张发票大概需要10秒左右,比起那位大佬的查询速度慢很多。我这套方案核心就在于发票验证码的识别上,慢也是因为这个环节识别的验证码有一半的机会不正确,需要反复进行识别,后续我打算用一些打过标记的验证码来训练一下PaddleOCR的模型,估计会提高不少识别效率。总之每月初时几百张发票大概大半天的时间就可以自动查验完毕。这个效率对我来说是够用了,我是真没搞明白他是怎么实现这么快的查验速度的,希望有知道的大佬共享一下实现方案。