Skip to content

Latest commit

 

History

History
633 lines (495 loc) · 32.2 KB

File metadata and controls

633 lines (495 loc) · 32.2 KB

四、与电子邮件打交道

电子邮件是最流行的数字通信方式之一。Python 有大量用于处理电子邮件的内置库。在本章中,我们将学习如何使用 Python 编写、发送和检索电子邮件。本章将介绍以下主题:

  • 通过smtplib库用 SMTP 发送电子邮件
  • 使用 TLS 保护电子邮件传输
  • 使用带有poplib的 POP3 检索电子邮件
  • 使用带有imapclient的 IMAP 检索电子邮件
  • 使用 IMAP 在服务器上处理电子邮件
  • 借助logging模块发送电子邮件

电子邮件术语

在开始使用 Python 编写第一封电子邮件之前,让我们回顾一下电子邮件的一些基本概念。通常,最终用户使用软件或图形用户界面(GUI)编写、发送和接收电子邮件。这种软件被称为电子邮件客户端,例如,Mozilla Thunderbird、Microsoft Outlook 等都是电子邮件客户端。同样的任务也可以通过 web 界面完成,即 webmail 客户端界面。其中一些常见的例子是:Gmail、Yahoo mail、Hotmail 等等。

从客户端界面发送的邮件不会直接到达收件人的计算机。您的邮件通过许多专门的电子邮件服务器传送。这些服务器运行一个名为邮件传输代理MTA的软件,其主要任务是通过分析邮件头等将电子邮件路由到适当的目的地。

许多其他事情也会在途中发生,然后邮件到达收件人的本地电子邮件网关。然后,收件人可以使用其电子邮件客户端检索电子邮件。

上述过程涉及一些协议。以下列出了其中最常见的:

  • 简单邮件传输协议SMTP):MTA 使用 SMTP 协议将您的电子邮件发送到收件人的电子邮件服务器。SMTP 协议只能用于从一台主机向另一台主机发送电子邮件。
  • 邮局协议 3POP3):POP3 协议为用户访问邮箱并将邮件下载到计算机提供了一种简单、标准化的方式。使用 POP3 协议时,您的电子邮件将从 Internet 服务提供商(ISP)的邮件服务器下载到本地计算机。您也可以将电子邮件副本留在 ISP 服务器上。
  • 互联网消息访问协议IMAP):IMAP 协议还为从 ISP 的本地服务器访问您的电子邮件提供了一种简单而标准的方式。IMAP 是一种客户机/服务器协议,在该协议中,ISP 为您接收和保存电子邮件。由于这只需要很小的数据传输,因此该方案即使在低速连接(如移动电话网络)上也能很好地工作。只有当您发送阅读特定电子邮件的请求时,才会从 ISP 下载该电子邮件。您还可以做一些其他有趣的事情,例如在服务器上创建和操作文件夹或邮箱,删除邮件,等等。

Python 有三个模块,smtplibpoplibimaplib,分别支持 SMTP、POP3 和 IMAP 协议。每个模块都有使用传输层安全TLS协议)安全传输信息的选项。每个协议还使用某种形式的身份验证来确保数据的机密性。

使用 SMTP 发送电子邮件

我们可以使用smtplibe-mail包从 Python 脚本发送电子邮件。smtplib模块提供一个 SMTP 对象,用于通过 SMTP 或扩展 SMTPESMTP协议发送邮件。e-mail模块帮助我们利用各种标题信息和附件构建电子邮件。此模块符合所述的互联网消息格式IMF)http://tools.ietf.org/html/rfc2822.html

撰写电子邮件

让我们使用email模块中的类来构造电子邮件消息。email.mime模块提供了用于从头创建电子邮件和 MIME 对象的类。MIME多用途互联网邮件扩展的首字母缩写。这是原始 Internet 电子邮件协议的扩展。它广泛用于交换不同类型的数据文件,如音频、视频、图像、应用等。

许多类都是从 MIME 基类派生的。我们将以使用email.mime.multipart.MIMEMultipart()类的 SMTP 客户端脚本为例。它接受通过关键字字典传递电子邮件头信息。让我们看看如何使用MIMEMultipart()对象指定电子邮件头。多部分 mime 是指在一封电子邮件中同时发送电子邮件的 HTML 和文本部分。当电子邮件客户端接收到多部分消息时,如果可以呈现 HTML,它将接受 HTML 版本,否则将呈现纯文本版本,如以下代码块所示:

    from email.mime.multipart import MIMEMultipart()
    msg = MIMEMultipart()
    msg['To'] = recipient
    msg['From'] = sender
    msg['Subject'] = 'Email subject..'

现在,将纯文本消息附加到此多部分消息对象。我们可以使用MIMEText()对象包装纯文本消息。此类的构造函数接受附加参数。例如,我们可以传递textplain作为它的参数。此消息的数据可以通过set_payload()方式进行设置,如下图:

    part = MIMEText('text', 'plain')
    message = 'Email message ….'
    part.set_payload(message)

现在,我们将纯文本消息附加到多部分消息,如下所示:

    msg.attach(part)

已准备好使用一个或多个 SMTP MTA 服务器将邮件路由到目标邮件服务器。但是,很明显,脚本只与特定 MTA 对话,MTA 处理消息的路由。

发送电子邮件

smtplib模块为我们提供了一个 SMTP 类,可以通过 SMTP 服务器套接字进行初始化。初始化成功后,此将为我们提供一个 SMTP 会话对象。SMTP 客户端将与服务器建立正确的 SMTP 会话。这可以通过对 SMTPsession对象使用ehlo()方法来完成。实际的邮件发送将通过对 SMTP 会话应用sendmail()方法完成。因此,典型的 SMTP 会话如下所示:

    session = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
    session.ehlo()
    session.sendmail(sender, recipient, msg.as_string())
    session.quit()

在我们的示例 SMTP 客户端脚本中,我们使用了谷歌的免费 Gmail 服务。如果您有一个 Gmail 帐户,那么您可以使用 SMTP 从 Python 脚本向该帐户发送电子邮件。您的电子邮件最初可能会被阻止,因为 Gmail 可能会检测到它是从一个不太安全的电子邮件客户端发送的。您可以更改您的 Gmail 帐户设置,并允许您的帐户发送/接收来自不太安全的电子邮件客户端的电子邮件。您可以在谷歌网站上了解有关通过应用发送电子邮件的更多信息 https://support.google.com/a/answer/176600?hl=en

如果您没有 Gmail 帐户,那么您可以在典型的 Linux 环境中使用本地 SMTP 服务器设置并运行此脚本。以下代码显示了如何通过公共 SMTP 服务器发送电子邮件:

#!/usr/bin/env python3
# Listing 1 – First email client
import smtplib

from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

SMTP_SERVER = 'aspmx.l.google.com'
SMTP_PORT = 25

def send_email(sender, recipient):
    """ Send email message """
    msg = MIMEMultipart()
    msg['To'] = recipient
    msg['From'] = sender
    subject = input('Enter your email subject: ')
    msg['Subject'] = subject
    message = input('Enter your email message. Press Enter when finished. ')
    part = MIMEText('text', "plain")
    part.set_payload(message)
    msg.attach(part)
    # create smtp session
    session = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
    session.ehlo()
    #session.set_debuglevel(1)
    # send mail
    session.sendmail(sender, recipient, msg.as_string())
    print("You email is sent to {0}.".format(recipient))
    session.quit()

if __name__ == '__main__':
    sender = input("Enter sender email address: ")
    recipient = input("Enter recipient email address: ")
    send_email(sender, recipient)

如果您运行这个脚本,那么您可以看到输出与这里提到的类似。为了匿名,以下示例中未显示实际电子邮件地址:

$ python3 smtp_mail_sender.py 
Enter sender email address: <SENDER>@gmail.com 
Enter recipeint email address: <RECEIVER>@gmail.com
Enter your email subject: Test mail
Enter your email message. Press Enter when finished. This message can be ignored
You email is sent to <RECEIVER>@gmail.com.

此脚本将使用 Python 的标准库模块smtplib发送一封非常简单的电子邮件。为了编写消息,已从email.mime子模块导入了MIMEMultipartMIMEText类。此子模块有各种类型的类,用于编写带有不同类型附件的电子邮件,例如,MIMEApplication()MIMEAudio()MIMEImage()等。

在本例中,send_mail()函数由两个参数调用:发送方和接收方。这两个参数都是电子邮件地址。电子邮件由MIMEMultipart()消息类构造。基本头,如ToFromSubject已添加到此类名称空间。消息体由MIMEText()类的实例组成。这是通过类 set_payload()方法完成的。然后,通过attach()方法将该有效负载附加到主消息。

为了与 SMTP 服务器通信,将通过实例化smtplib模块的SMTP()类来创建与服务器的会话。服务器名称和端口参数将传递给构造函数。根据 SMTP 协议,客户端将通过ehlo()方法向服务器发送扩展的 hello 消息。消息将通过sendmail()方式发送。

注意如果在 SMTP 会话对象上调用set_debuglevel()方法,那么它将生成额外的调试消息。在前面的示例中,该行被注释掉。取消注释该行将生成调试消息,如:

$ python3 smtp_mail_sender.py 
Enter sender email address: <SENDER>@gmail.com
Enter recipeint email address: <RECEIVER>@gmail.com
Enter your 
email subject: Test email
Enter your email message. Press Enter when finished. This is a test email
send: 'mail FROM:<[email protected]> size=339\r\n'
reply: b'250 2.1.0 OK hg2si4622244wib.38 - gsmtp\r\n'
reply: retcode (250); Msg: b'2.1.0 OK hg2si4622244wib.38 - gsmtp'
send: 'rcpt TO:<[email protected]>\r\n'
reply: b'250 2.1.5 OK hg2si4622244wib.38 - gsmtp\r\n'
reply: retcode (250); Msg: b'2.1.5 OK hg2si4622244wib.38 - gsmtp'
send: 'data\r\n'
reply: b'354  Go ahead hg2si4622244wib.38 - gsmtp\r\n'
reply: retcode (354); Msg: b'Go ahead hg2si4622244wib.38 - gsmtp'
data: (354, b'Go ahead hg2si4622244wib.38 - gsmtp')
send: 'Content-Type: multipart/mixed; 
boundary="===============1431208306=="\r\nMIME-Version: 1.0\r\nTo: RECEIVER@gmail.com\r\nFrom: SENDER@gmail.com\r\nSubject: Test  email\r\n\r\n--===============1431208306==\r\nContent-Type: text/plain; charset="us-ascii"\r\nMIME-Version: 1.0\r\nContent- Transfer-Encoding: 7bit\r\n\r\nThis is a test email\r\n-- ===============1431208306==--\r\n.\r\n'
reply: b'250 2.0.0 OK 1414233177 hg2si4622244wib.38 - gsmtp\r\n'
reply: retcode (250); Msg: b'2.0.0 OK 1414233177 hg2si4622244wib.38 - gsmtp'
data: (250, b'2.0.0 OK 1414233177 hg2si4622244wib.38 - gsmtp')
You email is sent to RECEIVER@gmail.com.
send: 'quit\r\n'
reply: b'221 2.0.0 closing connection hg2si4622244wib.38 - gsmtp\r\n'
reply: retcode (221); Msg: b'2.0.0 closing connection hg2si4622244wib.38 - gsmtp'

这很有趣,因为消息是以逐步方式通过公共 SMTP 服务器发送的。

使用 TLS 安全地发送电子邮件

TLS协议是SSL安全套接字层的继承者。这确保了客户端和服务器之间的通信是安全的。这是通过以加密格式发送消息来实现的,这样未经授权的人就无法看到消息。将 TLS 与smtplib一起使用并不困难。创建 SMTP 会话对象后,需要调用starttls()方法。在发送电子邮件之前,您需要使用 SMTP 服务器凭据登录到服务器。

下面是第二个电子邮件客户端的示例:

#!/usr/bin/env python3
# Listing 2
import getpass
import smtplib

from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

SMTP_SERVER = 'smtp.gmail.com'
SMTP_PORT = 587 # ssl port 465, tls port 587

def send_email(sender, recipient):
    """ Send email message """
    msg = MIMEMultipart()
    msg['To'] = recipient
    msg['From'] = sender
    msg['Subject'] = input('Enter your email subject: ')
    message = input('Enter your email message. Press Enter when finished. ')
    part = MIMEText('text', "plain")
    part.set_payload(message)
    msg.attach(part)
    # create smtp session
    session = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
    session.set_debuglevel(1)
    session.ehlo()
    session.starttls()
    session.ehlo
    password = getpass.getpass(prompt="Enter you email password: ") 
    # login to server
    session.login(sender, password)
    # send mail
    session.sendmail(sender, recipient, msg.as_string())
    print("You email is sent to {0}.".format(recipient))
    session.quit()

if __name__ == '__main__':
    sender = input("Enter sender email address: ")
    recipient = input("Enter recipeint email address: ")
    send_email(sender, recipient)

前面的代码类似于我们的第一个示例,除了对服务器的身份验证。在这种情况下,SMTP 用户通过服务器的身份验证。如果我们在打开 SMTP 调试后运行脚本,则会看到类似于以下内容的输出:

$ python3 smtp_mail_sender_tls.py 
Enter sender email address: SENDER@gmail.com
Enter recipeint email address: RECEPIENT@gmail.com
Enter your email subject: Test email
Enter your email message. Press Enter when finished. This is a test email that can be ignored.

用户输入后,开始与服务器通信。它将通过ehlo()方法启动。作为对该命令的响应,SMTP 服务器将发送一些带有返回代码250的响应行。此响应将包括服务器支持的功能。

这些响应的摘要将表明服务器已准备好继续使用客户端,如下所示:

send: 'ehlo debian6box.localdomain.loc\r\n'
reply: b'250-mx.google.com at your service, [77.233.155.107]\r\n'
reply: b'250-SIZE 35882577\r\n'
reply: b'250-8BITMIME\r\n'
reply: b'250-STARTTLS\r\n'
reply: b'250-ENHANCEDSTATUSCODES\r\n'
reply: b'250-PIPELINING\r\n'
reply: b'250-CHUNKING\r\n'
reply: b'250 SMTPUTF8\r\n'
reply: retcode (250); Msg: b'mx.google.com at your service, [77.233.155.107]\nSIZE 35882577\n8BITMIME\nSTARTTLS\nENHANCEDSTATUSCODES\nPIPELINING\ nCHUNKING\nSMTPUTF8'

初始命令后,客户端将使用starttls()方法升级到 TLS 的连接,如下图:

send: 'STARTTLS\r\n'
reply: b'220 2.0.0 Ready to start TLS\r\n'
reply: retcode (220); Msg: b'2.0.0 Ready to start TLS'
Enter you email password: 
send: 'ehlo debian6box.localdomain.loc\r\n'
reply: b'250-mx.google.com at your service, [77.233.155.107]\r\n'
reply: b'250-SIZE 35882577\r\n'
reply: b'250-8BITMIME\r\n'
reply: b'250-AUTH LOGIN PLAIN XOAUTH XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER\r\n'
reply: b'250-ENHANCEDSTATUSCODES\r\n'
reply: b'250-PIPELINING\r\n'
reply: b'250-CHUNKING\r\n'
reply: b'250 SMTPUTF8\r\n'
reply: retcode (250); Msg: b'mx.google.com at your service, [77.233.155.107]\nSIZE 35882577\n8BITMIME\nAUTH LOGIN PLAIN XOAUTH XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER\nENHANCEDSTATUSCODES\nPIPELINING\nCHUNKING\nSMTPUTF8'

在认证阶段,通过客户端脚本借助login()方法发送认证数据。请注意,身份验证令牌是 base-64 编码的字符串,用户名和密码由空字节分隔。对于复杂的客户端,还存在其他受支持的身份验证协议。以下是身份验证令牌的示例:

send: 'AUTH PLAIN A...dvXXDDCCD.......sscdsvsdvsfd...12344555\r\n'
reply: b'235 2.7.0 Accepted\r\n'
reply: retcode (235); Msg: b'2.7.0 Accepted'

客户端通过身份验证后,可以使用sendmail()方式发送电子邮件。将三个参数传递给此方法:发件人、收件人和消息。示例输出如下所示:

send: 'mail FROM:<[email protected]> size=360\r\n'
reply: b'250 2.1.0 OK xw9sm8487512wjc.24 - gsmtp\r\n'
reply: retcode (250); Msg: b'2.1.0 OK xw9sm8487512wjc.24 - gsmtp'
send: 'rcpt TO:<[email protected]>\r\n'
reply: b'250 2.1.5 OK xw9sm8487512wjc.24 - gsmtp\r\n'
reply: retcode (250); Msg: b'2.1.5 OK xw9sm8487512wjc.24 - gsmtp'
send: 'data\r\n'
reply: b'354  Go ahead xw9sm8487512wjc.24 - gsmtp\r\n'
reply: retcode (354); Msg: b'Go ahead xw9sm8487512wjc.24 - gsmtp'
data: (354, b'Go ahead xw9sm8487512wjc.24 - gsmtp')
send: 'Content-Type: multipart/mixed; boundary="===============1501937935=="\r\nMIME-Version: 1.0\r\n
To: <Output omitted>-===============1501937935==--\r\n.\r\n'
reply: b'250 2.0.0 OK 1414235750 xw9sm8487512wjc.24 - gsmtp\r\n'
reply: retcode (250); Msg: b'2.0.0 OK 1414235750 xw9sm8487512wjc.24 - gsmtp'
data: (250, b'2.0.0 OK 1414235750 xw9sm8487512wjc.24 - gsmtp')
You email is sent to RECEPIENT@gmail.com.
send: 'quit\r\n'
reply: b'221 2.0.0 closing connection xw9sm8487512wjc.24 - gsmtp\r\n'
reply: retcode (221); Msg: b'2.0.0 closing connection xw9sm8487512wjc.24 - gsmtp'

使用 POP3 和 poplib 检索电子邮件

存储的电子邮件可下载并由本地计算机读取。POP3 协议可用于从电子邮件服务器下载邮件。Python 有一个名为poplib的模块,可以用于此目的。该模块提供两个高级类POP()POP3_SSL(),分别实现 POP3 和 POP3S 协议,用于与 POP3/POP3S 服务器通信。它接受三个参数:主机、端口和超时。如果省略端口,则可以使用默认端口(110)。可选超时参数确定服务器上连接超时的长度(以秒为单位)。

POP3()的安全版本是其子类POP3_SSL()。它采用额外的参数,如 keyfile 和 certfile,用于提供 SSL 证书文件,即私钥和证书链文件。

为 POP3 客户机编写代码也非常简单。为此,通过初始化POP3()POP3_SSL()类来实例化邮箱对象。然后,使用以下命令调用user()pass_()方法登录服务器:

  mailbox = poplib.POP3_SSL(<POP3_SERVER>, <SERVER_PORT>) 
  mailbox.user('username')
       mailbox.pass_('password')

现在,您可以调用各种方法来操纵帐户和消息。这里列出了一些有趣的方法:

  • stat():此方法根据两个整数的元组返回邮箱状态,即邮件计数和邮箱大小。
  • list():此方法发送获取消息列表的请求,这已在本节后面的示例中演示。
  • retr():此方法为参数消息提供一个数字,指示必须检索的消息。它还将邮件标记为已读。
  • dele():此方法为必须删除的消息提供参数。在许多 POP3 服务器上,直到退出才执行删除。您可以使用rset()方法重置删除标志。
  • quit():此方法通过提交一些更改并断开您与服务器的连接来断开连接。

让我们看看如何通过访问谷歌的安全 POP3 电子邮件服务器来读取电子邮件信息。默认情况下,POP3 服务器安全地侦听端口995。以下是使用 POP3 获取电子邮件的示例:

#!/usr/bin/env python3
import getpass
import poplib

GOOGLE_POP3_SERVER = 'pop.googlemail.com'
POP3_SERVER_PORT = '995'

def fetch_email(username, password): 
    mailbox = poplib.POP3_SSL(GOOGLE_POP3_SERVER, POP3_SERVER_PORT) 
    mailbox.user(username)
    mailbox.pass_(password) 
    num_messages = len(mailbox.list()[1])
    print("Total emails: {0}".format(num_messages))
    print("Getting last message") 
    for msg in mailbox.retr(num_messages)[1]:
        print(msg)
    mailbox.quit()

if __name__ == '__main__':
    username = input("Enter your email user ID: ")
    password = getpass.getpass(prompt="Enter your email password:    ") 
    fetch_email(username, password)

正如您在前面的代码中所看到的,fetch_email()函数通过调用POP3_SSL()和服务器套接字创建了一个邮箱对象。通过调用user()pass_()方法,在此对象上设置用户名和密码。认证成功后,我们可以使用方法调用 POP3 命令,例如调用list()方法来列出电子邮件。在本例中,屏幕上显示了消息总数。然后,使用retr()方法检索单个消息的内容。

此处显示了一个示例输出:

$ python3 fetch_email_pop3.py 
Enter your email user ID: <PERSON1>@gmail.com
Enter your email password: 
Total emails: 330
Getting last message
b'Received: by 10.150.139.7 with HTTP; Tue, 7 Oct 2008 13:20:42 -0700 
(PDT)'
b'Message-ID: <[email protected]>'
b'Date: Tue, 7 Oct 2008 21:20:42 +0100'
b'From: "Mr Person1" <[email protected]>'
b'To: "Mr Person2" <[email protected]>'
b'Subject: Re: Some subject'
b'In-Reply-To: <[email protected]>'
b'MIME-Version: 1.0'
b'Content-Type: multipart/alternative; '
b'\tboundary="----=_Part_63057_22732713.1223410842697"'
b'References: <[email protected]>'
b'\t <[email protected]>'
b'Delivered-To: [email protected]'
b''
b'------=_Part_63057_22732713.1223410842697'
b'Content-Type: text/plain; charset=ISO-8859-1'
b'Content-Transfer-Encoding: quoted-printable'
b'Content-Disposition: inline'
b''
b'Dear Person2,'

使用 IMAP 和 imaplib 检索电子邮件

正如我们前面提到的,通过 IMAP 协议访问电子邮件并不一定会将消息下载到本地计算机或移动电话。因此,即使在任何低带宽的互联网连接上使用,这也是非常有效的。

Python 提供了一个名为imaplib的客户端库,可用于通过 IMAP 协议访问电子邮件。这提供了实现 IMAP 协议的IMAP4()类。它需要两个参数,即主机和端口来实现该协议。默认情况下,143已被用作端口号。

派生类,即IMAP4_SSL(),提供了 IMAP4 协议的安全版本。它通过 SSL 加密的套接字进行连接。因此,您需要一个 SSL 友好的套接字模块。默认端口为993。与POP3_SSL()类似,您可以提供私钥路径和证书文件路径。

IMAP 客户端的典型外观示例如下所示:

  mailbox = imaplib.IMAP4_SSL(<IMAP_SERVER>, <SERVER_PORT>) 
      mailbox.login('username', 'password')
      mailbox.select('Inbox')

上述代码将尝试启动 IMAP4 加密的客户端会话。login()方法成功后,您可以在创建的对象上应用各种方法。在上述代码片段中,使用了select()方法。这将选择用户的邮箱。默认邮箱名为Inbox。此邮箱对象支持的方法的完整列表可在 Python 标准库文档页面上找到,该页面位于https://docs.python.org/3/library/imaplib.html

在这里,我们想演示您如何使用search()方法搜索邮箱。它接受字符集和搜索条件参数。字符集参数可以是None,其中不向服务器发送特定字符的请求。但是,至少需要指定一个标准。要对邮件进行预先搜索排序,可以使用sort()方法。

与 POP3 类似,我们将使用一个安全的 IMAP 连接通过IMAP4_SSL()类连接到服务器。下面是 Python IMAP 客户端的一个轻量级示例:

#!/usr/bin/env python3
import getpass
import imaplib
import pprint

GOOGLE_IMAP_SERVER = 'imap.googlemail.com'
IMAP_SERVER_PORT = '993'

def check_email(username, password): 
    mailbox = imaplib.IMAP4_SSL(GOOGLE_IMAP_SERVER, IMAP_SERVER_PORT) 
    mailbox.login(username, password)
    mailbox.select('Inbox')
    tmp, data = mailbox.search(None, 'ALL')
    for num in data[0].split():
        tmp, data = mailbox.fetch(num, '(RFC822)')
        print('Message: {0}\n'.format(num))
        pprint.pprint(data[0][1])
        break
    mailbox.close()
    mailbox.logout()

if __name__ == '__main__':
    username = input("Enter your email username: ")
    password = getpass.getpass(prompt="Enter you account password: ")
    check_email(username, password)

在本例中,已创建了IMPA4_SSL()的实例,即邮箱对象。在本文中,我们将服务器地址和端口作为参数。使用login()方法成功登录后,您可以使用select()方法选择要访问的邮箱文件夹。在本例中,选择了Inbox文件夹。为了阅读邮件,我们需要从收件箱中请求数据。一种方法是使用search()方法。在成功接收到一些邮件元数据后,我们可以使用fetch()方法检索电子邮件信封部分和数据。在本例中,借助于fetch()方法寻找 RFC 822 类型的标准文本消息。我们可以使用 Python pretty print 或 print 模块在屏幕上显示输出。最后,对邮箱对象应用close()logout()方法。

上述代码将显示类似于以下内容的输出:

$ python3 fetch_email_imap.py 
Enter your email username: RECIPIENT@gmail.comn
Enter you Google password: 
Message b'1'
b'X-Gmail-Received: 3ec65fa310559efe27307d4e37fdc95406deeb5a\r\nDelivered-To: [email protected]\r\nReceived: by 10.54.40.10 with SMTP id n10cs1955wrn;\r\n    [Message omitted]

发送电子邮件附件

在上一节中,我们已经了解了如何使用 SMTP 协议发送纯文本消息。在本节中,让我们探讨如何通过电子邮件发送附件。我们可以使用第二个示例,其中我们使用 TLS 发送了一封电子邮件。撰写电子邮件时,除了添加纯文本消息外,还包括附加附件字段。

在本例中,我们可以将MIMEImage类型用于email.mime.image子模块。GIF 类型的图像将附加到电子邮件中。假定可以在文件系统路径中的任何位置找到 GIF 图像。此文件路径通常基于用户输入。

以下示例显示了如何随电子邮件发送附件:

#!/usr/bin/env python3

import os
import getpass
import re
import sys
import smtplib

from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

SMTP_SERVER = 'aspmx.l.google.com'
SMTP_PORT = 25

def send_email(sender, recipient):
    """ Sends email message """
    msg = MIMEMultipart()
    msg['To'] = recipient
    msg['From'] = sender
    subject = input('Enter your email subject: ')
    msg['Subject'] = subject
    message = input('Enter your email message. Press Enter when     finished. ')
    part = MIMEText('text', "plain")
    part.set_payload(message)
    msg.attach(part)
    # attach an image in the current directory
    filename = input('Enter the file name of a GIF image: ')
    path = os.path.join(os.getcwd(), filename)
    if os.path.exists(path):
        img = MIMEImage(open(path, 'rb').read(), _subtype="gif")
        img.add_header('Content-Disposition', 'attachment', filename=filename)
        msg.attach(img)
    # create smtp session
    session = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
    session.ehlo()
    session.starttls()
    session.ehlo
    # send mail
    session.sendmail(sender, recipient, msg.as_string())
    print("You email is sent to {0}.".format(recipient))
    session.quit()

if __name__ == '__main__':
    sender = input("Enter sender email address: ")
    recipient = input("Enter recipeint email address: ")
    send_email(sender, recipient)

如果您运行前面的脚本,它将询问常见的问题,即电子邮件发件人、收件人、用户凭据和图像文件的位置。

$ python3 smtp_mail_sender_mime.py 
Enter sender email address: SENDER@gmail.com
Enter recipeint email address: RECIPIENT@gmail.com
Enter your email subject: Test email with attachment 
Enter your email message. Press Enter when finished. This is a test email with atachment.
Enter the file name of a GIF image: image.gif
You email is sent to RECIPIENT@gmail.com.

通过日志模块发送电子邮件

在任何现代编程语言中,日志记录设施都具有相同的功能。类似地,Python 的日志模块具有非常丰富的特性和灵活性。我们可以在日志模块中使用不同类型的日志处理程序,例如控制台或文件日志处理程序。您可以最大化日志记录好处的一种方法是,在生成日志的同时,通过电子邮件将日志消息发送给用户。Python 的日志模块提供了一种名为BufferingHandler,的处理程序,它能够缓冲日志数据。

后面显示了扩展BufferingHandler的示例。名为BufferingSMTPHandler的子类由BufferingHandler定义。在本例中,使用日志模块创建 logger 对象的实例。然后,BufferingSMTPHandler的一个实例绑定到此记录器对象。日志记录级别设置为调试,以便它可以记录任何消息。四个单词的示例列表已用于创建四个日志条目。每个日志条目应类似于以下内容:

<Timestamp> INFO  First line of log
This accumulated log message will be emailed to a local user as set on top of the script.

现在,让我们看看完整的代码。以下是借助日志模块发送电子邮件的示例:

import logging.handlers
import getpass

MAILHOST = 'localhost'
FROM = 'you@yourdomain'
TO = ['%s@localhost' %getpass.getuser()] 
SUBJECT = 'Test Logging email from Python logging module (buffering)'

class BufferingSMTPHandler(logging.handlers.BufferingHandler):
    def __init__(self, mailhost, fromaddr, toaddrs, subject, capacity):
        logging.handlers.BufferingHandler.__init__(self, capacity)
        self.mailhost = mailhost
        self.mailport = None
        self.fromaddr = fromaddr
        self.toaddrs = toaddrs
        self.subject = subject
        self.setFormatter(logging.Formatter("%(asctime)s %(levelname)-5s %(message)s"))

    def flush(self):
        if len(self.buffer) > 0:
            try:
                import smtplib
                port = self.mailport
                if not port:
                    port = smtplib.SMTP_PORT
                    smtp = smtplib.SMTP(self.mailhost, port)
                    msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n" % (self.fromaddr, ",".join(self.toaddrs), self.subject)
                for record in self.buffer:
                    s = self.format(record)
                    print(s)
                    msg = msg + s + "\r\n"
                smtp.sendmail(self.fromaddr, self.toaddrs, msg)
                smtp.quit()
            except:
                self.handleError(None) # no particular record
            self.buffer = []

def test():
    logger = logging.getLogger("")
    logger.setLevel(logging.DEBUG)
    logger.addHandler(BufferingSMTPHandler(MAILHOST, FROM, TO, SUBJECT, 10))
    for data in ['First', 'Second', 'Third', 'Fourth']:
        logger.info("%s line of log", data)
    logging.shutdown()

if __name__ == "__main__":
    test()

如您所见,我们的BufferingSMTPHandler方法只覆盖了一个方法,即flush()。在构造函数__init__()上,使用setFormatter()方法设置基本变量和日志格式。在flush()方法中,我们创建了一个SMTP()对象的实例。SMTP 邮件标头已使用可用数据创建。日志消息已附加到电子邮件消息中,并且已调用sendmail()方法发送电子邮件消息。flush()方法中的代码被包装在try-except块中。

讨论的脚本输出类似于以下内容:

$ python3 logger_mail_send.py 
2014-10-25 13:15:07,124 INFO  First line of log
2014-10-25 13:15:07,127 INFO  Second line of log
2014-10-25 13:15:07,127 INFO  Third line of log
2014-10-25 13:15:07,129 INFO  Fourth line of log

现在,当您使用电子邮件命令(Linux/UNIX 机器自带)检查电子邮件时,您可以预期本地用户会收到电子邮件,如下所示:

$ mail
Mail version 8.1.2 01/15/2001\.  Type ? for help.
"/var/mail/faruq": 1 message 1 new
>N  1 you@yourdomain     Sat Oct 25 13:15   20/786   Test Logging email from Python logging module (buffering)

您可以通过在命令提示符下键入消息 ID 和&来查看消息的内容,如以下输出所示:

& 1
Message 1:
From you@yourdomain Sat Oct 25 13:15:08 2014
Envelope-to: faruq@localhost
Delivery-date: Sat, 25 Oct 2014 13:15:08 +0100
Date: Sat, 25 Oct 2014 13:15:07 +0100
From: you@yourdomain
To: faruq@localhost
Subject: Test Logging email from Python logging module (buffering)

2014-10-25 13:15:07,124 INFO  First line of log
2014-10-25 13:15:07,127 INFO  Second line of log
2014-10-25 13:15:07,127 INFO  Third line of log
2014-10-25 13:15:07,129 INFO  Fourth line of log

最后,您可以通过在命令提示符下键入快捷方式q退出邮件程序,如下所示:

& q
Saved 1 message in /home/faruq/mbox

总结

本章演示 Python 如何与三种主要的电子邮件处理协议进行交互:SMTP、POP3 和 IMAP。在每种情况下,都解释了如何使用客户机代码。最后,展示了在 Python 的日志模块中使用 SMTP 的示例。

在下一章中,您将学习如何使用 Python 与远程系统一起执行各种任务,例如使用 SSH 执行管理任务、通过 FTP 进行文件传输、Samba 等。还将简要讨论一些远程监控协议(如 SNMP)和身份验证协议(如 LDAP)。因此,请在下一章中编写更多 Python 代码。