python核心编程笔记——因特网客户端编程(三)

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邮箱中的邮件来看只显示了链接而没有显示文本。)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章