在学习过程中遇到了一些问题,把笔记记录下来希望能对跟我一样的小白有所帮助
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()