/autoEmail

自动从QQ邮箱下载发票并分析整理

Primary LanguagePythonApache License 2.0Apache-2.0

autoEmail_logo.png

背景:因为每个月有报销发票的需求,但每次到报销发票的时候,都需要去邮箱上一个个把发票下载下来,然后分类整理。这些都是耗时且重复的动作,就想着能不能把它自动化,同时看看能不能结合上最近大火的AI

目标:能够按要求自动下载发票文件,同时解析重命名发票文件

自动下载邮件中附件

这一步是很简单的啦,用Selenium就可以实现,我的是QQ邮箱。我们只需要打开浏览器按下F12一步步的查找从登录到进入邮件下载附件都需要点击哪些元素,然后用Selenium代替我们操作就行

登录

BASE_URL = 'https://mail.qq.com/'
def begin():
    global origin_window_handle
    driver.get(Constant.BASE_URL)
    origin_window_handle = driver.current_window_handle

这里登录就没有做的那么麻烦,自己扫码或者输入账号密码即可。

获取邮件

登录到QQ邮箱后,通过F12可以看到主要功能区是在iframe里面,因此我们要先点击收件箱然后切换到iframe中,否则没法找到元素

image.png

def switch_to_frame():
    recv_option = util.getDelayElement(By.PARTIAL_LINK_TEXT, "收件箱")
    recv_option.click()
    main_frame = util.getDelayElement(By.CSS_SELECTOR, "#mainFrame")
    driver.switch_to.frame(main_frame)

接着就获取邮件,每次只能获取当前页数的邮件,我这里是把未读和已读都获取到了

def get_mail_list():
    return driver.find_elements_by_class_name("M") + driver.find_elements_by_class_name("F")

处理邮件

因为我只要处理发票的邮件,用了最简单的方式,只处理当前邮件标题是否包含发票两字,同时发票邮件包括两种,带有附件和不带有附件的,带有附件的是邮件里直接附上了发票文件。不带有附件的是邮件里给了一个链接,需要点击后才能下载或者跳转到其他网站下载。因为两种方式的处理方式不同,所以需要分开存储哪些是带有附件的哪些没带有附件。同时为了避免同名标题不同发票的情况,我们使用mailId来进行存储,后续通过mailId来定位每一个邮件

image.png

def handle_mail():
    global end_flag
    invoice_list = []
    mail_list = get_mail_list()
    mail_num = len(mail_list)
    print(f'mail_num:{mail_num}, page: {current_page}')
    for mail in mail_list:
        mailid = mail.find_element(By.CSS_SELECTOR, 'td.tl.tf ').find_element(By.TAG_NAME, 'nobr').get_attribute(
            'mailid')
        title = mail.find_element_by_class_name("tt").text
        if '发票' in title:
            try:
                mail.find_element(By.CSS_SELECTOR, 'div.cij.Ju')
                invoice_list.append({'title': title, 'mailId': mailid})
            except NoSuchElementException as e:
                invoice_list.append({'title': title, 'mailId': mailid})
    print(f'-------发票: {len(invoice_list)}-------')
    for item in invoice_list:
        monitor.reset_create()
        mailId = item["mailId"]
        title = item["title"]
        tag = driver.find_element_by_xpath(f"//nobr[@mailid='{mailId}']")
        tag.click()
        time.sleep(1)
        if is_out_date():
            end_flag = True
            break
        try:
            if exist_element(By.ID, 'attachment'):
                download_attach()
                check_file(item)
            else:
                handle_no_attach()
                check_file(item)
        except Exception as e:
            record_fail(item)
        switch_to_frame()
        time.sleep(3)

处理带有附件的邮件

附件可能会有多个附件,我们只需要下载PDF文件即可

image.png

def download_attach():
    attachment = util.getDelayElement(By.ID, 'attachment')
    attach_items = attachment.find_elements(By.CSS_SELECTOR, 'div.att_bt.attachitem')
    for attach in attach_items:
        util.getDelayElement(By.CSS_SELECTOR, 'div.name_big')
        if '.pdf' in attach.find_element(By.CSS_SELECTOR, 'div.name_big').find_element(By.TAG_NAME, 'span').text:
            attach.find_element_by_partial_link_text('下载').click()
            break
    time.sleep(3)
    # driver.back()
    driver.refresh()
    switch_to_frame()
    time.sleep(4)

处理没有附件的邮件

这个才是本次的重点,对于没有附件只有下载链接的邮件,如何让selenium知道该点哪里。不同的邮件他们的展示也不同

image.png

image.png 解决方法就是让AI来告诉selenium该点哪里,通过F12可以发现,邮件的内容都是在一个固定的Div里面

image.png 那我们就可以获取这个Div里面的HTML片段,然后告诉AI,让它根据HTML片段解析出带有发票下载链接的标签文本,然后返回,selenium根据这个文本点击,以下是对于prompt

prompt = '''
你是一名HTML解析助手,你需要解析用户上传的HTML片段。
1.解析出片段中带有发票下载链接的超链接标签文本。
2.如果有多个下载链接,则找出下载为PDF格式的超链接标签文本即可。
例如: 
输入: 
<p style="display: flex;justify-content: flex-start;align-items: flex-start;font-size:14px;line-height:20px;">
          <span style="white-space: nowrap;color: #333">下载PDF文件:</span>
          <img width="20" src="https://img.pdd-fapiao.com/biz/bG9uZ2p1bmd3YW5n.png">
          <a href="https://www.hxpdd.com/s/Q3QQGcH49TCm" style="word-break:break-all;margin-left: 10px;color: #3786c7" rel="noopener" target="_blank">HelloWorld</a>
</p>
输出:
{"text":"HelloWorld"}
注意只需要返回对应的标签文本即可,不需要其他内容。结果输出为JSON:{'text':'xxx'}"}
'''

点击过后,一般会有两种结果,一种是点击后能够直接下载发票,另一种是跳转到其他网站后,再点击下载才能下载

image.png 针对这种我们就可以全文搜索带有下载字样的标签进行点击下载。经过上面这一套下来,基本90%的都能成功下载下来

解析整理发票

下载下来的发票命名各异

image.png 我希望能够通过文件名就能知道该发票的类型,金额和开票日期,这里用上了OCR+AI,用OCR来解析发票文件,然后将解析结果送给AI让其根据结果分析该发票属于哪种类别。最后再重命名发票

def parse_invoice():
    invoice_list = get_invoice_list()
    location = config.get_location()
    for invoice in invoice_list:
        file_path = os.path.join(location, invoice)
        invoice_info = OcrUtil.ocr_invoice(file_path)
        new_file_name = get_new_file_name(invoice_info)
        util.rename(invoice, new_file_name)
        print(f'{invoice} : {new_file_name}')


def get_new_file_name(invoice_info):
    date = util.parse_date(invoice_info['Date'], '%Y年%m月%d日', '%Y%m%d')
    summary = get_summary(invoice_info)
    return summary + '_' + invoice_info['Number'] + '_' + invoice_info['Total'].split('.')[0] + '_' + date


def get_summary(item):
    if 'VatElectronicItems' in item:
        info = json.loads(json.dumps(item['VatElectronicItems']))
    else:
        info = json.loads(json.dumps(item['VatInvoiceItemInfos']))
    return AI.ai_summary(info[0]['Name'])

最后就是这个样子

image.png

使用方法

在config.json文件设置文件下载地址和日期,日期的作用是在下载附件时只下载日期之后的附件,在OcrUtil.py文件里设置腾讯云的secret_id和secret_key,在AI.py文件里设置GPT的api_key后,然后运行main.py,等待浏览器拉起后,手动登录后即可

image.png

目前只实现了下载和解析重命名,邮箱目前只支持QQ邮箱,AI目前只支持GPT,后续会支持多个邮箱和AI 同时还在考虑可以加入哪些功能,有建议和优化欢迎大家在GitHub给我提,如果有帮助的话帮忙Star一下

地址:GitHub-autoEmail