【python】使用parseaddr和formataddr将多个收件人/抄送人都展示为美化后的状态

在学习过程中遇到了一些问题,把笔记记录下来希望能对跟我一样的小白有所帮助

简单介绍smtp、parseaddr、formataddr
smtp

smtp是发送邮件的协议,python内置了对smtp协议的支持。

基本的邮件发送示例:

from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import smtplib
from_addr = '[email protected]'  # 准备使用的发件人邮箱账号
password = 'password'  # 从发件人邮箱获取的授权码
to_addr = '[email protected]'  # 收件人的邮箱地址
cc_addr = '[email protected]'  # 抄送人的邮箱地址
# SMTP服务器地址,这里使用的是网易
smtp_server = 'smtp.163.com'    # 需要哪个自己百度(注意企业邮箱服务器地址是不同的)
# 编写邮件正文
msg = MIMEMultipart()
text_part = MIMEText('use smtp with python', 'plain', 'utf-8')
msg.attach(text_part)
# 构建发送服务
server = smtplib.SMTP(smtp_server, 25) # SMTP协议默认端口是25
server.set_debuglevel(1)
server.login(from_addr, password)  # 登录发件人邮箱,password是授权码!
# 收件人和抄送人在sendmail方法里都是list类型,中间用+号连接
server.sendmail(from_addr, [to_addr]+[cc_addr], msg.as_string())  # 收件人是一个list类型
server.quit()

这样一个简单的邮件就构建好了,但是查看发出去的邮件,会发现:
1、邮件没有主题;
2、收发件人的格式是错误的
3、明明小明收到了邮件,但是收件人里却没有展示小明

必须把From、To和Subject添加到msg中,才是一封完整的邮件
如果需要添加抄送人,则把Cc添加进去

在构建server之前,加上:

from email.header import Header
msg['From'] = from_addr 
msg['To'] = to_addr
msg['Cc'] = cc_addr
# 使用Header对标题进行编码
msg['Subject'] = Header('这里写邮件的主题', 'utf-8').encode()

msg[‘To’]和msg[‘Cc’]这里如果需要添加多个收件人/抄送人,直接在定义收件人、抄送人的句子中,将多个邮箱写在一个字符串中,用逗号隔开即可:

to_addr1 = '[email protected],[email protected]'
# 必须写在同一个字符串中!

然后再使用server发送邮件,就会发现:邮件有了发件人、收件人、抄送人的邮箱地址和邮件主题。

但是仔细观察会发现,这样发送的邮件会和直接使用网易邮箱发送的有一点区别之处:
收发件人包括抄送人只有邮箱地址,没有使用人的名称,如:[email protected]
而比较专业的显示方式是:小明

想要美化收发件人格式,要用到两个方法:
parseaddr() 和formataddr()

from email.utils import parseaddr, formataddr
# 将字符串打包为一个元组
name,addr =  parseaddr('小明>)
# 再把元组解析为一个转码后的字符串
setted_from_addr = formataddr((Header(name, 'utf-8').encode(), addr))
# print(setted_from_addr)

同理,收件人、抄送人的也做如此处理,为了方便,将上面的两个方法写成一个小工具

def format_addr(s):  
	name, addr = parseaddr(s)
    return formataddr((Header(name, 'utf-8').encode(), addr))

然后,设置msg的各项配置:

def set_email(from_name, to_name, sub_str, cc_name=None):
	msg['From'] = format_addr('%s<%s>' % (from_name, from_addr)) 
    msg['To'] = format_addr('%s<%s>' % (to_name, to_addr))  
    msg['Cc'] = format_addr('%s<%s>' % (cc_name, cc_addr))  
    msg['Subject'] = Header('%s' % sub_str, 'utf-8').encode()

看到这可能会有疑问:
为什么format_addr()方法里面一个字符串却要引用两个变量?
为什么不直接在from_addr、to_addr等的定义式中,直接写成
'小明'这种格式呢?

这是因为,在后面的server.sendmail()语句中,收件人和发件人的list中,存的必须是’[email protected]’这种格式,所以定义句中姓名和邮箱最好不要写到一起,引用两个变量是有必要的。

发件人只有一个,直接传入姓名和邮箱就可以了,但是怎么才能将多个收件人、多个抄送人都美化为姓名<邮箱>的格式呢?
前面说过,msg[‘To’]和msg[‘Cc’]接受的是一个字符串。当只简单格式化,不展示收件人姓名,只展示邮箱时,把多个收件人的邮箱,都放在一个字符串里,以逗号隔开即可。

这说明这个字符串会以逗号为分隔符被解析!联想一下,是不是把多个经过format_addr()方法转码后的字符串用逗号拼接在一起就可以了呢?

由于多个邮箱地址要定义成一个字符串,那么怎样才能把多个姓名和多个邮箱地址对应上,并拼接成一个转码后的字符串,并把多个转码后的字符串拼接成一整个字符串,传到msg[‘To’]中去呢?

我的解决办法是,把姓名也以逗号连接作为一个字符串,将两个字符串进行处理后拼接到一起:

def set_consignee(name_str, email_str):  # 这里的两个参数是name和addr的完整字符串
	name_list = name_str.split(',')
    email_list = email_str.split(',')
    format_list = []
    if len(name_list) == len(email_list):
        for i in range(len(name_list)):
            format_list.append(format_addr('%s<%s>' % (name_list[i], email_list[i])))
    else:
        raise Exception('姓名和邮箱数量不一致!')
    return format_list

上面代码的含义:如果姓名的个数和邮箱的个数相同,就认为是要格式化多个收件人。依次将姓名和对应的邮箱组合并转码,然后存入一个列表format_list。只要通过’,’.join(format_list),得到的就是一个由多个转码字符串和逗号组成的一个字符串。
包装一下,整体代码如下:

import smtplib
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr


class WorkReport:
    def __init__(self, from_addr, password, to_addr, smtp_server, cc_addr):
        self._from_addr = from_addr
        self._password = password
        self._to_addr = to_addr
        self._smtp_server = smtp_server
        self._cc_addr = cc_addr
        # 实例化邮件整体
        self.msg = MIMEMultipart()

    @staticmethod
    def format_addr(s):  # 格式化名称<邮箱>
        name, addr = parseaddr(s)
        return formataddr((Header(name, 'utf-8').encode(), addr))
        
    @staticmethod
    def get_date():
    	pass
        
    def set_consignee(self, name_str, email_str):  # 对收件人、抄送人名称+邮箱显示的美化处理
        name_list = name_str.split(',')
        email_list = email_str.split(',')
        format_list = []
        if len(name_list) == len(email_list):
            for i in range(len(name_list)):
                format_list.append(self.format_addr('%s<%s>' % (name_list[i], email_list[i])))
        else:
            raise Exception('姓名和邮箱数量不一致!')
        return format_list

    def set_email(self, from_name, to_name, sub_str, cc_name=None):  # 邮件格式设置

        # 设置邮件收发件人、抄送人、标题
        self.msg['From'] = self.format_addr('%s<%s>' % (from_name, self._from_addr))  # 美化发件人
        self.msg['To'] = ','.join(self.set_consignee(to_name, self._to_addr))  # 调用美化收件人方法
        self.msg['Cc'] = ','.join(self.set_consignee(cc_name, self._cc_addr))  #
        self.msg['Subject'] = Header('%s' % sub_str, 'utf-8').encode()
    def add_annex(self):  # 添加附件
        pass

    def send_email(self):
        # 写邮件正文
        text_part = MIMEText('邮件正文内容,可以写一个方法读取文件,比如我写这个其实是为了每天发日报用=。=', 'plain', 'utf-8')
        # 将写好的正文部分添加到邮件中
        self.msg.attach(text_part)
        # 发送邮件
        server = smtplib.SMTP(self._smtp_server, 25)
        server.set_debuglevel(1)
        server.login(self._from_addr, self._password)
        server.sendmail(self._from_addr, [self._to_addr] + [self._cc_addr], self.msg.as_string())
        server.quit()


if __name__ == '__main__':
	# 实例化,参数要依次对应
    tyf_report = WorkReport('[email protected]', '这里是授权码',
                            '[email protected],[email protected]', 'smtp.163.com',
                            cc_addr='[email protected],[email protected]')
    tyf_report.set_email('发件人名字', '收件人1,收件人2', 'xxx日工作日报','抄送人1,抄送人2')
    tyf_report.send_email()

运行效果:
在这里插入图片描述
【python】使用parseaddr和formataddr将多个收件人/抄送人都展示为美化后的状态_第1张图片
其实也省不了多大的事,但是直接用邮箱客户端我咳嗽!来根华子!

你可能感兴趣的:(python,smtp)