又要报销了,还在手动下载整理发票吗?

这篇具有很好参考价值的文章主要介绍了又要报销了,还在手动下载整理发票吗?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

大多数公司都是每个月定期提交报销,一般报销用的发票都是电子发票发到邮箱,每次要报销时都需要登录邮箱,点开邮件,一个个下载整理,工作量不大,但是发票多了也着实很烦。这个月终于下决心把这个过程自动化一下。

思路

查看了一下邮箱里的发票邮件,虽然主题内容格式不固定,但是基本都包含“发票”,所以可以用“发票”关键词将发票邮件筛选出来。然后解析发票邮件内容,将发票pdf文件提取下载,并整理到指定文件夹。

嗯,就这么简单。

实现

这种事还是用python比较快吧,搞起。

EMAIL相关库

在Python中,有一些常用的库可以用来操作和处理邮件(email):

  1. smtplibemail 模块

    Python标准库中的smtplibemail模块可以用来发送邮件。smtplib模块负责连接到SMTP服务器并发送邮件,而email模块负责创建和解析邮件内容。这两个模块结合使用可以实现邮件的发送。

    示例代码:

    import smtplib
    from email.message import EmailMessage
    
    msg = EmailMessage()
    msg.set_content('This is a test email sent from Python.')
    
    msg['Subject'] = 'Test Email'
    msg['From'] = 'sender@example.com'
    msg['To'] = 'recipient@example.com'
    
    server = smtplib.SMTP('smtp.example.com')
    server.send_message(msg)
    server.quit()
    
  2. imaplibpoplib 模块

    Python的imaplibpoplib模块可以用来接收邮件。imaplib模块用于连接到IMAP服务器,而poplib模块用于连接到POP3服务器。这两个模块可以用来检索邮件。

    示例代码:

    import imaplib
    import email
    
    mail = imaplib.IMAP4_SSL('imap.example.com')
    mail.login('username', 'password')
    mail.select('inbox')
    
    status, messages = mail.search(None, 'ALL')
    messages = messages[0].split()
    
    for mail_id in messages:
        _, msg = mail.fetch(mail_id, '(RFC822)')
        for response_part in msg:
            if isinstance(response_part, tuple):
                email_message = email.message_from_bytes(response_part[1])
                print(email_message['From'])
                print(email_message['Subject'])
                print(email.utils.parsedate(email_message['Date']))
    
    mail.close()
    mail.logout()
    
  3. yagmail

    yagmail是一个简化了发送和接收邮件的Python库。它简化了使用SMTP发送邮件和IMAP接收邮件的过程,提供了更加友好的API。

    示例代码:

    import yagmail
    
    yag = yagmail.SMTP('sender@example.com', 'password')
    contents = ['This is the body of the email', '/path/to/attachment.pdf']
    yag.send('recipient@example.com', 'Subject', contents)
    yag.close()
    

    yagmail库会处理SMTP服务器的连接和邮件发送,同时也可以方便地添加附件。

这些库提供了不同的功能和灵活性,可以根据自己的需求选择合适的库来操作邮件。

筛选发票邮件

这里一开始走了一点弯路。最初的想法是通过程序筛选发票邮件,然后将发票邮件移动到"发票"文件夹。

但是这样有两个问题,一是这样需要读取全部邮件,筛选出发票邮件,不符合最小权限原则,筛选完还需要把非发票邮件重新标记为“未读”;二是移动到"发票"文件夹这个操作的兼容性不太好,并不是所有邮箱服务商都支持这个操作。

后来想到其实邮箱都有设置收信规则的功能,可以设置一个规则,将收到的发票邮件移动到"发票"文件夹。这样我们就完全可以用自己常用的邮箱来接收发票邮件。

建议还是给接收发票邮件单独设置一个邮箱。

有了前面的基础,程序中就只需要检查指定的“发票”文件夹中的邮件就好了。

这部分功能被封装到EmailClient类中:

class EmailClient:
    def __init__(self, server, username, password):
        self.server = server
        self.username = username
        self.password = password
        self.client = None

    def connect(self):
        try:
            self.client = imapclient.IMAPClient(self.server, ssl=True)
            self.client.login(self.username, self.password)
            self.client.id_({"name": "IMAPClient", "version": "1.0.0"})
            return True
        except Exception as e:
            print(f"Failed to connect to the email server: {e}")
            return False

    def list_folders(self):
        if not self.client:
            print("Not connected to the email server. Call 'connect' method first.")
            return []

        folder_list = self.client.list_folders()
        return [folder_info[2] for folder_info in folder_list]

    def fapiao_folder_exists(self):
        if "发票" in self.list_folders():
            return True
        else:
            print("'发票'文件夹不存在,正在创建...")
            self.client.create_folder("发票")
            print("已创建'发票'文件夹,请登录邮箱创建收信规则。")
            return False

    def get_fapiao_emails(self, search_criteria=["UNSEEN"]):
        fapiao_emails = []

        if not self.client:
            print("Not connected to the email server. Call 'connect' method first.")
            return []

        self.client.select_folder("发票")
        email_ids = self.client.search(search_criteria)

        for email_id in email_ids:
            email_message = self.fetch_email_content(email_id)
            decoded_subject = email.header.decode_header(email_message["Subject"])

            # 初始化一个空字符串来存储解码后的主题文本
            subject_text = ""

            # 遍历解码结果
            for part, encoding in decoded_subject:
                # 如果编码为None,则假定使用UTF-8编码
                if encoding is None:
                    subject_text += part
                else:
                    # 使用指定编码解码部分
                    subject_text += part.decode(encoding, errors="ignore")

            # 检查解码后的主题文本是否包含"发票"
            if "发票" in subject_text:
                fapiao_emails.append([email_id, email_message])
            else:
                print(f"邮件主题不包含'发票',已标记'未读',请登录邮箱检查")
                print(f"email_id: {email_id}")
                print(f"email_subject: {subject_text}")
                print("-" * 80)
                self.set_email_unread(email_id)

        return fapiao_emails

以上代码定义了一个名为EmailClient的Python类,用于连接到邮件服务器,检索特定文件夹中的未读邮件,并再次检查主题是否包含“发票”关键词。

  1. __init__(self, server, username, password):

    • 这是类的构造函数,用于初始化EmailClient对象。它接受三个参数:server(邮件服务器地址)、username(邮箱用户名)和password(邮箱密码)。
    • 它将这些参数存储在类的实例变量中,以便在整个类中使用。
  2. connect(self):

    • 这个方法用于连接到邮件服务器。它使用imapclient库创建了一个IMAP客户端连接,该连接使用SSL加密。
    • 如果连接成功,方法返回True,否则返回False
  3. list_folders(self):

    • 这个方法用于列出邮箱中的所有文件夹。它首先检查是否已连接到邮件服务器,如果没有连接,它会打印一条错误消息并返回一个空列表。
    • 如果已连接,它使用IMAP客户端的list_folders()方法获取文件夹列表,并返回这些文件夹的名称。
  4. fapiao_folder_exists(self):

    • 这个方法用于检查是否存在名为“发票”的文件夹。如果存在,它返回True。如果不存在,它尝试创建这个文件夹,并返回False
    • 如果文件夹不存在,它会打印一条消息,然后使用IMAP客户端的create_folder()方法创建一个名为“发票”的文件夹。
  5. get_fapiao_emails(self, search_criteria=["UNSEEN"]):

    • 这个方法用于获取未读邮件中主题包含“发票”的邮件。它首先检查是否已连接到邮件服务器,如果没有连接,它会打印一条错误消息并返回一个空列表。
    • 如果已连接,它选择“发票”文件夹,并使用IMAP客户端的search()方法获取满足指定搜索条件(默认为未读邮件)的邮件ID。
    • 然后,它遍历这些邮件,解码邮件主题,并检查主题中是否包含“发票”关键词。如果包含,将邮件的ID和消息存储在一个列表中,并返回这个列表。
    • 如果主题不包含“发票”,它会将邮件标记为“未读”状态,然后打印一条消息。

下载发票

获取发票邮件的内容后,接下来就是对发票内容进行解析,找到发票文件并下载。

这里必须吐槽下,不同商家的发票邮件真的是五花八门,邮件内容格式各有特色。其实也是带给我们一些思考,像类似发票这种票据,应该从政府或行业层面出台统一标准,规定好数据交换格式,这样才能最大化数据流通效率。当然了,标准的制定通常是滞后行业发展的,欧美发达经济体那么标准体系那么健全,依然有大量企业使用自定义格式,导致数据交换效率低下。这就只能靠全社会一起努力了,尽早实现标准化、系统化。

目前下载发票使用了下面3中解析策略:

  1. 以附件发送发票pdf文件的邮件最好处理,这个符合邮件标准,不管正文说了啥,我们直接下载pdf附件就好了。
  2. 没有pdf文件附件的邮件,需要解析正文中的链接,找到pdf文件链接,然后下载。
  3. 最坑的就是正文中的链接连下载链接都不是,而是一个版式文件预览,然后还得手动点击按钮下载。这种目前就只能浏览器打开链接,模拟点击下载按钮了。这个过程可能需要人工干预。

下载了发票pdf文件后就比较好办了,把他们存到对应文件夹就好,按照日期整理到对应"年份/月份"文件夹。

发票下载被封装到FapiaoDownloader这个类:

class FapiaoDownloader:
    def __init__(
        self,
        download_dir=os.path.abspath(
            os.path.join(os.path.dirname(__file__), "../fapiao")
        ),
    ):
        # 创建输出目录

        if not os.path.exists(download_dir):
            os.makedirs(download_dir)

        self.download_dir = download_dir

    def download_fapiao(self, fapiao_emails):
        fapiao_pdfs = []
        for fapiao_email in fapiao_emails:
            # 'Mon, 9 Oct 2023 17:05:39 +0800'
            fapiao_email_date_str = decode_header(fapiao_email[1]["Date"])[0][0]
            fapiao_email_date = datetime.strptime(
                fapiao_email_date_str, "%a, %d %b %Y %H:%M:%S %z"
            )
            fapiao_email_month = fapiao_email_date.strftime("%Y/%m")
            download_dir = os.path.abspath(os.path.join(self.download_dir, fapiao_email_month))
            if not os.path.exists(download_dir):
                os.makedirs(download_dir)

            # 解析邮件附件
            pdf_attachments = self._download_attachments(
                fapiao_email, download_dir, fapiao_email_date
            )
            if pdf_attachments:
                # 邮件包含附件,跳过解析邮件正文
                fapiao_pdfs.extend(pdf_attachments)
                continue
            pdfs = self._download_url(fapiao_email, download_dir, fapiao_email_date)
            if pdfs:
                fapiao_pdfs.extend(pdfs)

        return fapiao_pdfs

下载后的效果:

又要报销了,还在手动下载整理发票吗?,python,python

未来路线

这个小工具的代码量不大,但是已经基本可以满足我的需求了。

未来主要的更新方向有3个:

  1. 跟进发票改革,适应新政策,比如即将全面实施的全电发票。
  2. 减少漏收漏下,提高发票下载的准确性和成功率。
  3. 增加发票信息提取功能,提取发票中的关键信息,比如发票金额、发票时间、发票类型、发票抬头等。这个功能对个人意义不大,但可以用于企业发票管理,比如发票到期提醒、发票金额统计等。良好的财务实践必要要求规范的票据管理,同时还要积极处理流程中的数据要素。

获取方式

  1. 无痛使用可移步 发票自动下载整理机器人
  2. 贡献源码可访问 github仓库

题外:fapiao vs invoice

因为国内的发票和国外的invoice有些细节上的差别,并不完全等同,所以这个小工具的命名中没有使用invoice,而是fapiao😀

公众号 | FunIO
微信搜一搜 “funio”,发现更多精彩内容。
个人博客 | blog.boringhex.top文章来源地址https://www.toymoban.com/news/detail-745563.html

到了这里,关于又要报销了,还在手动下载整理发票吗?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 还在手动调节温湿度?一招实现远程监控

    还在手动调节温湿度?一招实现远程监控

    无论是在医疗保健、食品加工、物流仓储还是制造业,精确的温湿度控制都是保障产品质量、设备稳定性以及人员健康的重要因素。 无论是在实验室中追求精确数据,还是在农田中寻求最佳生长条件,温湿度监控都在发挥着不可或缺的作用,塑造着我们周围的舒适和安全环境

    2024年02月12日
    浏览(11)
  • 还在手动造轮子?试试这款可以轻松集成多种支付渠道的工具!

    大家好,我是 Java陈序员 。 随着电商的兴起,各种支付也是蓬勃发展。 微信支付、支付宝支付、银联支付等各种支付方式可是深入到日常生活中。可以说,扫码支付给我们的生活带来了极大的便利。 同时,随着市场需求的变化,这也要求我们在企业开发中,需要集成第三方

    2024年02月05日
    浏览(10)
  • Python手动下载第三方库

    第三方库网址(https://www.lfd.uci.edu/~gohlke/pythonlibs/)(https://pypi.org/) 搜索自己想要下载的库 下载自己电脑、版本所对应的whl文件(有时whl文件要与python相对应,例如python3.9,对应下载的文件名字里有cp39) 将下载的whl文件保存到Python目录下的Scripts文件夹里。通常你会希望将它放

    2024年02月06日
    浏览(12)
  • 还在手动复制文章吗?教你如何一键将文章从notion同步到WordPress

    还在手动复制文章吗?教你如何一键将文章从notion同步到WordPress

    本文会给大家介绍如何在WordPress上安装一个插件,实现将notion上写的文章自动同步到WordPress上,从而提高写作效率,接下来请跟随我的脚步一起来操作吧! 在WordPress后台添加新插件页面中搜索“notion”,选择如下图红框勾选的插件“WP Sync for Notion”进行安装。 插件安装成功

    2024年01月20日
    浏览(11)
  • 还在手动封装文件上传?快来试试这款一行代码实现多平台文件存储的工具!

    大家好,我是 Java陈序员 。 文件上传下载,是我们在开发中经常会遇到的需求! 无论是本地存储、还是云存储,我们可以自己封装 API 来实现功能。 今天,给大家介绍一款一行代码实现多平台文件存储的工具,开箱即用! 关注微信公众号:【Java陈序员】,获取 开源项目分

    2024年01月18日
    浏览(14)
  • Python+Selenium webdriver mange用法-告别手动下载driver

    Python+Selenium webdriver mange用法-告别手动下载driver

    通常使用selenium时候我们需要自己单独去下载对应各浏览器版本的webdriver,并在后期针对不同操作系统(mac、windows、linux)或者不同浏览器版本都要反复选择对应版本webdriver 为了解决以上问题,webdriver-manage第三方库营运而生,它可以自动帮你识别当前运行环境下系统信息以及

    2024年02月15日
    浏览(29)
  • 手动下载Python第三方库whl文件并进行安装

    手动下载Python第三方库whl文件并进行安装 在Python开发中,我们经常需要使用第三方库来辅助我们完成各种任务。而通常在安装这些库时,我们会使用pip命令进行安装。但有时候因为网络环境等原因,pip无法正常工作,导致我们无法安装所需第三方库。这时候,手动下载对应的

    2024年02月11日
    浏览(13)
  • 关于selenium, 你还在因为chromedriver的版本与Chrome的版本不一致,需要手动更新chromedriver而烦恼吗?

    关于selenium, 你还在因为chromedriver的版本与Chrome的版本不一致,需要手动更新chromedriver而烦恼吗?

    平时做爬虫我比较喜欢用 selenium chrome ,一直困扰我一个问题,就是只要谷歌浏览器更新了,就要重新去下载对应版本的 chromedriver_win32 ,这让我十分烦恼 比如我的谷歌浏览器已经94版本了,但是 chromedriver_win32 还停留在92版本,就会报出下面的错误 selenium.common.exceptions.SessionN

    2024年02月12日
    浏览(11)
  • 还在为找不到AI生产力工具犯愁?这里整理了最全AI工具导航网站

    还在为找不到AI生产力工具犯愁?这里整理了最全AI工具导航网站

    许多新兴的AI工具和服务正在为人们的生活和工作带来巨大的改变,为了更加方便的寻找AI工具和工具分类索引的需求,也紧跟着出现了一批AI工具导航汇聚各类精选实用的AI工具,为用户提供便捷的分类索引,帮助快速找到适合自己的AI工具。 以下是我经常用到的一些AI工具集

    2024年03月15日
    浏览(13)
  • 调用示例、python语言调用翔云发票查验接口、发票OCR接口

    python语言调用翔云发票查验接口、发票OCR接口其实方法很简单,只需要能看懂开发代码,然后在翔云开发者中,下载所需要的语言开发示例,更换产品参数即可。 发票管理是企业日常工作中不可或缺的一环,但传统的发票查验和识别方式效率低下,给企业带来了很大的负担。

    2024年04月26日
    浏览(9)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包