Python实现批量下载邮箱中京东商城的发票

以QQ邮箱为例
可以设置获取指定时间内的邮件中的发票
可以设置获取指定地点内的邮件中的发票

import poplib
from  urllib import request
from email import policy
from email.parser import BytesParser
from email.header import decode_header, make_header
from email.headerregistry import UniqueDateHeader
import re,os
from xml.dom.minidom import parse


# 定义EmailClient类,用于连接到POP3服务器并从指定的邮件地址获取邮件
class JDInvoiceDownload:
    # 在初始化函数中,设置POP3服务器的来源、用户、密码和待查询的目标邮件地址
    def __init__(self, host,pop_server_port, user, password, target_email,file_path,target_addr=None,target_time=None):
        """
        初始化EmailClient类的新实例。
        参数:
        - host: 邮件服务器的主机名或IP地址。
        - pop_server_port: POP3服务器的端口号。
        - user: 用户名,用于登录邮件服务器。
        - password: 用户密码,用于登录邮件服务器。
        - target_email: 目标电子邮件地址。
        - file_path: 附件的文件路径。
        """
        self.POP3_SSL(host,pop_server_port)
        self.login(user, password)
        self.target_email = target_email
        self.file_path = file_path
        self.target_addr = target_addr
        self.target_time = target_time
    def POP3_SSL(self,pop_server_host,pop_server_port):
        try:
            # 连接pop服务器。如果没有使用SSL,将POP3_SSL()改成POP3()即可其他都不需要做改动
            self.pop_server = poplib.POP3_SSL(host=pop_server_host, port=pop_server_port, timeout=10)
            print("pop3----连接服务器成功,现在将检查用户名")
        except:
            print("pop3----对不起,给定的电子邮件服务器地址连接超时")
            exit(1)

    def login(self,user,password):
        try:
            # 验证邮箱是否存在
            self.pop_server.user(user)
            print("pop3----用户名已存在,现在将检查密码")
        except:
            print("pop3----对不起,给定的电子邮件地址似乎不存在")
            exit(1)
        try:
            # 验证邮箱密码是否正确
            self.pop_server.pass_(password)
            print("pop3----密码正确,现在将列出电子邮件")
        except:
            print("pop3----对不起,给定的用户名似乎不正确")
            exit(1)

    # 定义一个函数,用以清除文件名中的无效字符
    def sanitize_folder_name(self, name):
        invalid_characters = "<>:\"/\\|?*@"
        for char in invalid_characters:  # 遍历所有无效字符
            name = name.replace(char, "_")  # 将无效字符替换为下划线
        return name  # 返回清理后的名称

    # 定义一个函数,用以提取邮件的payload(有效载荷,即邮件主体内容)
    def get_payload(self, email_message):
        if email_message.is_multipart():  # 判断邮件是否为多部分邮件
            for part in email_message.iter_parts():  # 如果是,则遍历其中的每一部分
                content_type = part.get_content_type()  # 获取该部分的内容类型
                if content_type == 'text/html':  # 如果内容类型为HTML,则返回该部分内容
                    return part.get_content()
                elif content_type == 'text/plain':  # 如果内容类型为纯文本,则返回该部分内容
                    return part.get_content()
        elif email_message.get_content_type() == 'text/html':  # 如果邮件非多部分形式,且为HTML类型,则返回邮件内容
            return email_message.get_content()
        elif email_message.get_content_type() == 'text/plain':  # 如果邮件非多部分形式,且为纯文本类型,则返回邮件内容
            return email_message.get_content()

    def fetch_email(self):
        # 邮箱中其收到的邮件的数量
        email_count = len(self.pop_server.list()[1])
        print("email_num: ",email_count)
        # list()返回所有邮件的编号:
        _, mails, _ = self.pop_server.list()

        # for i in range(len(mails),len(mails)-1,-1):
        for i in range(len(mails),1, -1):
            # 通过retr(index)读取第index封邮件的内容;这里读取最后一封,也即最新收到的那一封邮件
            resp, lines, octets = self.pop_server.retr(i)
            email_content = b'\r\n'.join(lines)  # 将所有行连接成一个bytes对象
            # 解析邮件内容
            email_parser = BytesParser(policy=policy.default)  # 创建一个邮件解析器
            email = email_parser.parsebytes(email_content)  # 解析邮件内容,返回一个邮件对象
            email_time =  self.get_email_time(email)
            print("email_time: ",email_time)
            if email_time == self.target_time and  self.check_email_from(email):  # 如果发件人地址与指定的目标邮件地址一致,对邮件进行处理
                email_body = self.get_payload(email)  # 获取邮件正文

                if self.check_invoice_target_addr(email_body):
                    self.download_invoice(email_body,f"{i}.pdf",email_time)
            print("--------------------------------------------------------------------------------------")

    def check_email_from(self, email) -> bool:
        """
        对比发件人地址与指定的目标邮件地址是否一致
        :param email:
        :return:
        """
        # 解析邮件头部信息并提取发件人信息
        email_from = email.get('From').strip()  # 获取发件人信息,并去除尾部的空格
        email_from = str(make_header(decode_header(email_from)))  # 解码发件人信息,并将其转换为字符串
        print("发件人地址:", email_from," ",email_from == self.target_email)
        return  email_from == self.target_email


    def get_email_time(self,email):
        """
        解析邮件时间
        :param email:
        :return:
        """
        # 解析邮件时间
        email_time: UniqueDateHeader = email.get('Date')  # 获取邮件时间
        # 获取年月日
        return   email_time.datetime.strftime("%Y-%m-%d")

    def download_invoice(self, body, filename,email_time):
        """
        下载发票
        :param body:
        :param filename:
        :param email_time:
        :return:
        """
        pattern = r'href="([^"]*)">发票PDF文件下载</a>'
        match = re.search(pattern, body)
        if match:
            # 提取URL
            url = match.group(1)
            print("找到的-PDF-URL是:", "".join(url.split("amp;")))
            try:
                file_path = f"{self.file_path}\\{email_time}"
                # 判断文件夹是否存在
                if not os.path.exists(file_path):
                    os.makedirs(file_path)
                request.urlretrieve("".join(url.split("amp;")), f"{file_path}\\{filename}")
            except Exception as e:
                print("Error downloading:", e)


    def check_invoice_target_addr(self, body) -> bool:
        """读取XML文件,判断是否是某个地方的发票"""
        pattern = r'href="([^"]*)">发票XML文件下载</a> '
        match = re.search(pattern, body)
        if match:
            # 提取URL
            url = match.group(1)
            print("找到的-XML-URL是:", "".join(url.split("amp;")))
        try:
            file_name = f"{self.file_path}\\xml_file_test.xml"
            request.urlretrieve("".join(url.split("amp;")),file_name)
            dom = parse(file_name)
            # 获取文档元素对象
            data = dom.documentElement
            seller_addr = data.getElementsByTagName("EInvoiceData")[0].getElementsByTagName("SellerInformation")[
                0].getElementsByTagName("SellerAddr")[0].childNodes[0].nodeValue
            print("seller_addr:",seller_addr," "+self.target_addr in  seller_addr)
            if not self.target_addr in  seller_addr:
                return  False

        except Exception as e:
            print("Error parsing XML:", e)

        return  True

    def close(self):
        # 关闭连接
        self.pop_server.close()

if __name__ == '__main__':
    client = JDInvoiceDownload(
        host='pop.qq.com', # 邮箱服务器地址(qq邮箱无需改)
        pop_server_port=995, # 端口号(qq邮箱无需改)
        user=input("请输入你的邮箱地址:"), # 邮箱账号
        password=input("请输入你的授权码:"), # 授权码
        target_email='"京东JD.com" <customer_service@jd.com>', # 发件人
        file_path="D:\\Test", # 保存文件路径
        target_addr="广州", # 发票地址(不需要判断设置为None即可)
        target_time="2024-11-10" # 发票时间 (不需要判断设置为None即可)
    )
    client.fetch_email()
    client.close()


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CY耶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值