1. 程式人生 > 程式設計 >Python實現發票自動校核微信機器人的方法

Python實現發票自動校核微信機器人的方法

製作初衷:

  • 外地開了票到公司後發現資訊有錯誤,無法報銷;
  • 公司的行政和財務經常在工作日被問及公司開票資訊,影響心情和工作;
  • 引入相應的專業APP來解決發票問題對於一般公司成本較高;
  • 看到朋友孟要早睡寫過指令碼來解決這個問題,但因為公司場景不相同,無法複用,所以新寫了一個

本程式碼使用簡單的封裝方法,並做了比較走心的註釋,希望能給初學Python的小夥伴提供一些靈感,也能讓有實際需求的人可以快速修改、使用。

原始碼地址:https://github.com/yc2code/WechatInvoiceParser

P.S. 工具基於微信網頁版,因為微信官方對於賬號有限制,新建的賬號可能無法使用,會報:KeyError: 'pass_ticket'

,如圖:

在這裡插入圖片描述

所以工具只能使用註冊時間較早的賬號

發票自動校核微信機器人程式碼部分

1. 工具檔案 – Utils
包含三個部分:發票校核類 Invoice、解析資料類 DataParser 和推送日誌類 Pushover

  • Invoice 呼叫的百度API,上傳圖片資訊,得到解析資料;
  • DataParser 對得到的解析資料進行整理,得到傳送給使用者的資訊;
  • Pushover 出現呼叫問題時,第一時間相關資訊推送到維護者的裝置上。
# -*- coding: utf-8 -*-
# Utils.py
import base64
import csv
import os
import time
import requests
from Config import config
class Invoice:
 """
 發票識別類
 使用百度發票識別API,免費使用
 官方地址 https://ai.baidu.com/docs#/OCR-API/5099e085
 其它功能及配置請移步官網
 """
 @staticmethod
 def get_pic_content(image_path):
  """
  方法--開啟圖片
  以二進位制格式開啟
  """
  with open(image_path,'rb') as pic:
   return pic.read()
 @staticmethod
 def parse_invoice(image_binary):
  """
  方法--識別圖片
  呼叫百度介面,返回識別後的發票資料
  以下內容基本根據API呼叫的要求所寫,無需糾結
  各類報錯碼在官網文件可查
  百度API註冊及使用教程:http://ai.baidu.com/forum/topic/show/867951
  """
  # 識別質量可選high及normal
  # normal(預設配置)對應普通精度模型,識別速度較快,在四要素的準確率上和high模型保持一致,
  # high對應高精度識別模型,相應的時延會增加,因為超時導致失敗的情況也會增加(錯誤碼282000)
  access_token = "你的access_token"
  api_url = f"https://aip.baidubce.com/rest/2.0/ocr/v1/vat_invoice?access_token={access_token}"
  quality = "high"
  header = {"Content-Type": "application/x-www-form-urlencoded"}
  # 影象資料,base64編碼後進行urlencode,要求base64編碼和urlencode後大小不超過4M,
  # 最短邊至少15px,最長邊最大4096px,支援jpg/jpeg/png/bmp格式
  image_data = base64.b64encode(image_binary)
  try:
   data = {"accuracy": quality,"image": image_data}
   response = requests.post(api_url,data=data,headers=header)
   if response.status_code != 200:
    print(time.ctime()[:-5],"Failed to get info")
    return None
   else:
    result = response.json()["words_result"]
    invoice_data = {
     '檢索日期': '-'.join(time.ctime().split()[1:3]),'發票程式碼': result['InvoiceCode'],'發票號碼': result['InvoiceNum'],'開票日期': result['InvoiceDate'],'合計金額': result['TotalAmount'],'價稅合計': result['AmountInFiguers'],'銷售方名稱': result['SellerName'],'銷售方稅號': result['SellerRegisterNum'],'購方名稱': result['PurchaserName'],'購方稅號': result['PurchaserRegisterNum'],"發票型別": result["InvoiceType"]
    }
    return invoice_data
  except:
   message = "發票識別API調用出現錯誤"
   Pushover.push_message(message)
   return None
  finally:
   print(time.ctime()[:-5],"產生一次了呼叫")
 @staticmethod
 def save_to_csv(invoice_data):
  """
  方法--日誌儲存
  將識別記錄寫入資料夾下work_log.csv檔案
  若無此檔案則自動建立並寫入表頭
  """
  if "work_log.csv" not in os.listdir():
   not_found = True
  else:
   not_found = False
  with open('./work_log.csv','a+') as file:
   writer = csv.writer(file)
   if not_found:
    writer.writerow(invoice_data.keys())
   writer.writerow(invoice_data.values())
 @staticmethod
 def run(image_path):
  """
  主方法
  解析完成返回資訊,否則返回None
  """
  image_binary = Invoice.get_pic_content(image_path)
  invoice_data = Invoice.parse_invoice(image_binary)
  if invoice_data:
   Invoice.save_to_csv(invoice_data)
   return invoice_data
  return None
class DataParser:
 """
 資料分析類
 對識別返回後的資料進行整理,並於預設資訊對比,檢視有無錯誤
 這裡只簡單實現整理資訊和檢查名稱和稅號的方法,有興趣可以增加其他豐富的方法
 """
 def __init__(self,invoice_data):
  self.invoice_data = invoice_data
 def get_detail_message(self):
  """
  對得到的發票資訊的格式進行整理
  :return: 返回整理好的發票資訊
  """
  values = [value for value in self.invoice_data.values()]
  detail_mess = f"完整資訊為:" \
   f"\n發票程式碼: {values[1]}\n發票號碼: {values[2]}\n開票日期: {values[3]}" \
   f"\n合計金額: {values[4]}\n價稅合計: {values[5]}\n銷售方名稱: {values[6]}" \
   f"\n銷售方稅號: {values[7]}\n購方名稱: {values[8]}\n購方稅號:{values[9]}"
  return detail_mess
 def get_brief_message(self):
  """
  將資訊中的名稱和稅號和預設值進行對比
  只做對錯判斷,讀者豐富一下可以增加指出錯誤位置的資訊
  :return: 返回判斷的資訊
  """
  if self.invoice_data["購方名稱"] == config["company_name"]:
   brief_mess = "購方名稱正確"
  else:
   brief_mess = "!購方名稱錯誤!"
  if self.invoice_data["購方稅號"] == config["company_tax_number"]:
   brief_mess += "\n購方稅號正確"
  else:
   brief_mess += "\n!購方稅號錯誤!"
  return brief_mess
 def parse(self):
  brief_mess = self.get_brief_message()
  detail_mess = self.get_detail_message()
  return brief_mess,detail_mess
class Pushover:
 """
 訊息推送類
 本次使用Pushover為推送訊息軟體(30 RMB,永久,推薦)
 官網 https://pushover.net/
 可以向微信一樣把相關資訊推送至不同裝置
 如果不需要可以把相關程式碼註釋掉
 """
 @staticmethod
 def push_message(message):
  message += ">>>來自Python發票校驗"
  try:
   requests.post("https://api.pushover.net/1/messages.json",data={
    "token": "你的Token","user": "你的User","message": message
   })
  except Exception as e:
   print(time.ctime()[:-5],"Pushover failed",e,sep="\n>>>>>>>>>>\n")

2. 微信機器人檔案 – Wechat
包含一個部分:微信處理類 Wechat
作用是初始化機器人,對微信的訊息進行處理,分析並作出迴應。

# -*- coding: utf-8 -*-
# Wechat.py
import os
from wxpy import *
class Wechat:
 """
 微信處理類
 對微信的訊息進行處理,分析並作出迴應
 """
 def __init__(self,group_name,admin_name):
  self.bot = Bot() # 類被例項化的時候即對機器人例項化
  self.group_name = group_name # 指定群聊名
  self.admin_name = admin_name # 管理員微信名
  self.received_mess_list = [] # 過濾後的訊息列表
  self.order_list = [] # 管理命令列表
  self.pic_list = [] # 待解析圖片絕對路徑列表
 def get_group_mess(self):
  """
  方法--獲取訊息
  獲取所有正常訊息,進行過濾後存進訊息列表
  """
  # 呼叫此方法時先清空上次呼叫時列表所儲存的資料
  self.received_mess_list = []
  for message in self.bot.messages:
   # 如果為指定群聊或管理員的訊息,存入group_mess
   sender = message.sender.name
   # >>>這裡有一點要注意,如果你是用一個微信作為機器人且作為管理員<<<
   # >>>然後用這個微訊號在群聊發訊息,則資訊sender會之指向自己而不是群聊<<<
   # >>>建議使用單獨一個微訊號作為機器人
   if sender == self.group_name or sender == self.admin_name:
    self.received_mess_list.append(message)
   # 其他的訊息過濾掉
   self.bot.messages.remove(message)
  return None
 def parse_mess(self):
  """
  方法--處理群聊訊息
  過濾獲得的指定群聊訊息
  設定所有新增群聊圖片的絕對路徑及群聊中產生的文字命令
  """
  # 呼叫此方法時先清空上次呼叫時列表所儲存的資料
  self.pic_list = []
  self.order_list = []
  # self.group_order = []
  for message in self.received_mess_list:
   # 如果資訊型別為圖片,則儲存圖片並新增到圖片列表
   if message.type == 'Picture' and message.file_name.split('.')[-1] != 'gif':
    self.pic_list.append(Wechat.save_file(message))
   # 如果訊息型別為文字,則視為命令,儲存到命令列表中
   if message.type == 'Text':
    self.order_list.append(message)
  return None
 @staticmethod
 def save_file(image):
  """
  方法--儲存圖片
  這裡使用靜態方法,是因為本方法和類沒有內部互動,靜態方法可以方便其他程式的呼叫
  解析名稱,設定絕對路徑,儲存
  :param image: 接收到的圖片(可以看成是wxpy產生的圖片類,它具有方法和屬性)
  :return: 返回圖片的絕對路徑
  """
  path = os.getcwd()
  # 如果路徑下沒有Pictures資料夾,則建立,以存放接收到的待識別圖片
  if "Pictures" not in os.listdir():
   os.mkdir("Pictures")
  # 設定一個預設的圖片格式字尾
  file_postfix = "png"
  try:
   # 嘗試把圖片的名稱拆分,分別獲取名稱和字尾
   file_name,file_postfix = image.file_name.split('.')
  except Exception:
   # 當然有時候可能拆分不了,就把預設的字尾給它
   file_name = image.file_name
  # 賦予絕對路徑
  file_path = path + '/Pictures/' + file_name + '.' + file_postfix
  # 將圖片儲存到指定路徑下
  image.get_file(file_path)
  return file_path
 def send_group_mess(self,message):
  """
  方法--傳送群訊息
  :param message: 需要傳送的內容
  """
  try:
   # 如果群聊名稱被改變,搜尋時會報錯,如果找不到群聊,訊息不會發送
   group = self.bot.groups().search(self.group_name)[0]
   group.send(message)
  except IndexError:
   print("找不到指定群聊,資訊傳送失敗")
   return None
 def send_parse_log(self):
  """
  方法--傳送查詢日誌
  向群聊內傳送查詢日誌
  """
  try:
   # 如果群聊名稱被改變,搜尋時會報錯,如果找不到群聊,訊息不會發送
   group = self.bot.groups().search(self.group_name)[0]
  except IndexError:
   print("找不到指定群聊,查詢日誌傳送失敗")
   return None
  try:
   group.send_file("./work_log.csv")
  except:
   group.send("Oops,no log yet")
  return None
 def send_system_log(self):
  """
  方法--傳送系統日誌
  向群聊內傳送查詢日誌
  """
  try:
   # 如果群聊名稱被改變,搜尋時會報錯,如果找不到群聊,訊息不會發送
   group = self.bot.groups().search(self.group_name)[0]
  except IndexError:
   print("找不到指定群聊,系統日誌傳送失敗")
   return None
  try:
   group.send_file("./system_log.text")
  except:
   group.send("System log not found")
  return None

3. 主檔案 – Main
包含一個main函式,一部分為發票識別和處理,另一部分對於指令做出反應。

# -*- coding: utf-8 -*-
# Main.py
import time
from Utils import Invoice,DataParser
from Config import config
from Wechat import *
# Author : 達希
# Email : [email protected]
def main():
 """
 主方法
 一部分為發票識別和處理,另一部分對於指令做出反應
 """
 # 輸出重定向,將print語句都寫進系統日誌檔案
 file = open("./system_log.text","a+")
 sys.stdout = file
 # 例項化微信機器人,傳入群聊名和管理員名
 wechat = Wechat(config["group_name"],config["admin_name"])
 while True:
  time.sleep(1)
  wechat.get_group_mess()
  wechat.parse_mess()
  # 若群聊有要處理的圖片,則迭代解析
  if wechat.pic_list:
   for pic in wechat.pic_list:
    invoice_data = Invoice.run(pic)
    if invoice_data:
     data_parser = DataParser(invoice_data)
     brief_mess,detail_mess = data_parser.parse()
     wechat.send_group_mess(detail_mess) # 先發送發票識別詳細資訊
     time.sleep(0.5)
     wechat.send_group_mess(brief_mess) # 返回名稱和稅號是否有錯誤
    else:
     wechat.send_group_mess("請求未成功,請重試或聯絡管理員")
  # 若有相關命令,則做出相應反應
  if wechat.order_list:
   for order in wechat.order_list:
    if "開票資訊" in order.text:
     wechat.send_group_mess(config["company_name"])
     time.sleep(0.5)
     wechat.send_group_mess(config["company_tax_number"])
    elif "SEND LOG" in order.text:
     wechat.send_parse_log()
    elif "SEND SYSTEM LOG" in order.text:
     wechat.send_system_log()
    elif "BREAK" in order.text:
     wechat.send_group_mess("收到關機指令,正在關機")
     file.close()
     return None
if __name__ == "__main__":
 main()

4. 配置檔案 – Config

包含微信的配置檔案資訊

config = {
 "group_name": "發票校核ASAP",# 校核群聊名稱,由於本程式碼預設沒有同名群聊,所以建議設為複雜值
 "admin_name": "達希",# 管理員微信名(非備註)
 "company_name": "程式碼網路技術無限公司",# 預設購方名稱
 "company_tax_number": "XXX00000000000XXX" # 預設購方稅號
}

在這裡插入圖片描述

另外,程式碼在執行時會在同文件夾下建立一個Picture的資料夾,用於儲存待解析的圖片,會建立 work_log.csv 檔案,用於儲存識別資訊的記錄,還有 system_log.text 用於輸出執行相應的日誌。

由於本身需求較少,所以以上程式碼功能相對單薄,僅僅作為一個輔助的小指令碼使用。若要進行優化完善,wxpy庫提供了很多豐富的功能,可以在此基礎上打造更加合理完善的,符合個性化需求的微信機器人。

總結

到此這篇關於Python製作發票自動校核微信機器人的文章就介紹到這了,更多相關Python製作發票自動校核微信機器人內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!