geekdoc-python-zh/docs/pythonlibrary/how-to-send-emails-with-pyt...

14 KiB
Raw Permalink Blame History

如何用 Python 发送电子邮件

原文:https://www.blog.pythonlibrary.org/2021/09/21/how-to-send-emails-with-python/

Python 提供了几个非常好的模块,可以用来制作电子邮件。它们是电子邮件smtplib 模块。在这两个模块中,您将花一些时间学习如何实际使用这些模块,而不是复习各种方法。

具体来说,您将涉及以下内容:

  • 电子邮件的基础
  • 如何一次发送到多个地址
  • 如何使用“收件人”、“抄送”和“密件抄送”行发送电子邮件
  • 如何使用电子邮件模块添加附件和正文

我们开始吧!

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

smtplib 模块使用起来非常直观。你将写一个简单的例子,展示如何发送电子邮件。

打开您最喜欢的 Python IDE 或文本编辑器,创建一个新的 Python 文件。将以下代码添加到该文件并保存:

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。

在这里,您使用字符串的 join() 方法将前面的所有变量组合成一个字符串,其中每一行都以回车符("/r ")加新行("/n ")结束。如果你把正文打印出来,它会是这样的:

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

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

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 地址。

你为什么要这么做?许多组织使用不同的电子邮件服务器来发送电子邮件,或者如果电子邮件服务器升级并且名称改变,那么你只需要改变配置文件而不是代码。如果你的公司被另一家公司收购并合并,同样的事情也适用于发件人地址。

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

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

这是一个非常简单的配置文件。在其中,您有一个标记为 smtp 的部分,其中有两个项目:服务器和 from_addr 。您将使用 ConfigParser 来读取这个文件,并将它转换成一个 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)

您在这段代码中添加了一个小检查。你想首先获取脚本本身所在的路径,这就是 base_path 所代表的。接下来,将路径和文件名结合起来,得到配置文件的完全限定路径。然后检查该文件是否存在。

如果存在,您创建一个 ConfigParser ,如果不存在,您打印一条消息并退出脚本。为了安全起见,您应该在 ConfigParser.read() 调用周围添加一个异常处理程序,尽管该文件可能存在,但可能已损坏,或者您可能没有权限打开它,这将引发一个异常。

这将是一个你可以自己尝试的小项目。无论如何,假设一切顺利,并且成功创建了 ConfigParser 对象。现在,您可以使用常用的 ConfigParser 语法从 _addr 信息中提取主机和**。**

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

一次发送多封电子邮件

能够一次发送多封电子邮件是一个很好的功能。

继续修改你的最后一个例子,这样你就可以发送多封电子邮件了!

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 参数,并添加了一个 emails 参数,这是一个电子邮件地址列表。为此,您需要在正文的 To:部分创建一个逗号分隔的字符串,并将电子邮件列表传递给 sendmail 方法。因此,您执行以下操作来创建一个简单的逗号分隔的字符串:''。加入(邮件)。很简单,是吧?

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

现在你只需要弄清楚如何使用“抄送”和“密件抄送”字段发送邮件。

让我们创建一个支持该功能的新版本的代码!

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 个列表,每个列表有一个电子邮件地址。您创建的抄送密件抄送字段与之前完全相同,但是您还需要将 3 个列表合并成一个,这样您就可以将合并后的列表传递给 sendmail() 方法。

在 StackOverflow 这样的论坛上有一些传言说,一些电子邮件客户端可能会以奇怪的方式处理密件抄送字段,从而允许收件人通过电子邮件标题看到密件抄送列表。我无法确认这种行为,但我知道 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 )添加元素,就像向字典中添加键一样。您会注意到,您必须使用 email 模块的 formatdate 方法来插入正确格式化的日期。

要添加消息体,您需要创建一个 MIMEText 的实例。如果您注意的话,您会发现您没有添加密件抄送信息,但是您可以通过遵循上面代码中的约定很容易地这样做。

接下来,添加附件。您将它包装在一个异常处理程序中,并使用带有语句的来提取文件,并将其放入您的 MIMEBase 对象中。最后,你把它添加到 msg 变量中,然后发送出去。注意,您必须在 sendmail ()方法中将 msg 转换成一个字符串。

包扎

现在你知道如何用 Python 发送电子邮件了。对于那些喜欢小型项目的人来说,您应该回去在服务器周围添加额外的错误处理。 sendmail 部分代码,以防在这个过程中发生一些奇怪的事情。

一个例子是 SMTPAuthenticationErrorSMTPConnectError 。您还可以在附加文件的过程中加强错误处理,以捕捉其他错误。最后,您可能希望获得这些不同的电子邮件列表,并创建一个已删除重复项的规范化列表。如果您正在从文件中读取电子邮件地址列表,这一点尤其重要。

另外,请注意,您的发件人地址是假的。你可以使用 Python 和其他编程语言来欺骗电子邮件,但这是非常不礼貌的,而且可能是非法的,这取决于你住在哪里。你已经被警告了!

明智地使用你的知识,享受 Python 带来的乐趣和收益!

相关阅读

想学习更多 Python 基础知识?然后查看以下教程: