第17章-电子邮件/smtplib模块

python提供了两个非常好的模块,我们可以用它们来制作电子邮件。他们是 电子邮件SMTPLIB 模块。我们将花一些时间学习如何实际使用这些模块,而不是在这两个模块中介绍各种方法。具体来说,我们将涵盖以下内容:

  • 电子邮件的基础知识

  • 如何一次发送到多个地址

  • 如何使用“收件人”、“抄送”和“密件抄送”行发送电子邮件

  • 如何使用电子邮件模块添加附件和正文


我们开始吧!

电子邮件基础知识-如何使用smtplib发送电子邮件

这个 SMTPLIB 模块的使用非常直观。让我们编写一个简单的示例,演示如何发送电子邮件。将以下代码保存到硬盘上的文件中:

import smtplib

HOST = "mySMTP.server.com"
SUBJECT = "Test email from Python"
TO = "mike@someAddress.org"
FROM = "python@mydomain.com"
text = "Python 3.4 rules them all!"

BODY = "\r\n".join((
    "From: %s" % FROM,
    "To: %s" % TO,
    "Subject: %s" % SUBJECT ,
    "",
    text
    ))

server = smtplib.SMTP(HOST)
server.sendmail(FROM, [TO], BODY)
server.quit()

我们导入了两个模块, SMTPLIB 以及 一串 模块。此代码的三分之二用于设置电子邮件。大多数变量都是不需要解释的,所以我们只关注奇怪的变量,也就是 BODY .这里我们用 一串 模块将前面的所有变量组合成一个字符串,其中每行以回车(“/R”)和新行(“/N”)结尾。如果打印出正文,它将如下所示:

'From: python@mydomain.com\r\nTo: mike@mydomain.com\r\nSubject: Test email from Python\r\n\r\nblah blah blah'

之后,我们建立了一个到主机的服务器连接,然后调用smtplib模块 发送邮件 发送电子邮件的方法。然后我们断开与服务器的连接。您将注意到此代码中没有用户名或密码。如果服务器需要身份验证,则需要添加以下代码:

server.login(username, password)

这应该在创建服务器对象后立即添加。通常,您希望将此代码放入一个函数中,并使用其中一些参数调用它。您甚至可能希望将这些信息中的一些放入配置文件中。让我们把这段代码放到一个函数中。

import smtplib

def send_email(host, subject, to_addr, from_addr, body_text):
    """
    Send an email
    """
    BODY = "\r\n".join((
            "From: %s" % from_addr,
            "To: %s" % to_addr,
            "Subject: %s" % subject ,
            "",
            body_text
            ))
    server = smtplib.SMTP(host)
    server.sendmail(from_addr, [to_addr], BODY)
    server.quit()

if __name__ == "__main__":
    host = "mySMTP.server.com"
    subject = "Test email from Python"
    to_addr = "mike@someAddress.org"
    from_addr = "python@mydomain.com"
    body_text = "Python rules them all!"
    send_email(host, subject, to_addr, from_addr, body_text)

现在,只需查看函数本身,就可以看到实际代码有多小。一共13行!如果我们不把身体里的每一件东西都放在它自己的行上,我们可以把它缩短一些,但是它就没有那么可读了。现在我们将添加一个配置文件来保存服务器信息和 from 地址。为什么?在我所做的工作中,我们可能会使用不同的电子邮件服务器发送电子邮件,或者如果电子邮件服务器升级并且名称更改,那么我们只需要更改配置文件而不是代码。同样的事情也适用于 from 如果我们的公司被收购并合并到另一家公司的地址。

让我们看一下配置文件(另存为 email.ini ):

[smtp]
server = some.server.com
from_addr = python@mydomain.com

这是一个非常简单的配置文件。在里面我们有一个标有 smtp 其中有两项:服务器和源地址。我们将使用configobj来读取这个文件并将其转换为python字典。这是代码的更新版本(另存为 smtp_config.py ):

import os
import smtplib
import sys

from configparser import ConfigParser

def send_email(subject, to_addr, body_text):
    """
    Send an email
    """
    base_path = os.path.dirname(os.path.abspath(__file__))
    config_path = os.path.join(base_path, "email.ini")

    if os.path.exists(config_path):
        cfg = ConfigParser()
        cfg.read(config_path)
    else:
        print("Config not found! Exiting!")
        sys.exit(1)

    host = cfg.get("smtp", "server")
    from_addr = cfg.get("smtp", "from_addr")

    BODY = "\r\n".join((
        "From: %s" % from_addr,
        "To: %s" % to_addr,
        "Subject: %s" % subject ,
        "",
        body_text
    ))
    server = smtplib.SMTP(host)
    server.sendmail(from_addr, [to_addr], BODY)
    server.quit()

if __name__ == "__main__":
    subject = "Test email from Python"
    to_addr = "mike@someAddress.org"
    body_text = "Python rules them all!"
    send_email(subject, to_addr, body_text)

我们对这段代码添加了一点检查。我们首先要获取脚本本身所在的路径,这就是基路径所表示的路径。接下来,我们将该路径与文件名结合,以获得配置文件的完全限定路径。然后我们检查该文件是否存在。如果存在,我们会创建一个 ConfigParser 如果不是,我们打印一条消息并退出脚本。我们应该在 configParser.read()。 调用只是为了安全起见,尽管文件可能存在,但已损坏,或者我们可能没有权限打开它,这将引发异常。这将是一个你可以自己尝试的小项目。总之,假设一切正常,并且成功创建configParser对象。现在我们可以使用常见的configparser语法从主机和地址信息中提取。

现在我们准备好学习如何同时发送多封电子邮件了!

一次发送多封电子邮件

让我们稍微修改一下上一个示例,以便发送多封电子邮件!

import os
import smtplib
import sys

from configparser import ConfigParser

def send_email(subject, body_text, emails):
    """
    Send an email
    """
    base_path = os.path.dirname(os.path.abspath(__file__))
    config_path = os.path.join(base_path, "email.ini")

    if os.path.exists(config_path):
        cfg = ConfigParser()
        cfg.read(config_path)
    else:
        print("Config not found! Exiting!")
        sys.exit(1)

    host = cfg.get("smtp", "server")
    from_addr = cfg.get("smtp", "from_addr")

    BODY = "\r\n".join((
            "From: %s" % from_addr,
            "To: %s" % ', '.join(emails),
            "Subject: %s" % subject ,
            "",
            body_text
            ))
    server = smtplib.SMTP(host)
    server.sendmail(from_addr, emails, BODY)
    server.quit()

if __name__ == "__main__":
    emails = ["mike@someAddress.org", "someone@gmail.com"]
    subject = "Test email from Python"
    body_text = "Python rules them all!"
    send_email(subject, body_text, emails)

您会注意到,在本例中,我们删除了to-addr参数,并添加了一个email参数,它是一个电子邮件地址列表。为了使这项工作正常进行,我们需要在 To: 身体的一部分,也传递电子邮件 list发送邮件 方法。因此,我们执行以下操作来创建一个简单的逗号分隔字符串: '、'加入(电子邮件) .简单,嗯?

使用“收件人”、“抄送”和“密件抄送”行发送电子邮件

现在我们只需要了解如何使用cc和bcc字段发送。让我们创建一个支持该功能的新版本的代码!

import os
import smtplib
import sys

from configparser import ConfigParser

def send_email(subject, body_text, to_emails, cc_emails, bcc_emails):
    """
    Send an email
    """
    base_path = os.path.dirname(os.path.abspath(__file__))
    config_path = os.path.join(base_path, "email.ini")

    if os.path.exists(config_path):
        cfg = ConfigParser()
        cfg.read(config_path)
    else:
        print("Config not found! Exiting!")
        sys.exit(1)

    host = cfg.get("smtp", "server")
    from_addr = cfg.get("smtp", "from_addr")

    BODY = "\r\n".join((
            "From: %s" % from_addr,
            "To: %s" % ', '.join(to_emails),
            "CC: %s" % ', '.join(cc_emails),
            "BCC: %s" % ', '.join(bcc_emails),
            "Subject: %s" % subject ,
            "",
            body_text
            ))
    emails = to_emails + cc_emails + bcc_emails

    server = smtplib.SMTP(host)
    server.sendmail(from_addr, emails, BODY)
    server.quit()

if __name__ == "__main__":
    emails = ["mike@somewhere.org"]
    cc_emails = ["someone@gmail.com"]
    bcc_emails = ["schmuck@newtel.net"]

    subject = "Test email from Python"
    body_text = "Python rules them all!"
    send_email(subject, body_text, emails, cc_emails, bcc_emails)

在这段代码中,我们传递了3个列表,每个列表都有一个电子邮件地址。我们创建的cc和bcc字段与以前完全相同,但我们还需要将这三个列表合并为一个列表,以便将组合列表传递给sendmail()方法。在StackOverflow这样的论坛上,有些电子邮件客户端可能会以奇怪的方式处理bcc字段,从而允许收件人通过电子邮件头查看bcc列表。我无法确认这种行为,但我知道Gmail成功地从邮件头中删除了密件抄送信息。

现在我们准备好继续使用Python的电子邮件模块了!

使用电子邮件模块添加附件/正文

现在,我们将学习上一节的内容,并将其与Python电子邮件模块混合,以便发送附件。电子邮件模块使添加附件非常容易。代码如下:

import os
import smtplib
import sys

from configparser import ConfigParser
from email import encoders
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.utils import formatdate

#----------------------------------------------------------------------
def send_email_with_attachment(subject, body_text, to_emails,
                               cc_emails, bcc_emails, file_to_attach):
    """
    Send an email with an attachment
    """
    base_path = os.path.dirname(os.path.abspath(__file__))
    config_path = os.path.join(base_path, "email.ini")
    header = 'Content-Disposition', 'attachment; filename="%s"' % file_to_attach

    # get the config
    if os.path.exists(config_path):
        cfg = ConfigParser()
        cfg.read(config_path)
    else:
        print("Config not found! Exiting!")
        sys.exit(1)

    # extract server and from_addr from config
    host = cfg.get("smtp", "server")
    from_addr = cfg.get("smtp", "from_addr")

    # create the message
    msg = MIMEMultipart()
    msg["From"] = from_addr
    msg["Subject"] = subject
    msg["Date"] = formatdate(localtime=True)
    if body_text:
        msg.attach( MIMEText(body_text) )

    msg["To"] = ', '.join(to_emails)
    msg["cc"] = ', '.join(cc_emails)

    attachment = MIMEBase('application', "octet-stream")
    try:
        with open(file_to_attach, "rb") as fh:
            data = fh.read()
        attachment.set_payload( data )
        encoders.encode_base64(attachment)
        attachment.add_header(*header)
        msg.attach(attachment)
    except IOError:
        msg = "Error opening attachment file %s" % file_to_attach
        print(msg)
        sys.exit(1)

    emails = to_emails + cc_emails

    server = smtplib.SMTP(host)
    server.sendmail(from_addr, emails, msg.as_string())
    server.quit()

if __name__ == "__main__":
    emails = ["mike@someAddress.org", "nedry@jp.net"]
    cc_emails = ["someone@gmail.com"]
    bcc_emails = ["anonymous@circe.org"]

    subject = "Test email with attachment from Python"
    body_text = "This email contains an attachment!"
    path = "/path/to/some/file"
    send_email_with_attachment(subject, body_text, emails,
                               cc_emails, bcc_emails, path)

在这里,我们重命名了我们的函数并添加了一个新的参数, file_to_attach .我们还需要添加一个标题并创建一个 MIMEMultipart 对象。在添加附件之前,可以随时创建标题。我们将元素添加到 MIMEMultipart 对象(msg),就像我们要键入字典一样。您会注意到我们必须使用电子邮件模块 格式日期 方法插入格式正确的日期。要添加消息体,我们需要创建 MIMEText .如果您注意的话,您会发现我们没有添加BCC信息,但是您可以通过遵循上面代码中的约定很容易地做到这一点。接下来我们添加附件。我们将它包装在异常处理程序中,并使用 with 提取文件并将其放入 MIMEBase 对象。最后我们把它添加到 msg 变量,我们发送出去。注意,我们必须转换 msg 到中的字符串 发送邮件 方法。

总结

现在你知道如何用python发送电子邮件了。对于那些喜欢小型项目的人,您应该返回并在 server.sendmail 代码的一部分,以防过程中发生异常情况,例如 SMTPAuthenticationErrorSMTPConnectError .我们还可以在附加文件期间加强错误处理,以捕获其他错误。最后,我们可能希望获取这些不同的电子邮件列表,并创建一个已删除重复项的规范化列表。如果我们从文件中读取电子邮件地址列表,这一点尤其重要。

还要注意,我们的发件人地址是假的。我们可以使用python和其他编程语言来伪造电子邮件,但这是非常糟糕的礼节,而且可能是非法的,这取决于您居住的地方。你被警告了!明智地运用你的知识,享受 Python 带来的乐趣和利润!