Python脚本实现邮件自动化收发全攻略
目录导读
为什么要用Python操作邮件?
在日常工作中,我们常常需要批量发送通知邮件(如日报、报表)、自动读取验证码、监控收件箱中的关键信息(如订单确认、退款提醒),手动操作不仅耗时,还容易出错,Python通过内置库和第三方库,能够以极简代码实现邮件收发自动化,大幅提升效率。

适用场景:
- 自动发送:定时推送周报、营销邮件、系统告警。
- 自动接收:从收件箱提取附件、解析验证码、识别特定主题的邮件。
- 数据集成:将邮件内容存入数据库或触发后续工作流(如添加工单)。
问题1:Python收发邮件需要开启什么协议? 解答:发送邮件需使用SMTP协议(如smtp.gmail.com),接收邮件需使用IMAP(推荐,支持管理服务器上的邮件)或POP3(仅下载),大多数邮箱需要开启“允许低安全性应用”或生成专用应用密码(如QQ邮箱、163邮箱的授权码)。
核心库选择:smtplib与imaplib的对比
Python官方库提供了两组核心模块:
| 功能模块 | 发送邮件 (smtplib) | 接收邮件 (imaplib / poplib) |
|---|---|---|
| 使用场景 | 构造并发送邮件 | 读取/搜索/删除服务器上的邮件 |
| 核心类 | SMTP(或SMTP_SSL) |
IMAP4(或IMAP4_SSL) |
| 常用方法 | sendmail(), login() |
search(), fetch(), store() |
| 编码配合 | email.mime.text, email.mime.multipart |
email.parser, email.message_from_bytes |
关键点:
- 发送时需构造MIME对象(可带附件、HTML正文)。
- 接收时需先登录,选择收件箱(INBOX),再搜索指定条件的邮件(如“未读”、“来自某地址”)。
问题2:为什么有些邮箱用smtplib发送失败? 解答:常见原因包括:
- 未使用SSL加密(端口465或587需TLS)。
- 授权码错误(需用邮箱生成的专用密码,而非登录密码),包含敏感词或图片链接被服务器拦截,建议使用
SMTP_SSL()并设置login()里填授权码。
发送邮件的完整代码拆解
以下代码展示如何发送一封带附件和HTML样式的邮件,适配主流邮箱(如Gmail、QQ、163):
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
# 邮箱配置
smtp_server = "smtp.qq.com" # 发送服务器
port = 465 # SSL端口
sender = "your@qq.com"
password = "你的授权码" # 邮箱生成的授权码,非登录密码
receiver = "target@example.com"
# 构建邮件对象
msg = MIMEMultipart()
msg["From"] = sender
msg["To"] = receiver
msg["Subject"] = "Python自动化测试邮件 | 包含附件"
HTML格式)
body = """
<html>
<body>
<h2>这是一封自动发送的测试邮件</h2>
<p>附件中是本周的销售数据表。</p>
</body>
</html>
"""
msg.attach(MIMEText(body, "html", "utf-8"))
# 添加附件(以Excel为例)
filename = "data.xlsx"
try:
with open(filename, "rb") as attachment:
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header(
"Content-Disposition",
f"attachment; filename= {filename}",
)
msg.attach(part)
except FileNotFoundError:
print(f"附件 {filename} 未找到,已跳过附件发送。")
# 发送
try:
with smtplib.SMTP_SSL(smtp_server, port) as server:
server.login(sender, password) # 使用授权码登录
server.sendmail(sender, receiver, msg.as_string())
print("邮件发送成功!")
except smtplib.SMTPAuthenticationError:
print("认证失败:请检查授权码是否正确,或邮箱是否允许SMTP服务。")
except smtplib.SMTPRecipientsRefused:
print("收件人地址被拒绝,请确认邮箱格式。")
要点解析:
- 使用
MIMEMultipart作为容器,可同时携带文本和附件。 - 附件读取后需编码为Base64,避免中文乱码。
SMTP_SSL直接建立加密连接,避免明文传输。- 异常捕获(如认证失败、附件缺失)是生产环境的必要步骤。
问题3:可以发送多个收件人吗?如何实现?
解答:可以通过cc(抄送)和bcc(密送)实现,只需在msg["To"]中传入(逗号)分隔的地址,并分别设置msg["Cc"]和msg["Bcc"]字段,但smtp.sendmail()的参数to_addrs必须包含所有收件人(包括Cc和Bcc地址)。
接收并解析邮件的实战案例
以下代码演示登录邮箱并读取最近3封未读邮件的摘要(发件人、主题、正文文本):
import imaplib
import email
from email.header import decode_header
# IMAP配置
imap_server = "imap.qq.com"
username = "your@qq.com"
password = "你的授权码" # 同样是邮箱授权码
# 登录并选择收件箱
mail = imaplib.IMAP4_SSL(imap_server)
mail.login(username, password)
mail.select("INBOX") # 选择收件箱
# 搜索未读邮件
status, messages = mail.search(None, "UNSEEN")
if status == "OK":
email_ids = messages[0].split()
# 只处理最近3封
for e_id in email_ids[-3:]:
res, msg_data = mail.fetch(e_id, "(RFC822)")
for response_part in msg_data:
if isinstance(response_part, tuple):
msg = email.message_from_bytes(response_part[1])
# 解码主题
subject, encoding = decode_header(msg["Subject"])[0]
if isinstance(subject, bytes):
subject = subject.decode(encoding if encoding else "utf-8")
# 提取发件人
from_ = msg.get("From")
print(f"主题: {subject}")
print(f"发件人: {from_}")
# 获取正文(纯文本优先)
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type() == "text/plain":
body = part.get_payload(decode=True).decode("utf-8", errors="ignore")
break
else:
body = msg.get_payload(decode=True).decode("utf-8", errors="ignore")
print(f"正文前100字: {body[:100]}...")
print("-" * 40)
# 关闭连接
mail.logout()
关键细节:
- 使用
search(None, "UNSEEN")只抓取未读邮件,避免重复处理。 email.message_from_bytes()解析原始邮件数据。- 处理多部分邮件(含附件和HTML)时,需用
walk()遍历子部分。 - 记得
decode_header处理编码主题,否则可能出现乱码。
问题4:如何处理邮件附件?
解答:在上方代码遍历msg.walk()时,判断part.get_content_maintype() == 'application'或part.get_filename()不为空则视为附件,使用part.get_payload(decode=True)获得二进制数据,再保存到本地文件,示例:
if part.get_filename():
filename = decode_header(part.get_filename())[0][0]
if isinstance(filename, bytes):
filename = filename.decode("utf-8")
with open(filename, "wb") as f:
f.write(part.get_payload(decode=True))
常见问题解答(FAQ)
Q1:发送邮件时频繁报错“535 Authentication failed”,如何解决? A:确认两点:① 使用邮箱的授权码(不是登录密码);② 在邮箱设置中开启“SMTP服务”或“POP3/IMAP/SMTP服务”,多数邮箱(如QQ、163)需在网页端“设置”-“账户”中生成授权码。
Q2:接收邮件时,为什么有邮件解析不出正文?
A:可能原因是邮件是纯HTML多部分且无text/plain部分,或编码不是UTF-8,可尝试用get_content_type()判断,并回退到text/html再提取纯文本(用BeautifulSoup或正则去标签)。
Q3:脚本可以定时运行吗?
A:可以,在Windows系统使用“任务计划程序”调用python your_script.py;Linux/Mac使用crontab设定时间(如0 8 * * * python /path/script.py每天8点执行),商业应用建议用APScheduler库实现更灵活的任务调度。
Q4:如何确保邮件发送的并发稳定? A:对于大量邮件(如千封以上),务必控制并发数(建议用线程池限制为5-10个连接),并加入重试机制(如失败后等待10秒重试2次),重点:部分邮箱对单日发送量有上限(如免费QQ邮箱每天500封),需提前规划发送频率。
SEO优化与安全建议
安全注意事项
- 授权码保护:绝不可将授权码硬编码在公开仓库中,建议使用环境变量(
os.getenv("EMAIL_PASSWORD"))或配置文件(.env)。 - 连接加密:始终使用SSL/TLS端口(465或587),避免使用明文25端口,过滤**:避免发送大量垃圾邮件,否则IP会被邮箱服务器拉黑。
- 收件规则限制:接收脚本应设置超时(
mail.socket.setdefaulttimeout(10)),防止死循环。
代码性能优化
- 批量发送:可循环遍历收件人列表,但每次发送完加入
time.sleep(1)避免触发限速。 - 接收全量邮件:使用
search(None, "ALL")然后分页获取,每次只fetch 50封减少带宽消耗。 - 使用现代库:
yagmail(简化发送)和imbox(简化接收)适合快速开发,但官方库更适合需要深度自定义的场景。
搜索引擎排名提示包含核心关键词**:如“Python邮件自动化”、“smtp发送邮件教程”。
- 内链建设:在文章提到“SMTP协议”时链接到详细文档(如官方smtplib手册)。
- 结构化数据:使用H2/H3层级标题,配合
<ul>和<code>标签方便爬虫理解。 - 示例代码可复制:将代码段放在
<pre><code>中并提供简洁注释,提升用户停留时间。
通过本文的实战代码和最佳实践,您已掌握使用Python脚本操作邮件收发的核心技能,从单封发送到批量监控,从纯文本到多附件,按需组合这些模块,即可打造属于你自己的邮件自动化工作流。