Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

WRCoding/autoEmail

Repository files navigation

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

About

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

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

AltStyle によって変換されたページ (->オリジナル) /