一、背景介绍
1.1 一些专业名称的解释
MUA——Mail User Agent,邮件用户代理。是用户与电子邮件系统的交互接口,一般来说它就是我们PC机上的一个程序,提供一个好的用户界面,它提取用户在其界面填写的各项信息,生成一封符合SMTP等邮件标准的邮件,然后采用SMTP协议将邮件发送到发送端邮件服务器。常见的通用型代理有OutLook Express、FoxMail、网易闪电邮等
MTA——Mail Transfer Agent,邮件传输代理。负责邮件的传输,它采用端到端的传输的传输方式,源端主机参与邮件传输的全过程,也可称之为SMTP服务器。其实就是那些Email服务提供商,比如网易、新浪等等。比如我使用的电子邮件是163.com
,所以,发Email首先被投递到网易提供的MTA,再由网易的MTA发到对方服务商,可能是sina、qq或者其他的MTA,这个过程中间可能还会经过别的MTA
MDA——Mail Deliver Agent,邮件投递代理。Email到达收件人(比如使用的是新浪的邮箱)的MTA后,新浪的MTA会把Email投递到邮件的最终目的地MDA。Email到达MDA后,就静静地躺在新浪的某个服务器上,存放在某个文件或特殊的数据库里。Email不会直接到达对方的电脑,对方要取到邮件,必须通过MUA从MDA上把邮件取到自己的电脑上。
SMTP——Simple Mail Transfer Protocol,简单邮件传输协议 。是Internet上基于TCP/IP的应用层协议,定义了邮件发送方和接收方之间的连接传输,默认端口号TCP 25。SMTP有其一定的局限性,它只能传送ASCII文本文件,而对于一些二进制数据文件需要进行编码后才能传送。
POP3——Post Office Protocol Version 3,邮局协议的第三版本。主要用于支持使用客户端远程管理在服务器上的电子邮件。,它规定怎样将个人计算机连接到Internet的邮件服务器和下载电子邮件的电子协议。它是因特网电子邮件的第一个离线协议标准,POP3允许用户从服务器上把邮件存储到本地主机(即自己的计算机)上,同时删除保存在邮件服务器上的邮件
IMAP——Internet Message Access Protocol,因特网报文存取协议。它是跟POP3类似邮件访问标准协议之一。不同的是,开启了IMAP后,您在电子邮件客户端收取的邮件仍然保留在服务器上,同时在客户端上的操作都会反馈到服务器上,如:删除邮件,标记已读等,服务器上的邮件也会做相应的动作。所以无论从浏览器登录邮箱或者客户端软件登录邮箱,看到的邮件以及状态都是一致的。
1.2 电子邮件收发原理
有了上述基本概念,要编写程序来发送和接收邮件,本质上就是:
编写MUA把邮件发到MTA;
编写MUA从MDA上收邮件。
发邮件时,MUA和MTA使用的协议就是SMTP,后面的MTA到另一个MTA也是用SMTP协议。
收邮件时,MUA和MDA使用的协议有两种:POP3和IMAP。
邮件客户端软件在发邮件时,会让你先配置SMTP服务器,也就是你要发到哪个MTA上。假设你正在使用163的邮箱,你就不能直接发到新浪的MTA上,因为它只服务新浪的用户,所以,你得填163提供的SMTP服务器地址:smtp.163.com
,为了证明你是163的用户,SMTP服务器还要求你填写邮箱地址和邮箱口令,这样,MUA才能正常地把Email通过SMTP协议发送到MTA。
类似的,从MDA收邮件时,MDA服务器也要求验证你的邮箱口令,确保不会有人冒充你收取你的邮件,所以,Outlook之类的邮件客户端会要求你填写POP3或IMAP服务器地址、邮箱地址和口令,这样,MUA才能顺利地通过POP或IMAP协议从MDA取到邮件。
1.3 编码前的准备工作
使用Python收发邮件前,请先准备好至少两个电子邮件,如[email protected]
,[email protected]
,[email protected]
等,注意两个邮箱不要用同一家邮件服务商。
最后特别注意,目前大多数邮件服务商都需要手动打开SMTP发信和POP收信的功能,否则只允许在网页登录,以163邮箱为例配置步骤如下:
(1)登陆自己的163邮箱,在设置中选择“POP3/SMTP/IMAP”,配置如下图,并记住SMTP服务器地址,后续会用到
(2)找到设置中的“客户端授权码”,开启这项功能并设置授权码,系统会提示不允许与登陆密码相同
二、用python3发邮件
SMTP是发送邮件的协议,Python对SMTP支持有smtplib
和email
两个模块,email
负责构造邮件,smtplib
负责发送邮件
2.1. 最简单的示例,发送纯文本邮件(无附件、正文里无图片和html语句)
需要导入的包如下:(关于第一个包后续展开讲,在这个最简单的例子中使用这个)
from email.mime.text import MIMEText
import smtplib
""" 发送频繁的话会被当做垃圾邮件,运行程序显示554错误 """ from email.mime.text import MIMEText from email.utils import formataddr import smtplib # 输入发送人地址和口令: from_addr = '***@163.com' password = '***'#授权码而非登陆密码 # 输入收件人地址: to_addr = '***@qq.com' # 输入发件人SMTP服务器地址: smtp_server = 'smtp.163.com' #构造邮件 msg = MIMEText('这里写正文的内容……','plain','utf-8') #构造MIMEText对象 msg['From'] = formataddr(["发件人的昵称",from_addr]) #括号里的对应发件人邮箱昵称、发件人邮箱账号 msg['To'] = formataddr(["收件人的昵称",to_addr]) #括号里的对应收件人邮箱昵称、收件人邮箱账号 msg['Subject'] = "这里写主题" #邮件的主题,也可以说是标题 server = smtplib.SMTP(smtp_server, 25) # SMTP协议默认端口是25 #server.set_debuglevel(1)#打印出和SMTP服务器交互的所有信息 server.login(from_addr, password)#登录SMTP服务器 #sendmail()方法就是发邮件,由于可以一次发给多个人,所以传入一个list,邮件正文是一个str,as_string()把MIMEText对象变成str server.sendmail(from_addr, [to_addr], msg.as_string()) server.quit()#退出SMTP服务器
tips1:
from email.utils import formataddr中的formataddr使用help(formataddr)查看
可以看到formataddr和parseaddr是互逆的两个过程
举个例子:
formataddr(['我', '[email protected]'])的结果是:=?utf-8?b?5oiR?=
parseaddr('"我"')的结果是:'我', '[email protected]'
tips2:
password要填写授权码(也就是自己设置的),而不是邮箱登陆密码
tips3:
构造MIMEText
对象时,第一个参数就是邮件正文,第二个参数是MIME的subtype,传入'plain'
表示纯文本,最终的MIME就是'text/plain'
,最后一定要用utf-8
编码保证多语言兼容性
tips4:
SMTP协议就是简单的文本命令和响应。login()
方法用来登录SMTP服务器sendmail()
方法就是发邮件,由于可以一次发给多个人(用“,”隔开),所以传入一个list
邮件正文是一个str
,as_string()
把MIMEText
对象变成str
查看收件箱的结果如下:
为什么没有显示昵称我也不太清楚……只试验了自己的邮箱,并没有进行大范围的测试
2.2 第二个示例——邮件正文中包含html语句(当然也可以包含纯文本文字)
在构造MIMEText
对象时,把HTML字符串传进去,再把第二个参数由plain
变为html
就可以了
msg = MIMEText('Hello
' + 'send by Python...
' + '', 'html', 'utf-8')#发送html
查看邮件结果:
2.3 发送不同格式的附件(当然也可以包含纯文本)
基本思路就是,使用MIMEMultipart来标示这个邮件是多个部分组成的,然后attach各个部分。如果是附件,则add_header加入附件的声明。
在python中,MIME的这些对象的继承关系如下:
官方参考:https://docs.python.org/3/library/email.mime.html
一般来说,不会用到MIMEBase,而是直接使用它的继承类。MIMEMultipart有attach方法,而MIMENonMultipart没有,只能被attach。
MIME有很多种类型,这个略麻烦,如果附件是图片格式,要用MIMEImage,如果是音频,要用MIMEAudio等等……但是可以作为附件的文件类型实在是太多了,记起来又很麻烦,这里有一个懒人方法就是,不管什么类型的附件,都用MIMEApplication,MIMEApplication默认子类型是application/octet-stream。
application/octet-stream表明“这是个二进制的文件,希望你们那边知道怎么处理”,然后客户端,比如qq邮箱,收到这个声明后,会根据文件扩展名来猜测。
from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication from email.mime.image import MIMEImage #from email.mime.image import MIMEAudio #并没有找到MIMEAudio这个库 # 如名字所示Multipart就是分多个部分 # 构造一个MIMEMultipart对象代表邮件本身 msg = MIMEMultipart() # ---这是文字部分--- part1 = MIMEText("纯文本内容") msg.attach(part1) # xlsx类型附件 part2 = MIMEApplication(open('痒痒鼠.xlsx', 'rb').read()) part2.add_header('Content-Disposition', 'attachment', filename="痒痒鼠.xlsx") msg.attach(part2) #图片作为附件的时候,有时会出现图片不完整的情况 # jpg类型附件 part3 = MIMEApplication(open('qqq.jpg', 'rb').read()) part3.add_header('Content-Disposition', 'attachment', filename="qqq.jpg") msg.attach(part3) # jpg类型附件另一种方法 part33 = MIMEImage(open('qqq.jpg', 'rb').read()) part33.add_header('Content-Disposition', 'attachment', filename="qqq.jpg") msg.attach(part33) # pdf类型附件 part4 = MIMEApplication(open('马蜂窝庐山.pdf', 'rb').read()) part4.add_header('Content-Disposition', 'attachment', filename="马蜂窝庐山.pdf") msg.attach(part4) # mp3类型附件 part5 = MIMEApplication(open('天哪.mp3', 'rb').read()) part5.add_header('Content-Disposition', 'attachment', filename="天哪.mp3") msg.attach(part5) # mp3类型附件另一种方法,因为无法导入MIMEAudio这个库,失败
总结一下用法,可直接套用以下模板:
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
msg = MIMEMultipart()
#纯文本内容
part1 = MIMEText("纯文本内容")
msg.attach(part1)
# 添加附件
part2 = MIMEApplication(open('附件***.类型', 'rb').read())
part2.add_header('Content-Disposition', 'attachment', filename="附件***.类型")
msg.attach(part2)
查看邮件结果:
tips1:可以添加多个附件
tips2:附件为图片的时候有可能不完整
2.4 加密传输
使用标准的25端口连接SMTP服务器时,使用的是明文传输,发送邮件的整个过程可能会被窃听。要更安全地发送邮件,可以加密SMTP会话,实际上就是先创建SSL安全连接,然后再使用SMTP协议发送邮件。
某些邮件服务商,例如Gmail,提供的SMTP服务必须要加密传输。我们来看看如何通过Gmail提供的安全SMTP发送邮件。
必须知道,Gmail的SMTP端口是587,因此,修改代码如下:
smtp_server = 'smtp.gmail.com' smtp_port = 587 server = smtplib.SMTP(smtp_server, smtp_port) server.starttls() # 剩下的代码和前面的一模一样: server.set_debuglevel(1)
只需要在创建SMTP
对象后,立刻调用starttls()
方法,就创建了安全连接。后面的代码和前面的发送邮件代码完全一样。
三、用python3收邮件
讲完了发送邮件,现在讲解收取邮件
收取邮件就是编写一个MUA作为客户端,从MDA把邮件获取到用户的电脑或者手机上。收取邮件最常用的协议是POP协议,目前版本号是3,俗称POP3。
Python内置一个poplib
模块,实现了POP3协议,可以直接用来收邮件。
注意到POP3协议收取的不是一个已经可以阅读的邮件本身,而是邮件的原始文本,这和SMTP协议很像,SMTP发送的也是经过编码后的一大段文本。
要把POP3收取的文本变成可以阅读的邮件,还需要用email
模块提供的各种类来解析原始文本,变成可阅读的邮件对象。
所以,收取邮件分两步:
第一步:用poplib
把邮件的原始文本下载到本地;
第二部:用email
解析原始文本,还原为邮件对象。