登录
首页 >  文章 >  python教程

Python发邮件带附件教程详解

时间:2025-09-16 16:48:54 186浏览 收藏

想要用Python发送带附件的邮件?本文为你提供一份详尽教程,助你轻松实现这一功能。核心在于利用`smtplib`模块连接邮件服务器,并结合`email.mime`系列模块构建包含文本内容和附件的MIMEMultipart对象。不同于纯文本邮件,附件邮件需遵循MIME标准,结构更为复杂。本文将通过代码示例,详细讲解如何使用`MIMEText`处理正文,`MIMEBase`和`encoders`处理Base64编码的附件,以及如何解决常见的编码问题和文件类型考虑。同时,针对发送邮件时可能遇到的认证失败和连接超时问题,提供了一系列排查和解决方案,助你避坑,确保邮件顺利送达。掌握这些技巧,你也能轻松玩转Python邮件发送!

答案:发送带附件邮件需构造MIMEMultipart对象,结合MIMEText、MIMEBase和encoders处理正文与Base64编码的附件,并通过smtplib连接SMTP服务器发送;与纯文本邮件不同,附件邮件需遵循MIME标准,结构更复杂。

python如何发送带附件的邮件_python smtplib模块发送带附件邮件实例

用Python发送带附件的邮件,核心在于利用smtplib模块连接邮件服务器,并结合email.mime系列模块来精心构造一个多部分(multipart)邮件消息,其中包含了文本内容和附件的二进制数据。这并非简单的字符串拼接,而是需要遵循邮件协议的结构化过程。

解决方案

发送带附件的邮件需要我们构建一个MIMEMultipart对象,它能容纳邮件的各个部分,比如正文文本和附件。下面是一个实际的Python代码示例,展示了如何实现这一功能:

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
import os

def send_email_with_attachment(sender_email, sender_password, receiver_email, subject, body, attachment_path):
    """
    发送一封带附件的邮件。

    参数:
    sender_email (str): 发件人邮箱地址。
    sender_password (str): 发件人邮箱密码或授权码。
    receiver_email (str): 收件人邮箱地址。
    subject (str): 邮件主题。
    body (str): 邮件正文。
    attachment_path (str): 附件文件的完整路径。
    """
    try:
        # 创建一个MIMEMultipart对象,用于组合邮件的正文和附件
        msg = MIMEMultipart()
        msg['From'] = sender_email
        msg['To'] = receiver_email
        msg['Subject'] = subject

        # 添加邮件正文
        msg.attach(MIMEText(body, 'plain', 'utf-8'))

        # 处理附件
        if attachment_path and os.path.exists(attachment_path):
            filename = os.path.basename(attachment_path)

            # 打开附件文件并读取其二进制内容
            with open(attachment_path, 'rb') as attachment:
                # 创建MIMEBase对象,用于表示附件
                # 'application' 和 'octet-stream' 是通用的MIME类型,表示二进制数据
                # 如果是特定类型,如PDF,可以是 'application', 'pdf'
                part = MIMEBase('application', 'octet-stream')
                part.set_payload(attachment.read())

            # 对附件进行Base64编码,这是邮件传输二进制数据的标准方式
            encoders.encode_base64(part)

            # 添加附件头部信息,指定文件名
            part.add_header(
                'Content-Disposition',
                f'attachment; filename= {filename}',
            )

            # 将附件添加到MIMEMultipart对象中
            msg.attach(part)
        else:
            print(f"警告:附件文件 '{attachment_path}' 不存在或路径无效,将不发送附件。")

        # 连接SMTP服务器
        # 不同的邮件服务商有不同的SMTP服务器地址和端口
        # 例如:
        # Gmail: smtp.gmail.com, 587 (TLS) 或 465 (SSL)
        # Outlook/Hotmail: smtp.office365.com, 587 (TLS)
        # QQ邮箱: smtp.qq.com, 465 (SSL) 或 587 (TLS)

        # 这里以Gmail为例,使用TLS加密
        smtp_server = "smtp.gmail.com"
        smtp_port = 587 

        print(f"尝试连接SMTP服务器: {smtp_server}:{smtp_port}...")
        server = smtplib.SMTP(smtp_server, smtp_port)
        server.starttls()  # 启动TLS加密

        print(f"尝试登录邮箱: {sender_email}...")
        server.login(sender_email, sender_password)

        # 发送邮件
        text = msg.as_string() # 将MIMEMultipart对象转换为字符串
        server.sendmail(sender_email, receiver_email, text)
        print("邮件发送成功!")

    except smtplib.SMTPAuthenticationError as e:
        print(f"SMTP认证失败:请检查邮箱地址和密码/授权码是否正确。错误信息:{e}")
        print("对于Gmail等服务,可能需要生成应用专用密码。")
    except smtplib.SMTPConnectError as e:
        print(f"SMTP连接失败:请检查服务器地址和端口,或网络连接。错误信息:{e}")
    except smtplib.SMTPException as e:
        print(f"发送邮件时发生SMTP错误:{e}")
    except FileNotFoundError:
        print(f"错误:附件文件 '{attachment_path}' 未找到。")
    except Exception as e:
        print(f"发生未知错误:{e}")
    finally:
        if 'server' in locals() and server:
            server.quit()
            print("SMTP服务器连接已关闭。")

# --- 使用示例 ---
if __name__ == "__main__":
    # 请替换为你的实际邮箱信息和附件路径
    SENDER_EMAIL = "your_email@gmail.com"  # 你的发件邮箱
    # 注意:对于Gmail等,这里通常不是你的登录密码,而是“应用专用密码”
    SENDER_PASSWORD = "your_app_password"  
    RECEIVER_EMAIL = "recipient_email@example.com" # 收件人邮箱
    EMAIL_SUBJECT = "Python发送的带附件测试邮件"
    EMAIL_BODY = "你好!这是一封Python脚本发送的测试邮件,包含一个附件。"

    # 假设你有一个名为 'test.txt' 的文件在当前脚本目录下
    # 或者指定一个完整路径,例如:r"C:\Users\YourUser\Documents\report.pdf"
    ATTACHMENT_FILE = "test.txt" 

    # 为了测试,创建一个虚拟的附件文件
    with open(ATTACHMENT_FILE, "w") as f:
        f.write("这是附件里的内容,希望你能收到!\n")
        f.write("附件可以是任何类型的文件,比如图片、PDF或文档。")

    send_email_with_attachment(SENDER_EMAIL, SENDER_PASSWORD, RECEIVER_EMAIL, EMAIL_SUBJECT, EMAIL_BODY, ATTACHMENT_FILE)

    # 清理测试文件
    if os.path.exists(ATTACHMENT_FILE):
        os.remove(ATTACHMENT_FILE)
        print(f"测试文件 '{ATTACHMENT_FILE}' 已删除。")

为什么直接用smtplib发送普通文本邮件和发送带附件邮件的代码看起来差别那么大?

这个问题,我个人觉得是初学者最容易感到困惑的地方。说实话,一开始我也觉得这两种方式差异巨大,似乎完全是两套体系。但深入理解后,你会发现这其实是邮件协议设计的必然结果。

简单来说,当我们只发送纯文本邮件时,邮件内容可以被看作是一个简单的字符串,直接作为邮件体发送即可。smtplib模块本身就能很好地处理这种“一维”的数据流。你可能只需要一个MIMEText对象,甚至更简单地直接传递字符串。

然而,一旦涉及附件,情况就复杂了。邮件本身是一个文本协议,它并不能直接“传输”一个二进制文件(比如图片、PDF或压缩包)。这就好比你不能直接把一个包裹塞进一个信封里,你需要把包裹打包好,贴上标签,然后交给邮局处理。

这里的“打包”和“贴标签”工作,在Python中就由email.mime模块家族来完成。

  • MIMEMultipart:它就像一个大信封,能把邮件的各个部分(正文、附件1、附件2等)都装进去。它负责声明这封邮件是“多部分”的,并且每个部分之间如何分隔。
  • MIMEText:用于处理邮件的正文部分,告诉邮件客户端这部分是纯文本,编码是什么。
  • MIMEBase:这是处理非文本附件的基础类。它需要我们把附件的二进制数据读进来,然后进行Base64编码。Base64编码能把二进制数据转换成ASCII字符,这样邮件系统就能像传输普通文本一样传输它了。
  • encoders.encode_base64:专门负责把二进制数据编码成Base64格式。
  • add_header:这一步至关重要,它给附件“贴上标签”,告诉邮件客户端这个部分是什么文件类型(Content-Type,比如application/pdfimage/jpeg),以及它的原始文件名(Content-Disposition)。

所以,这种“看起来差别大”的根本原因在于,发送带附件的邮件,实际上是在构建一个更复杂的、结构化的邮件对象,而不是简单地发送一段文本。它需要我们明确地告诉邮件服务器和接收方的客户端,邮件的哪些部分是正文,哪些是附件,附件是什么类型,叫什么名字,等等。这背后都是为了遵循MIME(Multipurpose Internet Mail Extensions)标准,确保邮件内容能够被正确解析和显示。

处理邮件附件时,有哪些常见的编码问题和文件类型考虑?

在处理邮件附件时,编码和文件类型是两个绕不开的关键点,处理不好就可能导致附件乱码、无法打开或者被邮件系统误判。

1. 编码问题:

  • Base64编码是核心:邮件协议最初是为传输ASCII文本设计的,直接发送二进制数据(如图片、PDF)会导致数据损坏。Base64编码就是为了解决这个问题而生。它将任意二进制数据转换为一套由64种ASCII字符组成的字符串。在Python中,email.encoders.encode_base64()就是做这个事的。如果你忘记对附件进行Base64编码,接收方看到的附件很可能就是一堆乱码或者根本无法识别。
  • 文本附件的字符编码:如果你的附件本身是文本文件(比如.txt, .csv, .log),那么在将其作为附件发送时,需要确保它在本地保存时的字符编码(如UTF-8, GBK)与你在MIMETextMIMEBase中声明的charset一致。虽然附件通常会经过Base64编码,但如果文件内容本身是文本,其内部的字符编码仍然重要,尤其是在接收方打开附件时。通常,使用UTF-8是一个比较稳妥的选择,因为它支持绝大多数字符。
  • 邮件正文的编码:虽然和附件不是一回事,但邮件正文的编码也常出问题。我们通常会使用MIMEText(body, 'plain', 'utf-8')来确保正文内容(尤其是包含中文等非ASCII字符时)不会乱码。

2. 文件类型(MIME类型)考虑:

  • Content-Type的重要性:这是告诉邮件客户端附件是什么类型文件的关键。MIMEBase在创建时需要两个参数:maintypesubtype,它们共同构成了MIME类型(例如,image/jpegapplication/pdftext/plain)。
    • 如果你指定了正确的MIME类型,比如一个PDF文件设置为application/pdf,那么接收方的邮件客户端就能识别它是一个PDF,并可能直接预览或用默认的PDF阅读器打开。
    • 如果你使用了通用的application/octet-stream,这意味着“我不知道这是什么类型,但它是一个二进制数据流”。客户端通常会提示用户保存或选择程序打开,这虽然通用,但用户体验可能不如直接识别。
  • 如何确定MIME类型
    • 手动指定:如果你知道附件类型,可以直接写死,比如MIMEBase('application', 'pdf')
    • mimetypes模块:Python标准库中的mimetypes模块可以根据文件扩展名自动猜测MIME类型,这在处理多种未知类型附件时非常有用。
      import mimetypes
      # ...
      ctype, encoding = mimetypes.guess_type(attachment_path)
      if ctype is None or encoding is not None:
        ctype = 'application/octet-stream' # 无法猜测或有编码,回退到通用类型
      maintype, subtype = ctype.split('/', 1)
      part = MIMEBase(maintype, subtype)
      # ...
  • Content-Disposition:这个头部告诉邮件客户端如何“处置”这个附件。我们通常会设置attachment; filename="your_file_name.ext",这表示这是一个附件,并且建议客户端用指定的文件名保存。如果没有这个头部,或者设置不当,附件可能不会被识别为独立文件,而是直接显示在邮件正文里(如果客户端尝试解析的话),或者文件名乱码。

总而言之,处理附件时,你需要像个细心的邮递员,不仅要确保包裹内容完好无损(Base64编码),还要在包裹上贴好准确的标签(MIME类型和文件名),这样收件人才能顺利收到并正确使用你的“包裹”。

发送邮件时遇到认证失败或连接超时,该如何排查和解决?

在用Python脚本发送邮件时,认证失败和连接超时是两个非常常见的“拦路虎”。我遇到过太多次了,每次都得从头开始排查。这里我总结了一些经验,希望能帮到你。

1. 认证失败(smtplib.SMTPAuthenticationError):

这是最常见的问题,通常意味着你的脚本无法登录到SMTP服务器。

  • 密码或授权码错误
    • 检查密码:最直接的原因就是密码输错了。请仔细核对你的发件邮箱地址和密码。
    • 应用专用密码:这是很多邮件服务商(尤其是Gmail、Outlook、QQ邮箱等)在开启了两步验证(2FA)后强制要求的。你不能直接使用你的邮箱登录密码来登录SMTP服务器。你需要在邮箱设置中生成一个“应用专用密码”或“授权码”,然后用这个生成的密码来替代你的真实登录密码。这是我见过90%以上认证失败的原因。
      • Gmail:访问Google账户安全设置,找到“应用密码”或“应用专用密码”选项。
      • Outlook/Hotmail:在微软账户安全设置中,查找“应用密码”。
      • QQ邮箱:在邮箱设置中,找到“账户”选项卡,开启SMTP服务并生成“授权码”。
    • 旧版“允许不安全应用访问”:对于Gmail,以前有个选项叫“允许不安全应用访问”,现在基本已经停用或者不推荐使用了。如果你的代码依赖这个,现在很可能不再奏效。
  • SMTP服务器设置问题
    • 发件人邮箱的SMTP服务未开启:有些邮箱默认关闭了SMTP服务,需要手动去邮箱设置里开启。
    • 发件人邮箱的安全策略:有些企业邮箱或自定义域名邮箱会有更严格的安全策略,可能会限制外部IP访问或需要特定的认证方式。
  • IP地址限制:极少数情况下,你的发件邮箱可能设置了IP地址白名单,只有特定IP才能登录。

排查方法:

  1. 手动登录:尝试用你的邮箱地址和密码(或授权码)在网页端或邮件客户端(如Outlook, Thunderbird)手动登录并发送一封邮件,确认邮箱本身工作正常。
  2. 生成应用专用密码:如果你的邮箱开启了两步验证,立即去生成一个应用专用密码,并用它替换代码中的SENDER_PASSWORD
  3. 检查邮箱设置:登录你的发件邮箱网页版,检查SMTP服务是否开启,是否有其他安全设置限制。
  4. 日志分析:如果服务器返回了更详细的错误信息,仔细阅读它们。

2. 连接超时(smtplib.SMTPConnectError 或其他网络错误):

连接超时通常与网络环境、服务器地址或端口配置有关。

  • SMTP服务器地址或端口错误
    • 核对服务器地址:不同邮件服务商的SMTP服务器地址不同(例如smtp.gmail.com, smtp.office365.com, smtp.qq.com)。确保你的代码中填写的地址是正确的。
    • 核对端口号
      • 587:通常用于TLS加密连接(需要调用server.starttls())。
      • 465:通常用于SSL加密连接(需要使用smtplib.SMTP_SSL而不是smtplib.SMTP)。
      • 混淆端口和加密方式是常见错误。如果你用smtplib.SMTP连接465端口,或用smtplib.SMTP_SSL连接587端口,都可能失败。
  • 防火墙或网络限制
    • 本地防火墙:你的电脑或服务器上的防火墙可能阻止了Python脚本对外连接587465端口。检查防火墙设置,尝试临时关闭防火墙进行测试。
    • 网络环境:你所在的网络(公司网络、公共Wi-Fi)可能对某些端口进行了限制或屏蔽。尝试更换网络环境(例如,从公司网络切换到手机热点)进行测试。
    • 代理服务器:如果你在代理服务器后面,确保你的Python环境配置了正确的代理设置。
  • DNS解析问题:你的系统可能无法正确解析SMTP服务器的域名。尝试ping smtp.gmail.com等命令,看是否能解析并连通。
  • SMTP服务器暂时故障:虽然不常见,但邮件服务商的SMTP服务器也可能偶尔出现短暂的故障或维护。等待一段时间再试。

排查方法:

  1. 双重检查配置:再次核对SMTP服务器地址和端口,确保与你的邮件服务商要求一致。
  2. 测试端口连通性
    • 在命令行使用telnet smtp.gmail.com 587(或对应服务器和端口),如果能看到欢迎信息,说明端口是开放的。如果连接失败或超时,则可能是防火墙或网络问题。
    • 对于Windows用户,可能需要先在“程序和功能”中开启Telnet客户端。
  3. 更换网络环境:如上所述,切换网络可以快速判断是否是网络限制。
  4. 检查日志:Python的smtplib模块在连接失败时会抛出异常,异常信息通常会给出一些线索。

遇到这些问题时,保持耐心,一步步排查,通常都能找到症结所在。记住,大多数时候问题出在配置上,而不是代码逻辑本身。

到这里,我们也就讲完了《Python发邮件带附件教程详解》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于的知识点!

相关阅读
更多>
最新阅读
更多>
课程推荐
更多>