Python发邮件带附件教程详解
想要用Python发送带附件的邮件?本文为你提供一份详尽教程,助你轻松实现这一功能。核心在于利用`smtplib`模块连接邮件服务器,并结合`email.mime`系列模块构建包含文本内容和附件的MIMEMultipart对象。不同于纯文本邮件,附件邮件需遵循MIME标准,结构更为复杂。本文将通过代码示例,详细讲解如何使用`MIMEText`处理正文,`MIMEBase`和`encoders`处理Base64编码的附件,以及如何解决常见的编码问题和文件类型考虑。同时,针对发送邮件时可能遇到的认证失败和连接超时问题,提供了一系列排查和解决方案,助你避坑,确保邮件顺利送达。掌握这些技巧,你也能轻松玩转Python邮件发送!
答案:发送带附件邮件需构造MIMEMultipart对象,结合MIMEText、MIMEBase和encoders处理正文与Base64编码的附件,并通过smtplib连接SMTP服务器发送;与纯文本邮件不同,附件邮件需遵循MIME标准,结构更复杂。
用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/pdf
或image/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)与你在MIMEText
或MIMEBase
中声明的charset
一致。虽然附件通常会经过Base64编码,但如果文件内容本身是文本,其内部的字符编码仍然重要,尤其是在接收方打开附件时。通常,使用UTF-8是一个比较稳妥的选择,因为它支持绝大多数字符。 - 邮件正文的编码:虽然和附件不是一回事,但邮件正文的编码也常出问题。我们通常会使用
MIMEText(body, 'plain', 'utf-8')
来确保正文内容(尤其是包含中文等非ASCII字符时)不会乱码。
2. 文件类型(MIME类型)考虑:
Content-Type
的重要性:这是告诉邮件客户端附件是什么类型文件的关键。MIMEBase
在创建时需要两个参数:maintype
和subtype
,它们共同构成了MIME类型(例如,image/jpeg
,application/pdf
,text/plain
)。- 如果你指定了正确的MIME类型,比如一个PDF文件设置为
application/pdf
,那么接收方的邮件客户端就能识别它是一个PDF,并可能直接预览或用默认的PDF阅读器打开。 - 如果你使用了通用的
application/octet-stream
,这意味着“我不知道这是什么类型,但它是一个二进制数据流”。客户端通常会提示用户保存或选择程序打开,这虽然通用,但用户体验可能不如直接识别。
- 如果你指定了正确的MIME类型,比如一个PDF文件设置为
- 如何确定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才能登录。
排查方法:
- 手动登录:尝试用你的邮箱地址和密码(或授权码)在网页端或邮件客户端(如Outlook, Thunderbird)手动登录并发送一封邮件,确认邮箱本身工作正常。
- 生成应用专用密码:如果你的邮箱开启了两步验证,立即去生成一个应用专用密码,并用它替换代码中的
SENDER_PASSWORD
。 - 检查邮箱设置:登录你的发件邮箱网页版,检查SMTP服务是否开启,是否有其他安全设置限制。
- 日志分析:如果服务器返回了更详细的错误信息,仔细阅读它们。
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
端口,都可能失败。
- 核对服务器地址:不同邮件服务商的SMTP服务器地址不同(例如
- 防火墙或网络限制:
- 本地防火墙:你的电脑或服务器上的防火墙可能阻止了Python脚本对外连接
587
或465
端口。检查防火墙设置,尝试临时关闭防火墙进行测试。 - 网络环境:你所在的网络(公司网络、公共Wi-Fi)可能对某些端口进行了限制或屏蔽。尝试更换网络环境(例如,从公司网络切换到手机热点)进行测试。
- 代理服务器:如果你在代理服务器后面,确保你的Python环境配置了正确的代理设置。
- 本地防火墙:你的电脑或服务器上的防火墙可能阻止了Python脚本对外连接
- DNS解析问题:你的系统可能无法正确解析SMTP服务器的域名。尝试
ping smtp.gmail.com
等命令,看是否能解析并连通。 - SMTP服务器暂时故障:虽然不常见,但邮件服务商的SMTP服务器也可能偶尔出现短暂的故障或维护。等待一段时间再试。
排查方法:
- 双重检查配置:再次核对SMTP服务器地址和端口,确保与你的邮件服务商要求一致。
- 测试端口连通性:
- 在命令行使用
telnet smtp.gmail.com 587
(或对应服务器和端口),如果能看到欢迎信息,说明端口是开放的。如果连接失败或超时,则可能是防火墙或网络问题。 - 对于Windows用户,可能需要先在“程序和功能”中开启Telnet客户端。
- 在命令行使用
- 更换网络环境:如上所述,切换网络可以快速判断是否是网络限制。
- 检查日志:Python的
smtplib
模块在连接失败时会抛出异常,异常信息通常会给出一些线索。
遇到这些问题时,保持耐心,一步步排查,通常都能找到症结所在。记住,大多数时候问题出在配置上,而不是代码逻辑本身。
到这里,我们也就讲完了《Python发邮件带附件教程详解》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于的知识点!

- 上一篇
- CentOS7编译安装Golang教程

- 下一篇
- HTML记忆卡片游戏开发教程
-
- 文章 · python教程 | 29分钟前 |
- Matplotlib如何修改特定点颜色
- 383浏览 收藏
-
- 文章 · python教程 | 59分钟前 |
- VSCodeGitBash无法使用Conda命令解决方法
- 257浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python正则表达式调试技巧详解
- 327浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- NumPy矩阵乘法与线性运算全解析
- 419浏览 收藏
-
- 文章 · python教程 | 2小时前 |
- DjangoLDAP认证:用户搜索与组权限配置教程
- 462浏览 收藏
-
- 文章 · python教程 | 2小时前 |
- Python读取DICOM医疗数据方法
- 300浏览 收藏
-
- 文章 · python教程 | 4小时前 |
- Python图像处理:Pillow库入门教程
- 319浏览 收藏
-
- 文章 · python教程 | 4小时前 |
- Python类型提示是什么?有何优势?
- 419浏览 收藏
-
- 文章 · python教程 | 4小时前 |
- Python实现GPT-2文本生成教程
- 216浏览 收藏
-
- 文章 · python教程 | 4小时前 |
- Pandas多行更新技巧:map与update用法解析
- 168浏览 收藏
-
- 文章 · python教程 | 5小时前 |
- with语句与上下文管理器原理解析
- 277浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 514次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 499次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- AI Mermaid流程图
- SEO AI Mermaid 流程图工具:基于 Mermaid 语法,AI 辅助,自然语言生成流程图,提升可视化创作效率,适用于开发者、产品经理、教育工作者。
- 629次使用
-
- 搜获客【笔记生成器】
- 搜获客笔记生成器,国内首个聚焦小红书医美垂类的AI文案工具。1500万爆款文案库,行业专属算法,助您高效创作合规、引流的医美笔记,提升运营效率,引爆小红书流量!
- 634次使用
-
- iTerms
- iTerms是一款专业的一站式法律AI工作台,提供AI合同审查、AI合同起草及AI法律问答服务。通过智能问答、深度思考与联网检索,助您高效检索法律法规与司法判例,告别传统模板,实现合同一键起草与在线编辑,大幅提升法律事务处理效率。
- 651次使用
-
- TokenPony
- TokenPony是讯盟科技旗下的AI大模型聚合API平台。通过统一接口接入DeepSeek、Kimi、Qwen等主流模型,支持1024K超长上下文,实现零配置、免部署、极速响应与高性价比的AI应用开发,助力专业用户轻松构建智能服务。
- 719次使用
-
- 迅捷AIPPT
- 迅捷AIPPT是一款高效AI智能PPT生成软件,一键智能生成精美演示文稿。内置海量专业模板、多样风格,支持自定义大纲,助您轻松制作高质量PPT,大幅节省时间。
- 614次使用
-
- Flask框架安装技巧:让你的开发更高效
- 2024-01-03 501浏览
-
- Django框架中的并发处理技巧
- 2024-01-22 501浏览
-
- 提升Python包下载速度的方法——正确配置pip的国内源
- 2024-01-17 501浏览
-
- Python与C++:哪个编程语言更适合初学者?
- 2024-03-25 501浏览
-
- 品牌建设技巧
- 2024-04-06 501浏览