文章目录
- 一、前言
-
- 1、邮件构成
- 二、email模块
-
- 1、email模块的Message类
-
- Message类常用方法
- 2、email.mine模块:构建电子邮件信息
-
- MIMEBase类实现
- 3、email.parser模块:解析电子邮件信息
- 4、email.header模块:丰富、解析邮件头
- 5、email.utils模块:其他工具
- 6、email.iterators模块:迭代器
- 7、完整示例
-
- 构建邮件内容
- 解析邮件内容
- 二、smtplib模块
-
- 1、导入smtplib模块
- 2、建立链接、登录
- 3、发送邮件
- 4、退出服务器
- 5、完整示例
- 三、imaplib模块
-
- 1、导入imaplib模块
- 2、建立链接,登录
- 3、选择电子收件箱的邮件
- 4、搜索电子邮件
- 5、获取每封邮件的内容
- 6、退出IMAP服务器
- 7、完整示例
- 四、实际应用
-
- 实例一:发送邮件:smtplib模块+email模块
-
- 示例1:群发邮件
- 示例2:发送带多个附件的邮件
- 示例3:发送带HTML图片的邮件
- 发送邮件的代码封装
- 实例二:收取邮件:imaplib模块+email模块
-
- 示例1:邮件内容(邮件头和邮件)的解码及附件下载
- 收取邮件的代码封装
- 五、遇到报错及解决方案
一、前言
定时发送邮件、群发邮件在工作中比较常见的操作。比如,营销人员节假日时需要给客户群发祝福邮件,或者数据分析人员,工作中常常被要求将数据处理成客户份需要的格式并以邮件发送到对方邮箱。所以,本文主要讲利用python构造与解析邮件内容(email模块)、发送邮件(smtplib模块)、接收邮件(imaplib模块)三个基本模块的使用方法及一些常见的问题与实例应用举例。
1、邮件构成
学习如何用email模块构建邮件内容和解析邮件内容前,先了解一封邮件包括的信息,一封邮件通常会分为邮件头和邮件体。
- 邮件头
邮件头存着这封邮件的基本信息,包括有标题、发信人地址、收信人地址、邮件体内容类型和邮件编码方式,每条信息称为一个域,基本说格式是:{域名}:{信息}。
域名 | 含义 |
---|---|
Received | 传输路径 |
Return-Path | 回复地址 |
Delivered-To | 发送地址 |
Reply-To | 回复地址 |
From | 发件人地址 |
To | 收件人地址 |
Cc | 抄送地址 |
- 邮件体
邮件体为邮件的具体内容,具体内容的类型格式(Content-Type),为maintype/subtype的全小写。
maintype常见的类型有text、multipart,下面是常用的邮件体的Content-Type:
邮件体类型 | 含义 |
---|---|
text/html | 邮件为超文本正文 |
text/plain | 邮件为纯文本正文 |
multipart/alternative | 邮件包括纯文本正文(text/plain)和超文本正文(text/html) |
multipart/related | 邮件正文中包括图片,声音等内嵌资源 |
multipart/mixed | 邮件包含附件。向上兼容,如果一个邮件有纯文本正文、超文本正文、内嵌资源、附件、则选择mixed,也是默认类型 |
二、email模块
1、email模块的Message类
- Message类是email的核心类,它是email对象模型中基类,提供了设置和查询邮件头部,访问消息体的核心方法。
Message基类与MIME类的继承关系如下图所示:
Message类常用方法
- as_string()
以展平的字符串形式返回来个消息对象。
示例:用于将邮件内容转换为字符串格式,便于smtplib.sendmail模块发送邮件参数格式。
# smtplib 用于邮件的发信动作
import smtplib
# email 用于构建邮件内容
from email.mime.text import MIMEText
from email.header import Header
# 邮件正文内容
text='''人生如幽径 覆盖以凋零
勿心怀恐惧 勿转身离去
一夜复一夜 一日复一日
细尝发间风 路从眼前生
'''
msg=MIMEText(text,'plain','utf-8')
msg # msg是一个email.message.Message对象。
# 输出:
# email.mime.text.MIMEText at 0x14bb79d1cd0>
msg.as_string()
# 输出:
# 'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: base64\n\n5Lq655Sf5aaC5bm95b6EIOimhuebluS7peWHi+mbtgrli7/lv4PmgIDmgZDmg6cg5Yu/6L2s6Lqr\n56a75Y67CuS4gOWknOWkjeS4gOWknCDkuIDml6XlpI3kuIDml6UK57uG5bCd5Y+R6Ze06aOOIOi3\nr+S7juecvOWJjeeUnwo=\n'
- as_bytes()
以字节串对象的形式返回整个扁平化后的消息。
示例:用于将邮件内容转换为字节串格式,便于smtplib.sendmail模块发送邮件参数格式。
# smtplib 用于邮件的发信动作
import smtplib
# email 用于构建邮件内容
from email.mime.text import MIMEText
from email.header import Header
# 邮件正文内容
text='''人生如幽径 覆盖以凋零
勿心怀恐惧 勿转身离去
一夜复一夜 一日复一日
细尝发间风 路从眼前生
'''
msg=MIMEText(text,'plain','utf-8')
msg # msg是一个email.message.Message对象。
# 输出:
# email.mime.text.MIMEText at 0x14bb79d1cd0>
msg.as_bytes()
# 输出:
# b'Content-Type: text/html; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: base64\nFrom: zxt@cndatacom.com\nTo: zxt@cndatacom.com\nSubject: python smtp\n\nPHA+UHl0aG9uIOmCruS7tuWPkemAgea1i+ivlS4uLjwvcD4KPHA+PGEgaHJlZj0iaHR0cDovL3d3\ndy56aGlodS5jb20iPui/meaYr+S4gOS4qumTvuaOpTwvYT48L3A+Cg==\n'
- is_multipart()
如果该消息的载荷是一个子Message对象列表则返回True,当is_multipart()返回False时,载荷应当是一个字符串对象。
示例:获取邮件的,用于判断邮件体类型maintype是否是multipart。不同邮件体类型,邮件体的解析方式不一样。
# msg是一个email.message.Message对象。
if msg.is_multipart():
continue
- attach(payload)
将给定的payload添加到当前载荷中,当前载荷在该调用之前必须为None或是一个Message对象列表,在调用之后,此载荷将总是一个Message对象列表,如果你想将此载荷设为一个标量对象(如字符串),请改用set_payload()。
示例:将要以邮件附件形式发送的test.txt文件的数据信息添加到当前Message对象列表中。
msg = MIMEMultipart() #创建一个带附件的邮件实例
# 通过MIMEText构造附件1,传送当前目录下的test.txt文件
att1=MIMEText(open('test.txt','rb').read(),'base64','utf-8')
att1['Content-Type']='application/octet-stream'
att1['Content-Disposition']='attachment;filename="test.txt"' #这里的filename可以任意写,写什么名字,邮件中显示什么名字。
msg.attach(att1)
- get_payload(i=None,decode=False)
返回当前的载荷,它在is_multipart()为True时将是一个Message对象列表,在is_multipart()为False时则是一个字符串。如果该载荷是一个列表且你修改了这个列表对象,那么就是原地修改了消息的载荷。
示例:解析邮件正文内容。
for part in msg.walk(): # msg是一个email.message.Message对象。
if part.is_multipart()='False':
part.get_playload(decode=True)
- get(name,failobj=None)
返回指定名称标头字段的值。如果指定名称标头未找到则会返回可选的 failobj (默认为 None)。
示例:获取收件的邮件头的信息。
for header in ['From','To','Subject']:
value=msg.get(header,'') # msg是一个email.message.Message对象。
注意: 如果指定名称的字段在消息标头多次出现,具体返回哪个字段值是未定义的。请使用 get_all(name, failobj=None) 方法来获取所有指定名称标头的值。get_all()返回是包含字符串的列表,get()返回是字符串。
示例:如果收件人是多个地址,需要用get_all()将所有地址信息提取出来
recipient=msg.get_all('To','') # msg是一个email.message.Message对象。
- add_header(_name,_value,**_param)
高级头字段设定。可以使用关键字参数为字段提供附加参数。 _name 是字段名, _value 是字段主值。
示例:
# msg是一个email.message.Message对象。
msg.add_header('Content-Disposition', 'attachment', filename='bud.gif') # 等价于att1['Content-Disposition']='attachment,filename=“bud.gif”'
# 会添加一个形如下面的头字段:
# Content-Disposition: attachment; filename="bud.gif"
带有非ASCII字符的拓展接口:
# msg是一个email.message.Message对象。
msg.add_header('Content-Disposition', 'attachment',filename=('iso-8859-1', '', 'Fußballer.ppt'))
- get_param(param,failobj=None,header=‘content-type’,unquote=True)
将content-Type标头的形参param作为字符串返回。如果消息没有Content-Type标头或者没有这样的形参,则返回failobj(默认为None)。
示例:在解析邮件体的时候,判断邮件是否带有附件。
for part in msg.walk(): # msg是一个email.message.Message对象。
filename=part.get_param("name")
if filename:
print("接收的邮件有附件。")
else:
print("接收的邮件是纯文本邮件。")
- get_filename(failobj=None)
返回信息头当中Content-Disposition字段中名为filename的参数值,如果该字段当中没有此,该方法会退而寻找 Content-Type 字段当中的 name 参数值。如果这个也没有找到,或者这些个字段压根就不存在,返回 failobj 。
示例:在解析邮件体时,判断邮件是否带有附件。
for part in msg.walk(): # msg是一个email.message.Message对象。
filename=part.get_filename()
if filename:
print("接收的邮件有附件。")
else:
print("接收的邮件是纯文本邮件。")
- get_content_type()
返回消息的内容类型。返回的字符串会被强制转换为 maintype/subtype 的全小写形式,如果消息中没有 Content-Type 标头,则将返回get_default_type()给出的默认类型,绝大多数的信息,其默认内容类型都是 text/plain 。
- get_content_maintype()
返回信息的主要内容类型。即返回的是 get_content_type() 方法所返回的列如 maintype/subtype 的字符串当中的maintype部分。
- get_content_charset(failobj=None):获取内容的type编码方式
返回 Content-Type 头字段中的 charset 参数,强制小写。如果字段当中没有此参数,或者这个字段压根不存在,返回 failobj 。
- get_content_disposition()
如果信息的 Content-Disposition 头字段存在,返回其字段值;否则返回 None。
示例:返回邮件体的Content-Type。
# smtplib 用于邮件的发信动作
import smtplib
# email 用于构建邮件内容
from email.mime.text import MIMEText
from email.header import Header
# 邮件正文内容
text='''<p>Python 邮件发送测试...</p>
<p><a href="http://www.zhihu.com">这是一个链接</a></p>
'''
msg=MIMEText(text,'plain','utf-8')
msg.get_content_type()
# 输出:
# 'text/html'
msg.get_content_maintype()
# 输出:
# 'text'
msg.get_content_charset()
# 输出:
# 'utf-8'
msg.get_content_disposition() #没有Content-Disposition就返回None。
- walk()
一个多功能生成器。它可以被用来以深度优先顺序遍历信息对象树的所有部分和子部分。一般而言,walk()会被用作for循环的迭代器,每一次迭代都返回其下一个子部分。
示例:
可以打印一封具有多部分结构之信息的每个部分的MIME类型。
for part in msg.walk(): # msg是一个email.message.Message对象。
print(part.get_content_type())
# 输出:
# multipart/report
# text/plain
# message/delivery-status
# text/plain
# text/plain
# message/rfc822
# text/plain
walk()会遍历所有is_multipart() 方法返回True任何部分的子部分。
for part in msg.walk(): # msg是一个email.message.Message对象。
print(part.get_content_maintype() == 'multipart',part.is_multipart())
# 输出:
# True True
# False False
# False True
# False False
# False False
# False True
# False False
# 在这里,message 的部分并非 multiparts,但是它们有确实包含子部分!is_multipart() 返回 True,walk 也深入进这些子部分中。
from email.iterators import _structure
_structure(msg)
# 输出:
# multipart/report
# text/plain
# message/delivery-status
# text/plain
# text/plain
# message/rfc822
# text/plain
2、email.mine模块:构建电子邮件信息
-
MIMEBase作为MIME相关对象基类,继承Message,拥有Message操作邮件head和body的所有函数。MIME在邮件头部增加了Content-Type和MIME-Version两个头部信息。
-
多部分邮件MIMEMultipart类,表示用MIMEMultipart()方法来表示这个邮件由多个部分组成,然后再通过attach()方法将各部分内容分别加入到MIMEMultipart容器中。
-
非多部分邮件MIMEMultipart类是其他具体类(MIMEApplication,MIMEText,MIMEImage等)的基类,也就是说它们不运行用户使用attach()方法,它们是通过set_payload()方法来实现设置邮件payload功能,只能被attach。
-
MIMEBase基类与MIME各对象的继承关系如下:
MIMEBase类实现
1、要写入邮件内容,首先要导入email模块,因为email是一个包,这个包的init.py文件为空,也就是我们仅仅导入包的话(import email),就什么都做不了,所以需要使用from…import…语句。
- 邮件内容是纯文本、HTML页面。
from email.mime.text import MIMEText
- 邮件的附件形式为图片。
from email.mime.image import MIMEImage
- 邮件的附件形式为音频。
from email.mime.audio import MIMEAudio
- 邮件的附件形式为其他类型的格式,比如Pdf,doc,xlsx,csv等类型。
from email.mime.application import MIMEApplication
- 邮件形式为多形式组合,可包含文本和附件。
from email.mime.multipart import MIMEMultipart
2、构建邮件内容
- 构建纯文本的邮件内容,使用MIMEText方法。
MIMEText(_text,_subtype,_chartset)
参数解析:
text:文本内容,自定义。
subtype:文本类型,默认为 “plain”(纯文本),如果是一个链接,设置为 “html” 。
chartset:文本编码,中文为"utf-8"
示例1:正文是纯文本
text='''你好,
人生如幽径 覆盖以凋零
勿心怀恐惧 勿转身离去
一夜复一夜 一日复一日
细尝发间风 路从眼前生
'''
msg=MIMEText(text,'plain','utf-8')
示例2:正文是html格式的网页链接
text='''<p>Python 邮件发送测试...</p>
<p><a href="http://www.zhihu.com">这是一个链接</a></p>
'''
msg=MIMEText(text,'html','utf-8')
示例3:正文是html格式的图片,参考文章:html代码里面src引用电脑本地的图片
text='''<p>Python 邮件发送测试...</p>
<img src="C:\\Users\\pic1.jpg"></img>
'''
msg=MIMEText(text,'html','utf-8')
- 构建附件为图片的邮件内容,使用MIMEImage方法。如果正文内容是图片,只能使用html格式放图片,用MIMEText方法。
MIMEImage(_imagedata, _subtype=None)
参数解析:
imagedata:一个包含原始图片信息的字符串。
示例1:图片以附件形式显示。
msg=MIMEImage(open('F:\\python_test\\pic1.jpg','rb').read())
示例2:图片以HTML形式在正文显示。
msg=MIMEMultipart()
append_imgs = [r".\pic1.jpg"]
append_img = r".\pic1.jpg"
img_tag=f"<p><img src='cid:image{
append_imgs.index(append_img)}'></img></p>"
#读取图片信息
att=MIMEImage(open(append_img,'rb').read())
#定义图片ID,在HTML文本中引用
att.add_header('Content-ID',f'<image{
append_imgs.index(append_img)}>')
msg.attach(att)
msg.attach(MIMEText(img_tag,'html','utf-8'))
html语言可以带上图片链接(<img src=r".\pic1.jpg"></img>),那么我们在发送邮件的时候就需要对这些链接的图片做特殊处理。否则在对方接收到邮件的时候会看不到图片。我们特殊处理的方法就是把它们当成附件(直接使用MIMEImage)发送。
但如果想要图片不显示在附件里,直接显示在正文(使用MIMEImage+MIMEText)。首先需要对输入的content进行解析,找到所带图片的cid。然后把content中<img src=r".\pic1.jpg"></img>这段代码变成<img src=” cid:IMG”></img>
- 构建附件为音频的邮件内容,使用MIMEAudio方法。
MIMEAudio(_audiodata, _subtype=None)
参数解析:
audiodata:一个包含原始音频数据的字符串
示例:
msg=MIMEAudio(open("F:\\python_test\\audio1.mp3",'rb').read())
- 构建附件是其他类型的格式的文件,比如pdf,doc,xlsx,csv等类型,使用MIMEApplication方法,根据文件后的扩展名进行处理。
MIMEApplication(_data, _subtype=‘octet-stream’)
参数解析:
data:一个包含原始应用程序数据的字符串。
subtype:默认为’octet-stream’,表明“这是个二进制,不知道文件的下载类型”,客户端收到这个声明后,根据文件后的扩展名进行处理。
示例:
msg=MIMEApplication(open('F:\\python_test\\test.csv','rb').read())
- 构建包含文本和附件的邮件内容,使用MIMEMultipart方法。
MIMEMultipart(_subtype=‘mixed’)
参数解析:
常见的multipart类型有三种:multipart/alternative, multipart/related和multipart/mixed。
subtype:
- “alternative”:邮件包括纯文本正文(text/plain)和超文本正文(text/html);
- “related”:邮件正文中包括图片,声音等内嵌资源。;
- “mixed”:邮件包含附件。向上兼容,如果一个邮件有纯文本正文、超文本正文、内嵌资源、附件、则选择mixed,也是默认类型。
示例:添加各种类型的附件
msg = MIMEMultipart() #创建一个带附件的邮件实例
# 通过MIMEText构造附件1,传送当前目录下的test.txt文件
att1=MIMEText(open('test.txt','rb').read(),'base64','utf-8')
att1['Content-Type']='application/octet-stream'
att1['Content-Disposition']='attachment;filename="test.txt"' #这里的filename可以任意写,写什么名字,邮件中显示什么名字。
msg.attach(att1)
# 通过MIMEImage构造附件2,传送当前目录下面的pic1.jpg文件
att2=MIMEImage(open('pic1.jpg','rb').read())
att2['Content-Type']='application/octet-stream'
att2['Content-Disposition']='attachment,filename="pic1.jpg"'
msg.attach(att2)
# 通过MIMEAudio构造附件3,传送当前目录下面的audio1.mp3文件
att3=MIMEAudio(open('audio1.mp3','rb').read(),'audio')
att3['Content-Type']='application/octet-stream'
att3['Content-Disposition']='attachment,filename="audio1.mp3"'
msg.attach(att3)
# 通过MIMEApplication构造附件4,传送当前目录下面的test.pdf文件
att4=MIMEApplication(open('test.pdf','rb').read())
att4['Content-Type']='application/octet-stream'
att4['Content-Disposition']='attachment,filename="test.pdf"'
msg.attach(att4)
上传附件注意点:
- 理解Content-Type、Content-Disposition、Content-ID
Content-Type:(内容类型)定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件。可以参考:Content-Type介绍。
Content-Disposition:附件存在方式,有两种属性:inline和attachment。
- inline:默认值,嵌入文字里的,将文件内容直接显示在页面。比如HTML格式邮件中显示的图片。
- attachment:当做附件处理。
- filename:如果将Content-Disposition设置为attachment,还可以定义下载文件的文件名。
- 另一种附件重命名的方式: 将上面示例中"通过MIMEApplication构造附件4"中的代码<att4[‘Content-Disposition’]=‘attachment,filename=“test.pdf”’>换成下面写法:
att4.add_header('Content-Disposition','attachment',filename="test.pdf")
Content-ID:头字段用于“multipart/related”组合消息中的内嵌资源指定一个唯一标识号,在HTML格式的正文中可以使用这个唯一标识号来引用该内嵌资源。
示例:设将一个表示内嵌图片的MIME消息的Content-ID头字段设置为如下形式:
#定义图片ID,在HTML文本中引用
append_imgs=[r'.\pic1.jpg']
append_img=r'.\pic1.jpg'
msg.add_header('Content-ID',f'<image{
append_imgs.index(append_img)}>')
# 在HTML正文中就需要使用如下HTML语句来引用该图片资源。
# append_imgs=[r'.\pic1.jpg']
# append_img=r'.\pic1.jpg'
# img_tag=f"<p><img src='cid:image{append_imgs.index(append_img)}'></p>"
3、email.parser模块:解析电子邮件信息
- Parser(fp,headersonly=False)
从文本模式的文件对象fp读取所有数据,解析读取的文本,并返回根消息的对象。fp必须同时支持文件类对象上的readline()和read()方法。
from email.parser import Parser #用POP3协议收取邮件(poplib模块)时,常用到email模块中解析邮件的方式。
注意:从一个字符串或文件对象中创建一个消息对象是非常常见,更方便的函数如下:
- email.message_from_string()
从一个字符串中返回消息对象。
- email.message_from_bytes()
从一个bytes-like object中返回消息对象。
示例:
import email
for email_id in email_ids:
status,email_data=conn.fetch(email_id,"(RFC822)") #以RFC822格式获取邮件格式
raw_email = email_data[0][1]
msg=email.message_from_string(raw_email) #再用email.message_from_string转换为message对象,就可以当做message操作了。
msg
# 输出:
# <email.message.Message at 0x2c42ca2a430>
import email
for email_id in email_ids:
status,email_data=conn.fetch(email_id,"(RFC822)") #以RFC822格式获取邮件格式
raw_email = email_data[0][1]
msg=email.message_from_bytes(raw_email) #再用email.message_from_bytes转换为message对象,就可以当做message操作了。
msg
# 输出:
# <email.message.Message at 0x2c42ca2a430>
4、email.header模块:丰富、解析邮件头
- 使用email.header模块中的Header方法,将Message对象中的字段赋值为Header的实例,而不是使用字符串作为字段值。
from email.message import Message
from email.header import Header
msg=Message()
msg[‘From’]=Header(‘xxx’)
msg[‘To’]=Header(‘xxx’)
msg[‘Subject’]=Header(‘xxx’)
示例1:丰富邮件头,包括主题、发件人、收件人等信息。可以是from收件人邮箱地址,to发件人地址,主题是"python_test"。
from email.header import Header
msg['From']=Header(from_addr)
msg['To']=Header(to_addr)
msg[