使用Python接收电子邮件(第3节)


在前面的教程中,我们已经详细介绍了如何使用SMTP协议发送电子邮件。本节教程中我们将介绍如何使用Python接收邮件接收邮件有两种常用的协议分别为POP3协议IMAP协议

POP3协议是指Post Office Protocol(version 3)(邮局协议第3版),是一种简单的邮件接收协议,允许电子邮件客户端下载邮件服务器上的邮件,但是在客户端的操作(如移动邮件、标记已读等)不会同步到邮件服务器上,比如通过客户端收取了邮箱的两封邮件并移动到其他文件夹,邮件服务器上的这些邮件不会被同步移动。POP3协议的优点在于其简单易用,适用于个人用户和小型企业。但是,由于它不支持邮件在服务器上的同步更新,因此不适合多设备同步使用的场景。

IMAP协议是指Internet Mail Access Protocol(互联网邮件访问协议),是一种更先进的电子邮件接收协议。与POP3不同,它提供了邮件服务器与电子邮件客户端之间的双向通信,任何在客户端做的改变都会同步到邮件服务器上。例如,删除邮件,标记已读等,服务器上的邮件也会做相应的动作。所以无论从浏览器登录邮箱或者客户端软件登录邮箱,看到的邮件以及状态都是一致的;而POP3在客户端的操作不会反馈到服务器上。

1、使用POP3协议接收邮件

Python内置一个poplib模块,实现了POP3协议,可以直接用来接收邮件。使用poplib模块收取邮件也很简单,该模块同样提供了poplib.POP3和 poplib.POP3_SSL两个类,分别用于连接普通的POP服务器和基于SSL的POP服务器。以下是一个简单的Python脚本,演示了如何通过SMTP服务器发送邮后再通过POP3服务器接收邮件。

动手练一练:

import smtplib
import poplib
from time import sleep

# 发件人邮箱账号
from_addres = "5********8@qq.com"
# 发件人邮箱密码,即配置生成的授权码
password = "m***********b"
# 收件人邮箱账号,你可以填任意想要发送的邮箱,也可以发送给自己
to_addres = "5********8@qq.com"
# 邮件正文
origmsg = '''\
From: %(who)s
To: %(who)s
Subject: Learn Python

Learn Python in www.pyhint.com
''' % {'who': from_addres}

#使用SMTP完成邮件的发送
sendSvr = smtplib.SMTP_SSL("smtp.qq.com",)
# 登录操作
sendSvr.login(from_addres, password)
#参数:发件人,收件人,邮件整体(消息头和消息体的字符串表示)
errs = sendSvr.sendmail(from_addres,to_addres,origmsg)
# 退出服务器
sendSvr.quit()
#,assert返回为假就会触发异常
assert len(errs) == 0, errs
print("QQ邮件发送成功!")

# 睡眠5秒钟,等待邮件被投递,让服务器完成消息的发送和接收。
sleep(5)

# 使用pop3完成邮件的获取,创建一个pop3接收对象
recvSvr = poplib.POP3_SSL("pop.qq.com")
# 设置用户名
recvSvr.user(from_addres)
# 设置密码
recvSvr.pass_(password)
# 获取邮件列表
emailist = recvSvr.stat()
#下载第一个邮件
rsp, msg, siz = recvSvr.retr(emailist[0])
print(msg)
print("pop3接收邮件完成")

上面以QQ邮箱为例,使用smtp发送了一个普通文本邮件,睡眠5秒钟后,等待邮件被投递,再使用pop3协议读取了邮件列表,并下载了第一个邮件,最后成功返回了第一个邮件内容“Learn Python in www.pyhint.com”。

2、使用IMAP协议接收邮件

Python内置一个imaplib模块,实现了IMAP协议接收邮件,与POP3协议接收邮件的操作基本类似。例如:

动手练一练:

import smtplib
import imaplib
import email
from time import sleep

# 发件人邮箱账号
from_addres = "5********8@qq.com"
# 发件人邮箱密码,即配置生成的授权码
password = "m***********b"
# 收件人邮箱账号,你可以填任意想要发送的邮箱,也可以发送给自己
to_addres = "5********8@qq.com"
# 邮件正文
origmsg = '''\
From: %(who)s
To: %(who)s
Subject: Learn Python

Learn Python in www.pyhint.com
''' % {'who': from_addres}

#使用SMTP完成邮件的发送
sendSvr = smtplib.SMTP_SSL("smtp.qq.com",)
# 登录操作
sendSvr.login(from_addres, password)
#参数:发件人,收件人,邮件整体(消息头和消息体的字符串表示)
errs = sendSvr.sendmail(from_addres,to_addres,origmsg)
# 退出服务器
sendSvr.quit()
#,assert返回为假就会触发异常
assert len(errs) == 0, errs
print("QQ邮件发送成功!")

# 睡眠5秒钟,等待邮件被投递,让服务器完成消息的发送和接收。
sleep(5)

# 使用IMAP协议完成邮件的获取,连接服务器
imap_server = imaplib.IMAP4_SSL("imap.qq.com",)

#登录服务器
imap_server.login(from_addres, password)

#选择默认“收件箱”,并打印邮件数量
res, data = imap_server.select('INBOX')
print(res, data)
print(data[0])

#获取最新的一封邮件
typ, lines = imap_server.fetch(data[0], '(RFC822)')

#解析出邮件
msg = email.message_from_string(lines[0][1].decode('utf-8'))
print(msg)
imap_server.close()

3、解析邮件

使用POP3协议获取邮件其实很简单,真正麻烦的是把邮件的原始内容解析为可以阅读的邮件对象。首先使用poplib.POP3或poplib.POP3_SSL按POP3协议从服务器端下载邮件。再使用email.parser.Parser或email.parser.BytesParser解析邮件内容,得到EmailMessage对象,从EmailMessage对象中读取邮件内容。例如:

动手练一练:

import smtplib
import poplib, os.path, mimetypes
from email.mime.text import MIMEText
from email.utils import formataddr
from time import sleep
from email.parser import BytesParser
from email.policy import default

# 发件人邮箱账号
from_addres = "5********8@qq.com"
# 发件人邮箱密码,即配置生成的授权码
password = "m***********b"
# 收件人邮箱账号,你可以填任意想要发送的邮箱,也可以发送给自己
to_addres = "5********8@qq.com"
# html内容
text = """
    <h1>学会Python编程,月薪过万不是梦! </h1>
    <p><a href="https://www.pyhint.com">在线学习Python编程,进入www.pyhint.com</a></p>
    <p>
    学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。
    <img src="cid:img01">
    </p>
    """
# 指定消息体使用纯文本格式
message = MIMEText(text, "html", "utf-8")
# 发件人显式的名字
message["From"] = formataddr(["Python编程教学", from_addres])
# 收件人显式的名字
message["To"] = formataddr(["在线编程学习", to_addres]) 
# 邮件标题
message["Subject"] = "Python解析邮件"

try:
    # QQ邮箱SMTP服务器,端口是25
    server = smtplib.SMTP_SSL("smtp.qq.com",)
    # 登录
    server.login(from_addres, password)
    # 发送
    server.sendmail(from_addres, to_addres, message.as_string())
    print("QQ邮件发送成功!")
    # 退出服务器
    server.quit()
except smtplib.SMTPException as e:
    print("QQ邮件发送失败!", e)

# 睡眠5秒钟,等待邮件被投递
sleep(5)

# 连接到POP3服务器:
conn = poplib.POP3_SSL("pop.qq.com",)
# 可以打开或关闭调试信息:
conn.set_debuglevel(1)
# 可选:打印POP 3服务器的欢迎文字:
print(conn.getwelcome().decode('utf-8'))
# 输入用户名
conn.user(from_addres)
# 输入密码,即配置生成的授权码
conn.pass_(password)
# 获取邮件统计信息,相当于发送POP3的stat命令
message_num, total_size = conn.stat()
print('邮件数: %s. 总大小: %s' % (message_num, total_size))
# mails列表保存每封邮件的编号、大小
resp, mails, octets = conn.list()
print(resp, mails)
# 获取指定邮件的内容(此处传入总长度,也就是获取最后一封邮件)
# resp保存服务器的响应码
# data保存该邮件的内容
resp, data, octets  = conn.retr(len(mails))
# 将data的所有数据(原本是一个字节列表)拼接在一起
msg_data = b'\r\n'.join(data)
# 将字符串内容解析成邮件,此处一定要指定policy=default
msg = BytesParser(policy=default).parsebytes(msg_data)
print(type(msg))
print('发件人:' + msg['from'])
print('收件人:' + msg['to'])
print('主题:' + msg['subject'])
print('第一个收件人名字:' + msg['to'].addresses[0].username)
print('第一个发件人名字:' + msg['from'].addresses[0].username)
for part in msg.walk():
    counter = 1
    # 如果maintype是multipart,说明是容器(用于包含正文、附件等)
    if part.get_content_maintype() == 'multipart' :
        continue
    # 如果maintype是multipart,说明是邮件正文部分
    elif part.get_content_maintype() == 'text':
        print(part.get_content())
    # 处理附件
    else :
        # 获取附件的文件名
        filename = part.get_filename()
        # 如果没有文件名,程序要负责为附件生成文件名
        if not filename:
            # 根据附件的contnet_type来推测它的后缀名
            ext = mimetypes.guess_extension(part.get_content_type())
            # 如果推测不出后缀名
            if not ext:
                # 使用.bin作为后缀名
                ext = '.bin'
            # 程序为附件来生成文件名
            filename = 'part-%03d%s' % (counter, ext)
        counter += 1
        # 将附件写入的本地文件
        with open(os.path.join('.', filename), 'wb') as fp:
            fp.write(part.get_payload(decode=True))
# 退出服务器,相当于发送POP3的quit命令
conn.quit()

上面的例子中,使用smtp发送了一个HTML格式的邮件,睡眠5秒钟后,等待邮件被投递,再使用pop3协议读取了邮件列表,并下载了第一个邮件,如果程序要读取第一个邮件的各部分内容,则需要调用该对象的walk()方法,该方法返回一个可迭代对象,程序使用for循环遍历walk()方法的返回值,对邮件内容进行逐项处理:如果邮件某项的“maintype”是“multipart”,则说明这一项是容器,包含邮件内容、附件等其他项。如果邮件某项的“maintype”是“text”,则说明这一项的内容是文本,通常就是邮件正文或文本附件。对于这种文本内容,程序直接将其输出到控制台中。如果邮件某项的“maintype”是其他,则说明这一项的内容是附件,程序会自动将附件内容保存在本地文件中。

执行以上代码,可以看到程序收取了指定的第一个邮件,并将邮件内容输出到控制台中,将邮件附件保存在本地文件中。