电子邮件介绍
电子邮件的运作流程基本可以参考传统信封邮寄的过程。
假设我的电子邮件地址是ABC@qq.com
,对方的电子邮件地址是DEF@163.com
。然后我们通过Foxmail
等软件写好信息后点击发送,电子邮件就发送出去了。注意:这些电子邮件被称为MUA
即Mail User Agent -- 邮件用户代理
。
邮件发送出去后并不是直接到达对方电脑,而是发到MTA
即Mail Transfer Agent -- 邮件传输代理
,也就是那些邮件服务提供商,如腾讯、网易等。由于我的电子邮件是@qq.com
,属于腾讯,所以邮件就先发送到腾讯提供的MTA
,再由腾讯的MTA
发送给对方的即网易的MTA
,当然可能中间会有其他MTA
,我们不关心具体线路,只关心速度。
邮件到达网易的MTA
后,网易的MTA
会把邮件投递到最终目的地MDA
即Mail Delivery Agent -- 邮件投递代理
。邮件到达MDA
后,就静静地躺在网易的某个服务器上,存放在某个文件或特殊的数据库里,我们将这个长期保存邮件的地方称为电子邮箱。
由于对方不一定开机,不一定联网。对方要取到邮件,必须通过MUA
从MDA
上把邮件取到自己电脑上。
所以一封电子邮件的旅程是
发件人 -> MUA -> MTA -> MTA -> 若干个MTA -> MDA <- MUA <- 收件人
所以编写程序发送和收取邮件的本质就是
- 编写
MUA
将邮件发送到MTA
- 编写
MUA
从MDA
上获取邮件。
发邮件时,MUA
和MTA
之间、MTA
和MTA
之间使用的协议是SMTP -- Simple Mail Transfer Protocol
。
收邮件时,MUA
和MDA
使用的协议有两种,POP -- Post Office Protocol
,目前版本是3,俗称POP3
;IMAP -- Internet Message Access Protocol
,目前版本是4,优点是不但能取邮件,还可以直接操作MDA上存储的邮件,比如从收件箱移到垃圾箱,等等。
邮件客户端软件在发送邮件时,需要配置SMTP服务器,也就是要发送到哪个服务器的MTA
上,如我的电子邮件地址是@qq.com
,需要配置腾讯的SMTP服务器,同时为了证明你是腾讯的用户,还需要填上邮箱地址和邮箱口令,这样才能将邮件通过MUA
发送到MTA
上。
同理,邮件客户端在收取邮件时,需要配置POP3或IMAP服务器,以及提供邮箱地址和邮箱口令来验证身份。
SMTP发送邮件
SMTP
是发送邮件的协议,Python已经内置了对SMTP
协议的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。
Python的内置模块email
负责构建邮件,smtplib
负责发送邮件。
纯文本邮件
我们以从一个QQ邮箱发送到另一个QQ邮箱(换成163邮箱也是可以的)为例。QQ邮箱的SMTP服务器地址是smtp.qq.com
,默认是开启了SSL
,故端口是465
。
用MIME
构造邮件,用SMTP_SSL
构造连接SMTP服务的客户端。
# -*- coding:UTF-8 -*-
import re
from smtplib import SMTP_SSL
from email.mime.text import MIMEText
# 构造邮件 第一个参数是内容 第二个参数是邮件内容类型plain表示纯文本 第三个参数是编码
m = MIMEText("hello, send by cute boy in python.", _subtype='plain', _charset="utf-8")
from_addr = input("请输入邮箱地址: ")
passwd = input("请输入邮箱口令: ")
smpt_server = input("请输入SMPT服务器地址: ")
smpt_port = int(input("请输入SMPT服务器端口: ").strip())
to_addr = input("请输入收件人地址(多个用逗号或空格隔开): ")
addr_list = re.split("[\s,]+", to_addr)
# 开启SSL加密的SMTP
server = SMTP_SSL(smpt_server, smpt_port)
# 打印出和SMTP服务器交互的所有信息
server.set_debuglevel(1)
# 用邮件地址和授权码登录
server.login(from_addr, passwd)
# 发送邮件 可以发送多人所以收件地址是个list
# 邮件正文是str 所以需要使用as_string()方法将MIME变成str
server.sendmail(from_addr, addr_list, m.as_string())
server.quit()
效果图如下所示
收到的邮件如下所示。
我们发现收到的邮件没有主题信息,收件人名字没有显示,也没有友好地显示发件人地址。其原因就在于,邮件主题、如何显示收/发件人信息并不是通过SMTP协议发给MTA,而是包含在发给MTA的文本中的。我们需要将From
、To
、Subject
信息放在MIMEText
里,这样才是一封完整的邮件。
使用自定义函数myformataddr
来格式化From
、To
、Subject
信息。
m['To']
接收的是字符串而不是list,如果有多个邮件地址,用,
分隔即可。
完整代码如下,其实只是添加了格式化函数和From
、To
、Subject
信息。
# -*- coding:UTF-8 -*-
import re
from smtplib import SMTP_SSL
from email.mime.text import MIMEText
from email.header import Header
from email.utils import parseaddr, formataddr
# 格式化邮件地址 如果有中文就需要用Header进行编码
def myformataddr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, charset="utf-8").encode(), addr))
# 构造邮件 第一个参数是内容 第二个参数是邮件内容类型plain表示纯文本 第三个参数是编码
m = MIMEText("hello, send by cute boy in python.", _subtype='plain', _charset="utf-8")
from_addr = input("请输入邮箱地址: ")
passwd = input("请输入邮箱口令: ")
smpt_server = input("请输入SMPT服务器地址: ")
smpt_port = int(input("请输入SMPT服务器端口: ").strip())
to_addr = input("请输入收件人地址(多个用逗号或空格隔开): ")
addr_list = re.split("[\s,]+", to_addr)
m['From'] = myformataddr("Gentleman <{}>".format(from_addr))
# 多个收件人用,分隔
to_str=",".join((myformataddr("beautiful girl-{} <{}>".format(i+1, addr)) for i,addr in enumerate(addr_list)))
m['To'] = to_str
# 如果有中文 需要用Header进行格式化
m['Subject'] = Header("to beautiful girl", charset="utf-8").encode()
# 开启SSL加密的SMTP
server = SMTP_SSL(smpt_server, smpt_port)
# 打印出和SMTP服务器交互的所有信息
server.set_debuglevel(1)
# 用邮件地址和授权码登录
server.login(from_addr, passwd)
# 发送邮件 可以发送多人所以收件地址是个list
# 邮件正文是str 所以需要使用as_string()方法将MIME变成str
server.sendmail(from_addr, addr_list, m.as_string())
server.quit()
HTML邮件
发送HTML
邮件很简单,只需要把发送内容编程html内容,内容类型变成html
即可。如下所示。
其他不变,只是需要修改MIMEText
即可。
content = """\
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>文档标题</title>
</head>
<body>
<h1>我的第一个HTML页面</h1>
<p><a href="https://www.liaoxuefeng.com/wiki/1016959663602400/1017790702398272">Python教程</a></p>
</body>
</html>
"""
m = MIMEText(content, _subtype='html', _charset="utf-8")
带附件的邮件
譬如想发送一个带附件的邮件,邮件类型是HTML
,然后HTML
内容是可以引用附件的文件的如图片。
我们可以构造一个MIMEMultipart
对象,然后往该对象添加一个MIMEText
作为邮件正文,再继续往里面添加表示附件的MIMEBase
对象。
类之间的关系图如下
我们可以用MIMEMultipart
对象去attach
附件,MIMENonMultipart
只能被attach
附件类型是图片时可以用子类MIMEImage
,是audio
类型时可以用MIMEAudio
,当不清楚类型是什么的时候可以用MIMEApplication
,MIMEApplication
默认子类型是·application/octet-stream`,表明这是个二进制文件,让收件箱服务器根据文件扩展名猜测如何处理。
Message
+- MIMEBase
+- MIMEMultipart
+- MIMENonMultipart
+- MIMEMessage
+- MIMEText
+- MIMEImage
+- MIMEApplication
+- MIMEAudio
# -*- coding:UTF-8 -*-
import re
from smtplib import SMTP_SSL
from email import encoders
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.mime.base import MIMEBase
from email.header import Header
from email.utils import parseaddr, formataddr
# 格式化邮件地址 如果有中文就需要用Header进行编码
def myformataddr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, charset="utf-8").encode(), addr))
def attach_pic(m, fpath, fname, ftype, fid):
with open(fpath, "rb") as f:
# 设置附件的MIME和文件名,这里是jpg类型 也可用MIMEImage
mime = MIMEBase("image", ftype, filename=fname)
# 加上必要的头信息
mime.add_header('Content-Disposition', 'attachment', filename=fname)
mime.add_header('Content-ID', '<{}>'.format(fid))
mime.add_header('X-Attachment-Id', fid)
# 读取附件内容
mime.set_payload(f.read())
# 用Base64编码
encoders.encode_base64(mime)
m.attach(mime)
def attach_normalfile(m, fpath, fname, fid):
with open(fpath, "rb") as f:
# Create an application/* type MIME document
mime = MIMEApplication(f.read())
# 加上必要的头信息
mime.add_header('Content-Disposition', 'attachment', filename=fname)
mime.add_header('Content-ID', '<{}>'.format(fid))
mime.add_header('X-Attachment-Id', fid)
m.attach(mime)
def create_mime_obj():
content = """\
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>文档标题</title>
</head>
<body>
<h1>我的第一个HTML页面</h1>
<p><a href="https://www.liaoxuefeng.com/wiki/1016959663602400/1017790702398272">Python教程</a></p>
<p><img src="cid:0"></p>
</body>
</html>
"""
m = MIMEMultipart()
# 添加文本信息 注意这里我添加的是html文本
m.attach(MIMEText(content, _subtype='html', _charset="utf-8"))
# 添加图片
attach_pic(m, "d:/imgs/th.jpg", "th.jpg", "jpg", "0")
attach_pic(m, "d:/imgs/IMG_7743.JPG", "IMG_7743.JPG", "jpg", "1")
# 添加普通文件
attach_normalfile(m, "d:/imgs/python简明教程中文.pdf", "python简明教程中文.pdf", "2")
attach_normalfile(m, "d:/imgs/Hive优化.sql", "Hive优化.sql", "3")
attach_normalfile(m, "d:/imgs/群星 - 心痛2010.mp3", "群星 - 心痛2010.mp3", "4")
return m
m = create_mime_obj()
from_addr = input("请输入邮箱地址: ")
passwd = input("请输入邮箱口令: ")
smpt_server = input("请输入SMPT服务器地址: ")
smpt_port = int(input("请输入SMPT服务器端口: ").strip())
to_addr = input("请输入收件人地址(多个用逗号或空格隔开): ")
addr_list = re.split("[\s,]+", to_addr)
m['From'] = myformataddr("Gentleman <{}>".format(from_addr))
# 多个收件人用,分隔
to_str=",".join((myformataddr("beautiful girl-{} <{}>".format(i+1, addr)) for i,addr in enumerate(addr_list)))
m['To'] = to_str
# 如果有中文 需要用Header进行格式化
m['Subject'] = Header("to beautiful girl", charset="utf-8").encode()
# 开启SSL加密的SMTP
server = SMTP_SSL(smpt_server, smpt_port)
# 打印出和SMTP服务器交互的所有信息
server.set_debuglevel(1)
# 用邮件地址和授权码登录
server.login(from_addr, passwd)
# 发送邮件 可以发送多人所以收件地址是个list
# 邮件正文是str 所以需要使用as_string()方法将MIME变成str
server.sendmail(from_addr, addr_list, m.as_string())
server.quit()
POP3收取邮件
Python内置一个poplib
模块,实现了POP3协议,可以直接用来收邮件。
由于用python收取邮件不常用,此处就不在这里贴实践代码了,更多细节可以参考廖雪峰老师的Python教程中关于POP3收取邮件这一部分