文章目录
ps:python版本为3.6.2
电子邮件
. 根据RFC2822的定义,“(电子邮件)消息由头字段(统称消息标题)以及后面可选的正文组成”。即邮件可以没有正文,但是一定要有标题。
电子邮件系统组件和协议
. 实际上电子邮件诞生于因特网之前,电子邮件最早是使用在同一台主机上的不同用户之间的通信,网络出现后,用户就可以在不同的主机之间进行通信。
电子邮件是如何工作呢?简单来说,有一台发送计算机(发件人的消息从这里发送出去)和一台接收计算机(收件人的邮件服务器)。最好的解决方案是发送计算机知道如何连接到接收计算机,这样就可以直接把消息发送过去。但实际上一般没有这么顺利。
发送计算机需要找到某一台中间主机,而这台中间主机最终能到达最后的接收主机。接着这台中间主机需要找到一台离接收主机更近一些的主机。所以,在发送主机和接收主机之间,可能会有多台称为跳板的主机。如果仔细看看收到的电子邮件消息头标题,会看到一个“护照”标记,其中记录了这封邮件最终抵达之前,一路上都到过哪些地方。
电子邮件系统最重要的组件是消息传输代理(MTA),这是在邮件交换主机上运行的服务器进程,它负责邮件的路由、队列处理和发送工作。MTA 就是邮件从发送主机到接收主机所要经过的主机和“跳板”,所以也称为“消息传输”的“代理”。
要让所有这些工作起来,MTA 要知道两件事情:1)如何找到消息应该到达的下一台MTA;2)如何与另一台 MTA 通信。第一件事有域名服务(DNS)来查找目的域名的MX(MaileXchange,邮件交换)来完成,查找到的可能不是最终主机,而是一个跳板。
发送电子邮件
. 为了发送电子邮件,邮件客户端必须要连接到一个 MTA,MTA 靠某种协议进行通信。MTA之间通过消息传输系统(MTS)互相通信。只有两个 MTA 都使用这个协议时,才能进行通信。由于以前存在很多不同的计算机系统,每个系统都使用不同的网络软件,因此这种通信很危险,具有不可预知性。更复杂的是,有的计算机使用互连的网络,而有的计算机使用调制解调器拨号,消息的发送时间也是不可预知的。这种复杂性导致了现代电子邮件的基础之一 —— 简单邮件传输协议(Simple Mail Transfer Protocol,SMTP)的诞生。
SMTP 是在因特网上的 MTA 之间消息交换的最常用 MTSMTA。用 SMTP 把电子邮件从一台(MTA)主机传送到另一台(MTA)主机。 发电子邮件时,必须要连接到一个外部 SMTP服务器,此时邮件程序是一个 SMTP 客户端。而 SMTP 服务器也因此成为消息的第一站。
Python和SMTP
. Python也有一个smtplib模块和一个需要实例化的smtplib.SMTP类,其通信过程如下:
1.连接到服务器;
2.登录(根据需要);
3.发出服务请求;
4.退出。
像NNTP一样,登录是可选的,只有在服务器启用了SMTP身份验证(SMTP-AUTH)时才需要登录,其通信时也只要一个端口号:25.
smtplib.SMTP类方法
. 下表列举一些常见的SMTP对象的方法:
方法 | 描述 |
---|---|
sendmail(from, to, msg[, mopts, ropts]) | 将 msg 从 from 发送至 to(以列表或元组表示),还可以选择性地设置 ESMTP 邮件(mopts)和收件人(ropts)选项 |
ehlo()或 helo() | 使用 EHLO 或 HELO 初始化 SMTP 或 ESMTP 服务器的会话。这是可选的,因为sendmail()会在需要时自动调用相关内容 |
starttls(keyfile=None, certfile=None) | 让服务器启用 TLS (安全传输层协议)模式。如果给定了 keyfile 或 certfile,则它们用来创建安全套接字 |
set_debuglevel(level) | 为服务器通信设置调试级别 |
quit() | 关闭连接并退出 |
login(user, passwd) | 使用用户名和密码登录 SMTP 服务器 |
. 对大多数电子邮件发送程序来说,只需要两个方法:sendmail()和 quit()。sendmail()的所有参数都要遵循 RFC 2822,即电子邮件地址必须要有正确的格式,消息正文要有正确的前导标题,正文必须由回车和换行符(\r\n)对分隔。
交互式SMTP示例
. 介绍一个交互式示例,使用QQ邮箱的STMP服务器发送邮件:
>>> s = smtplib.SMTP('smtp.qq.com')
>>> s.login('[email protected]','xxxxxxxxxx') #这里输入的不是密码,而是授权码
(235, b'Authentication successful')
>>> s.sendmail('[email protected]','[email protected]','\r\nxxxxxx\r\n')
{}
接受电子邮件
. 对于家庭用户来说,在家里放一个工作站来运行SMTP是不现实的,因此需要一种新的系统,能够周期性的把邮件下载到本地,以供离线使用。
这种家用的应用程序叫做邮件用户代理(Mail User Agent,MUA)。MUA从服务器上下载邮件,在这个过程中可能会自动删除邮件。不过MUA也需要能发送邮件,也就是说:在发送邮件的时候,应用程序能够直接使用SMTP和MUA进行通信。
POP和IMAP
. 第一个用于下载邮件的协议是邮局协议(Post Office Protocal,POP),“邮局协议(POP)的目的是让用户的工作站可以访问邮箱服务器里的邮件,并在工作站中通过简单邮件传输协议(SMTP)将邮件发送到邮件服务器”。
在 POP 出现几年之后有了一个与之竞争的协议,即因特网消息访问协议(Internet Message Access Protocol,IMAP)。IMAP 旨在提供比 POP 更完整的解决方案,但它也因此比 POP 更复杂。当前广泛使用的版本是 IMAP4rev1。实际上,Microsoft Exchange 这个当今最主要的邮件服务器使用 IMAP 作为下载方式。
Python和POP3
. 与之前一样,这里导入 poplib 并实例化 poplib.POP3 类。标准流程如下所示:
1.连接到服务器;
2.登录;
3.发出服务请求;
4.退出。
其伪代码如下:
from poplib import POP3
p = POP3('pop.python.is.cool')
p.user(...)
p.pass_(...)
...
p.quit()
交互式POP3示例
. 下面是使用 Python 的 poplib 模块的交互式示例:
>>> import poplib
>>> p = poplib.POP3_SSL('pop.qq.com')
>>> p.user('[email protected]')
b'+OK'
>>> p.pass_('xxxxxxxxxxx') #也是授权码,不是密码
b'+OK'
>>> p.stat()
(14, 180452)
>>> rsp,msg,siz = p.retr(14)
>>> rsp,siz
(b'+OK', 853)
poplib.POP3类方法
. 下表是常用的一些POP3对象的方法
方法 | 描述 |
---|---|
user(loginname) | 向服务器发送登录名,并显示服务器的响应,表示服务器正在等待输入该用户的密码 |
pass_(passwd) | 在用户使用 user()登录后,发送 passwd。如果登录失败,则抛出异常 |
stat() | 返回邮件的状态,即一个长度为 2 的元组(msg_ct, mbox_siz),分别表示消息的数量和消息的总大小(即字节数) |
list([msgnum]) | stat()的扩展,从服务器返回以三元组表示的整个消息列表(rsp, msg_list, rsp_siz),分别为服务器的响应、消息列表、返回消息的大小。如果给定了 msgnum,则只返回指定消息的数据 |
retr(msgnum) | 从服务器中得到消息的 msgnum,并设置其“已读”标志。返回一个长度为 3 的元组(rsp, msglines, msgsiz),分别为服务器的响应、消息的 msgnum 的所有行、消息的字节数 |
dele(msgnum) | 把消息 msgnum 标记为删除,大多数服务器在调用 quit()后执行删除操作 |
quit() | 注销、提交修改(如处理“已读”和“删除”标记等)、解锁邮箱、终止连接,然后退出 |
客户端程序SMTP和POP3示例
. 以下程序用SMTP和POP3创建一个既能下载也能上传和发送电子邮件的客户端:
from smtplib import SMTP
from poplib import POP3_SSL
from time import sleep
Smtpsvr = 'smtp.qq.com'
Popsvr = 'pop.qq.com'
who = '[email protected]'
body = '''\
From: %(who)s
To: %(to)s
Subject: test msg
Hello World!
''' % {'who': who}
sendSvr = SMTP(Smtpsvr)
sendSvr.login('[email protected]','xxxxxxxxxxxx')
errs = sendSvr.sendmail(who,[who],body)
sendSvr.quit()
assert len(errs) == 0, errs
sleep(10)
recvSvr = POP3_SSL(Popsvr)
recvSvr.user('[email protected]')
recvSvr.pass_('xxxxxxxxxxxx')
rsp,msg,siz = recvSvr.retr(recvSvr.stat()[0])
for eachLine in msg:
print(eachLine)
Python和IMAP4
. Python通过imaplib模块支持IMAP4,流程与之前介绍的一样,下面是伪代码:
from imaplib import IMAP4
s = IMAP4('imap.python.is.cool')
s.login(...)
...
s.close()
s.logout()
imaplib.IMAP4 类中的常用方法
. IMAP 协议比 POP 复杂,下表只列出了一些基本方法:
方法 | 描述 |
---|---|
close() | 关闭当前邮箱。如果访问权限不是只读,则本地删除的邮件在服务器端也会被丢弃 |
fetch(message_set, message_parts) | 获取之前由message_set设置的电子邮件消息状态(或使用message_parts获取部分状态信息) |
login(user, password) | 使用指定的用户名和密码登录 |
logout() | 从服务器注销 |
noop() | ping 服务器,但不产生任何行为 |
search(charset, *criteria) | 查询邮箱中至少匹配一块 criteria 的消息。如果 charset 为 False,则默认使用 US-ASCII |
select(mailbox= 'INBOX ', read-only=False) | 选择一个文件夹(默认是 INBOX),如果是只读,则不允许用户修改其中的内容 |
交互式 IMAP4 示例
>>> from imaplib import IMAP4
>>> s = IMAP4('imap.qq.com')
>>> s.login('[email protected]','xxxxxxxxx') #还是授权码
('OK', [b'Success login ok'])
>>> rsp,msgs = s.select('INBOX',True)
>>> rsp
'OK'
>>> msgs
[b'169']
>>> rsp,data = s.fetch(msgs[0],'(RFC822)')
>>> rsp
'OK'
>>> for line in data[0][1].splitlines()[:5]:
print(line)
b'X-QQ-mid: sysmail1t1547604076thzpz0bpw'
b'X-QQ-ACTION: no-bounce'
b'X-QQ-STYLE: 1'
b'X-QQ-INNER-PENDING: 1'
b'X-QQ-INNER-SUBJECT: =?GBK?B?zajTw8n6yNXM4dDR08q8/g==?='
>>> s.close()
('OK', [b'CLOSE Done'])
>>> s.logout()
('BYE', [b'LOGOUT received'])
实战
生成电子邮件
. 电子邮件消息可能不仅包含了纯文本,还有附件、文本中的格式等。邮件互换消息扩展(MIME)格式就用来识别这些不同的部分。
Python的email包很适合处理并管理整个电子邮件消息的 MIME 部分,下面是两个创建电子邮件的示例,即 make_mpa_msg()和 make_img_msg(),前者创建的电子邮件包含一条链接,后者创建的电子邮件包含一张图片:
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from smtplib import SMTP
def make_mpa_msg():
email = MIMEMultipart('alternative')
text = MIMEText('Hello World!','plain','utf-8')
email.attach(text)
html = MIMEText(
'<p><a href="http://www.baidu.com">这是一个链接!</a></p>','html','utf-8')
email.attach(html)
return email
def make_img_msg(fn):
f = open(fn,'rb')
data = f.read()
f.close()
email = MIMEImage(data,name = fn)
email.add_header('Content-Disposition',
'attachment;filename = "%s"' % fn)
return email
def sendMsg(fr,to,msg):
s = SMTP('smtp.qq.com')
s.login('[email protected]','xxxxxxxx')
errs = s.sendmail(fr,to,msg)
s.quit()
if __name__ == '__main__':
SENDER = 'xxxxxxx'
RECIPS = 'xxxxxxx'
img_name = 'x:/xx/xx'
print('send multipart alternative msg...')
msg = make_mpa_msg()
msg['From'] = SENDER
msg['To'] = ','.join(RECIPS)
msg['Subject'] = 'multipart alternative test'
sendMsg(SENDER,RECIPS,msg.as_string())
print('sending image msg...')
msg = make_img_msg(img_name)
msg['From'] = SENDER
msg['To'] = ','.join(RECIPS)
msg['Subject'] = 'image file test'
sendMsg(SENDER,RECIPS,msg.as_string())
. 创建多部分类型的消息需要使用到 email.mime.multiple.MIMEMultipart 类,并传递alternative作为唯一的参数来实例化这个类。
纯文本和链接都会用到 email.mime.text.MIMEText 类。这两部分是在邮件创建之后才创建的。
创建带图片的消息需要使用到email.mime.image.MIMEImage 实例。
(疑问:在IDLE中查看msg类容的时候能看到是多部分的,但从QQ邮箱中的邮件来看只显示了链接而没有显示文本。)