邮件是我们日常工作中主要的沟通媒介之一。目前几乎所有编程语言都支持发送和接收电子邮件。 电子邮件发送协议主要是 SMTP,收件协议主要是 POP3 和 IMAP。
SMTP 的全称是“Simple Mail Transfer Protocol”,即简单邮件传输协议。它是一组用于从源地址到目的地址传输邮件的规范,通过它来控制邮件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。SMTP 服务器就是遵循 SMTP 协议的发送邮件服务器。
POP3 是Post Office Protocol 3的简称,即邮局协议的第 3 个版本,它规定怎样将个人计算机连接到 Internet 的邮件服务器和下载电子邮件的电子协议。它是因特网电子邮件的第一个离线协议标准,POP3 允许用户从服务器上把邮件存储到本地主机(即自己的计算机)上,同时删除保存在邮件服务器上的邮件,而 POP3 服务器则是遵循 POP3 协议的接收邮件服务器,用来接收电子邮件的。
在我们开始编写邮件操作的相关代码之前,先了解一下电子邮件在互联网上是如何运作的。电子邮件发送和接收的过程如下图所示:
根据上图分析,我们发现当两台不同的服务器进行消息通讯时,是 SMTP 进行握手,A 服务器发送邮件到 B 服务器,B 服务器的 SMTP 会替本地用户接受外面发过来的邮件,然后 POP3/IMAP 去读取 SMTP 收取的邮件。
SMTP 是邮局,POP3 是快递员,快递员只会去邮局领取邮件,而不是直接去发件人手上直接拿,两个地区(邮箱服务器)邮件收发也是通过邮局(SMTP)来完成
虽然 POP3 是用来接收邮件的协议,但不要理解成没有 SMTP 一样能收取邮件,邮件服务器是通过 SMTP 来控制邮件的中转,两个邮件服务器之间传输工作和 POP3 一点关系都没有。
下面我们举个具体栗子来说明邮件发送和接收的整个过程,这里先给出用户 A 从 QQ 邮箱发送邮件到用户 B 的 163 邮箱的图示,然后对图示的过程进行详细的介绍:
图示的六个步骤分别进行如下的说明:
邮件服务器
图示出现了两个邮件服务器,QQ 和 163 邮件服务器。用户想要在网上收发邮件,必须要有专门的邮件服务器。邮件服务器我们可以假想为现实生活中的邮局。
如果按功能划分,邮件服务器可以划分为两种类型:
邮件客户端
我们可以直接在网站上进行邮件收发,也可以用邮件客户端软件。比如常见的 FoxMail,Outlook Express。邮件客户端软件通常集邮件撰写,发送和收发功能于一体,主要用于帮助用户将邮件发送给 SMTP 邮件服务器和从 POP3/IMAP 邮件服务器读取用户的电子邮件。
电子邮箱
电子邮箱也称为 E-mail 地址,比如用户 A 的 xx@qq.com,和用户 B 的 xx@163.com。用户能通过 E-mail 地址标识自己发送的电子邮件,同时也可以通过这个地址接收别人发来的电子邮件。电子邮箱需要到邮件服务器进行申请,也就是说,电子邮箱其实就是用户在邮件服务器上申请的账户。邮件服务器会把接收到的邮件保存到为该账户所分配的邮箱空间中,用户通过用户名密码登录到邮件服务器查收该地址已经收到的邮件。一般来讲,邮件服务器为用户分配的邮箱空间是有限的。
邮件传输协议
电子邮件需要在邮件客户端和邮件服务器之间,以及两个邮件服务器之间进行邮件传递,那就必须要遵守一定的规则,这个规则就是邮件传输协议。下面我们分别简单介绍几种协议(后面会详细讲解):
我们说所有的邮件服务器和邮件客户端软件程序都是基于上面的协议编写的,接下来将介绍如何使用 Python 语言发送和接收邮件。
在使用程序发邮件之前,我们需要打开自己邮箱的 SMTP 功能,各家邮箱的设置方法就不一一讲述了,具体使用时可以百度一下,下面以 126 邮箱设置为例做一个简单的演示:
开通 126 的 SMTP 发送邮件协议后,我们就可以使用该服务器发送邮件了,下面,我们用 126 的 SMTP 邮箱服务器向 qq 邮箱发送邮件。
import smtplib from email.mime.text import MIMEText ''' 发送邮件之前,我们首先设置 126 邮箱服务器所需信息 ''' # 126 邮箱服务器地址 mail_host = 'smtp.126.com' # 126 用户名 mail_user = 'fxxxxxx3' # 密码(注意:该密码不是你 126 邮箱的登录密码,而是你申请 SMTP 服务时,126 邮箱给你的授权码) mail_pass = 'ExxxxxxP' # 邮件发送方邮箱地址 sender = 'fxxxxxx3@126.com' # 邮件接受方邮箱地址,注意需要[]包裹,这意味着你可以写多个邮件地址群发 receivers = ['3xxxxxx3@qq.com'] ''' 设置要发送的 email 信息 ''' # 创建邮件对象,并设置邮件正文内容 message = MIMEText('老鸟python你好,我要跟你学习python。', 'plain', 'utf-8') # 邮件主题 message['Subject'] = 'title' # 发送方信息 message['From'] = sender # 接受方信息 message['To'] = receivers[0] #登录并发送邮件 try: smtpObj = smtplib.SMTP() # 连接到服务器 smtpObj.connect(mail_host, 25) # 登录到服务器 smtpObj.login(mail_user, mail_pass) # 发送 smtpObj.sendmail(sender, receivers, message.as_string()) # 退出 smtpObj.quit() print('发送成功') except smtplib.SMTPException as e: print('发送失败', e) # 打印错误信息
执行上面代码,我们就可以把邮件发送到了 QQ 邮箱里面,打开 QQ 就可以看到收到的内容,如下图。
如果我们要发送的是 HTML 邮件,而不是普通的纯文本文件怎么办呢?方法很简单,在构造 MIMEText 对象时把 HTML 字符串传进去,再把第二个参数由 plain 变为 html 就可以了。代码实现如下:
import smtplib from email.mime.text import MIMEText ''' 发送邮件之前,我们首先设置 126 邮箱服务器所需信息 ''' # 126 邮箱服务器地址 mail_host = 'smtp.126.com' # 你登录的 126 邮箱用户名 mail_user = 'fxxxxxx3' # 密码(注意:该密码不是你 126 邮箱的登录密码,而是你申请 SMTP 服务时,126 邮箱给你的授权码) mail_pass = 'ExxxxxxP' # 你登录的 126 邮箱账号 sender = 'fxxxxxx3@126.com' # 邮件接受方邮箱地址,注意需要[]包裹,这意味着你可以写多个邮件地址群发 receivers = ['3xxxxxx3@qq.com'] ''' 设置要发送的 email 信息 ''' mail_msg = """ <p>老鸟python你好,我要跟你学习python。</p> <p><a href="https://www.birdpython.com">这是老鸟python的官网。</a></p> """ # 创建邮件对象,并设置邮件正文内容 message = MIMEText(mail_msg, 'html', 'utf-8') # 邮件主题 message['Subject'] = 'title' # 发送方信息 message['From'] = sender # 接受方信息 message['To'] = receivers[0] #登录并发送邮件 try: smtpObj = smtplib.SMTP() # 连接到服务器 smtpObj.connect(mail_host, 25) # 登录到服务器 smtpObj.login(mail_user, mail_pass) # 发送 smtpObj.sendmail(sender, receivers, message.as_string()) # 退出 smtpObj.quit() print('发送成功') except smtplib.SMTPException as e: print('发送失败', e) # 打印错误信息
执行以上代码后,打开 QQ 邮箱,可以看到收到的结果,如下图。
如果要把一个图片嵌入到邮件正文中怎么做?直接在HTML邮件中链接图片地址行不行?答案是,大部分邮件服务商都会自动屏蔽带有外链的图片,因为不知道这些链接是否指向恶意网站。
要把图片嵌入到邮件正文中,我们只需按照发送附件的方式,先把邮件作为附件添加进去,然后,在 HTML 中通过引用 src="cid:image1" 就可以把附件作为图片嵌入了。如果有多个图片,给它们依次编号,然后引用不同的 cid:x 即可。
把上面代码加入 MIMEMultipart 的 MIMEText 从 plain 改为 html,然后在适当的位置引用图片:
import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.mime.image import MIMEImage ''' 发送邮件之前,我们首先设置 126 邮箱服务器所需信息 ''' # 126 邮箱服务器地址 mail_host = 'smtp.126.com' # 126 用户名 mail_user = 'fxxxxxx3' # 密码(注意:该密码不是你 126 邮箱的登录密码,而是你申请 SMTP 服务时,126 邮箱给你的授权码) mail_pass = 'ExxxxxxP' # 邮件发送方邮箱地址 sender = 'fxxxxxx3@126.com' # 邮件接受方邮箱地址,注意需要[]包裹,这意味着你可以写多个邮件地址群发 receivers = ['3xxxxxx3@qq.com'] ''' 设置要发送的 email 信息 ''' # 创建邮件对象 message = MIMEMultipart() mail_msg = """ <p>老鸟python的图片演示:</p> <p>第 1 张图片:</p> <p><img src="cid:image1"></p> <p>第 2 张图片:</p> <p><img src="cid:image2"></p> """ message.attach(MIMEText(mail_msg, 'html', 'utf-8')) # 要发送的第 1 张图片,注意:图片在本地当前目录下 with open('birdpython.png', 'rb') as f: msgImage = MIMEImage(f.read()) msgImage.add_header('Content-ID', '<image1>') message.attach(msgImage) # 要发送的第 2 张图片,注意:图片在本地当前目录下 with open('ruhua.png', 'rb') as f: msgImage = MIMEImage(f.read()) msgImage.add_header('Content-ID', '<image2>') message.attach(msgImage) # 邮件主题 message['Subject'] = 'title' # 发送方信息 message['From'] = sender # 接受方信息 message['To'] = receivers[0] # 登录并发送邮件 try: smtpObj = smtplib.SMTP() # 连接到服务器 smtpObj.connect(mail_host, 25) # 登录到服务器 smtpObj.login(mail_user, mail_pass) # 发送 smtpObj.sendmail(sender, receivers, message.as_string()) # 退出 smtpObj.quit() print('发送成功') except smtplib.SMTPException as e: print('发送失败', e) # 打印错误信息
执行以上代码后,打开 QQ 邮箱,可以看到收到的结果,如下图。
我们同样可以发送带附件的邮件,发送带附件的邮件,首先要创建 MIMEMultipart 实例,然后构造附件,如果有多个附件,可依次构造,最后利用 smtplib.smtp 发送。
import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.mime.image import MIMEImage ''' 发送邮件之前,我们首先设置 126 邮箱服务器所需信息 ''' # 126 邮箱服务器地址 mail_host = 'smtp.126.com' # 126 用户名 mail_user = 'fxxxxxx3' # 密码(注意:该密码不是你 126 邮箱的登录密码,而是你申请 SMTP 服务时,126 邮箱给你的授权码) mail_pass = 'ExxxxxxP' # 邮件发送方邮箱地址 sender = 'fxxxxxx3@126.com' # 邮件接受方邮箱地址,注意需要[]包裹,这意味着你可以写多个邮件地址群发 receivers = ['3xxxxxx3@qq.com'] ''' 设置要发送的 email 信息 ''' # 创建可以发送附件的邮件对象 message = MIMEMultipart() # 设置邮件正文内容 message.attach(MIMEText('老鸟python你好,请查看我发的附件。', 'plain', 'utf-8')) # 构造附件1,传送当前目录下的 test.txt 文件 with open('test.txt', 'rb')as f: att1 = MIMEText(f.read(), 'base64', 'utf-8') att1["Content-Type"] = 'application/octet-stream' # 这里的 filename 可以任意写,写什么名字,邮件中显示什么名字 att1["Content-Disposition"] = 'attachment; filename="test.txt"' message.attach(att1) # 构造附件2,传送当前目录下的 bird.png 文件 with open('birdpython.png', 'rb')as f: att2 = MIMEImage(f.read()) att2["Content-Type"] = 'application/octet-stream' att2["Content-Disposition"] = 'attachment; filename="bird.png"' message.attach(att2) # 邮件主题 message['Subject'] = 'title' # 发送方信息 message['From'] = sender # 接受方信息 message['To'] = receivers[0] # 登录并发送邮件 try: smtpObj = smtplib.SMTP() # 连接到服务器 smtpObj.connect(mail_host, 25) # 登录到服务器 smtpObj.login(mail_user, mail_pass) # 发送 smtpObj.sendmail(sender, receivers, message.as_string()) # 退出 smtpObj.quit() print('发送成功') except smtplib.SMTPException as e: print('发送失败', e) # 打印错误信息
执行以上代码后,打开 QQ 邮箱,可以看到收到的结果,如下图。
POP3 协议本身很简单,以下面的代码为例,我们来获取最新的一封邮件内容:
import poplib # 输入邮件地址, 口令和 POP3 服务器地址: email = input('Email: ') password = input('Password: ') pop3_server = input('POP3 server: ') # 连接到POP3服务器: server = poplib.POP3(pop3_server) # 可以打开或关闭调试信息: server.set_debuglevel(1) # 可选:打印POP3服务器的欢迎文字: print(server.getwelcome().decode('utf-8')) # 身份认证: server.user(email) server.pass_(password) # stat()返回邮件数量和占用空间: print('Messages: %s. Size: %s' % server.stat()) # list()返回所有邮件的编号: resp, mails, octets = server.list() # 可以查看返回的列表类似[b'1 82923', b'2 2184', ...] print(mails) # 获取最新一封邮件, 注意索引号从1开始: index = len(mails) resp, lines, octets = server.retr(index) # lines存储了邮件的原始文本的每一行, # 可以获得整个邮件的原始文本: msg_content = b'\r\n'.join(lines).decode('utf-8') # 打印出邮件内容,或者可以逐条解析每个邮件 print(msg_content) # 关闭连接: server.quit()
到目前为止,我们已经学会用 python 写程序进行发送邮件和读取邮件了,其实,用程序发送邮件是我们经常用到的,比如写个邮件群发工具来推广我们的产品等等,注意不要太流氓!
会发送普通文本邮件和html邮件。
会发送图片邮件。
会发送带附件的邮件。
写一个群发邮件工具,来推广一张带链接的图片。
请问发邮件可以定时吗?程序应该怎么写呢?
感谢作者的分享
发邮件到qq邮箱被归类为垃圾邮件怎么办?
之前好像试过python发邮件,后来发现qq什么都要 温馨提示:登录第三方客户端时,密码框请输入“授权码”进行验证。生成授权码 神烦,这个授权码还会过期...
现在很多邮箱都在这个层面做了限制,也是因为被滥发垃圾邮件搞多了
请教一下,为什么我在python里调好了文字纵向对齐,但是邮件发出来后文字对不齐呀?非常感谢,我困扰了好久了
如果公司设置在登陆邮箱之前需要先登录某个网址,这种情况下用python直接访问邮箱的话会无法访问,这种情况该如何处理呢
谢谢,还有一个问题…我一个词都没改,为什么有时候就能正常发送,有时候就显示500smtp错误?
去看返回的报错信息。多半是因为内容被判为垃圾邮件了吧
请问怎么收邮件附件呢