1. 项目缘起:为什么我们需要自动发邮件?
在任何一个需要与外部系统或人员交互的自动化流程里,邮件通知都是一个绕不开的环节。想象一下,你写了一个爬虫脚本,每天凌晨定时抓取数据并生成报告,难道要自己每天早起手动把报告发出去吗?或者,你搭建了一个服务器监控系统,当CPU使用率超过90%时,难道要指望运维人员24小时盯着屏幕吗?显然不现实。这时候,一个能够自动、可靠地发送邮件的Python脚本,就成了连接自动化世界与现实通知的“信使”。
我最初接触这个需求,是在做一个数据备份的定时任务。脚本会在备份完成后,需要将成功或失败的结果通知给相关人员。手动操作不仅低效,而且容易遗忘。通过Python的smtplib和email库,我实现了这个功能,它稳定运行了数年,成为了我工具箱里的常备利器。今天,我就把这个从环境准备、原理剖析、代码实现到深度避坑的完整过程拆解给你。无论你是想给爬虫程序加个“完工通知”,还是为监控脚本添上“报警铃铛”,这篇详解都能让你直接“抄作业”。
2. 核心原理与协议:SMTP是如何工作的?
在动手写代码之前,我们必须先搞懂邮件是怎么发出去的。这涉及到SMTP协议。你可以把它理解为邮局系统。当你寄一封信时,你需要知道收件人地址,并把信投递到邮筒(或邮局)。SMTP服务器就是那个“邮局”。
SMTP(Simple Mail Transfer Protocol),简单邮件传输协议,是一组用于从源地址到目的地址传输邮件的规则。发送邮件的过程,本质上就是你的Python程序(作为SMTP客户端)与SMTP服务器进行一系列“对话”的过程。
这个过程通常分为几个步骤:
- 建立连接:你的程序连接到SMTP服务器的指定端口(通常是25或加密的465/587端口)。
- 握手与认证:服务器说“你好”,客户端回应“你好”,然后客户端需要提供用户名和密码(或授权码)来证明自己的身份。这就是登录邮箱账号的过程。
- 声明发件人与收件人:客户端告诉服务器:“这封信来自
sender@example.com,要寄给receiver@example.com”。 - 传输邮件内容:客户端将邮件的所有内容(包括正文、主题、附件等)按照一定的格式(由
email库帮我们构建)发送给服务器。 - 结束会话:客户端说“再见”,服务器回应,连接关闭。
你的Python脚本扮演的是“寄信人”的角色,而像QQ邮箱、163邮箱、Gmail或者你公司自己的邮件服务器,它们提供了“邮局”(SMTP服务器)的服务。你需要知道这个“邮局”的地址(SMTP服务器地址)、端口号以及你的“寄信凭证”(账号和密码/授权码)。
注意:现在绝大多数公开的邮件服务商(如QQ、163、Gmail)都要求使用SSL或TLS加密连接,并且普通密码登录通常已不可用,必须使用“授权码”。这是一个非常重要的安全实践,也是新手最容易卡住的第一道坎。
3. 环境准备与关键配置:获取你的“邮局通行证”
理论懂了,接下来是实战准备。你需要准备三样东西:一个发件邮箱、该邮箱的SMTP服务器信息、以及一个授权码(而非登录密码)。
我以最常用的QQ邮箱和163网易邮箱为例,说明如何获取这些信息。其他邮箱服务商的流程大同小异。
3.1 获取QQ邮箱的SMTP配置
- 登录QQ邮箱网页版。
- 点击顶部菜单栏的“设置”。
- 选择“账户”选项卡。
- 向下滚动,找到“POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务”这一栏。
- 你会看到“POP3/SMTP服务”和“IMAP/SMTP服务”。我们需要开启前者。点击后面的“开启”按钮。
- 根据提示,可能需要验证密保。验证成功后,系统会给你一个16位的授权码。请立即复制并妥善保存这个授权码,它只会显示一次!这就是你代码里要用的密码。
- 记下QQ邮箱的SMTP服务器地址:
smtp.qq.com,SSL加密端口为465,TLS加密端口为587。我们通常使用465端口。
3.2 获取163邮箱的SMTP配置
- 登录163邮箱网页版。
- 点击页面左上角的“设置” > “POP3/SMTP/IMAP”。
- 勾选“开启POP3服务”或“开启IMAP/SMTP服务”。同样,根据提示进行安全验证。
- 系统会给你一个授权码(注意,不是你的邮箱登录密码)。保存好。
- 记下163邮箱的SMTP服务器地址:
smtp.163.com,SSL端口465, TLS端口587。
为什么用授权码?这是为了安全。如果你直接在代码里写入邮箱登录密码,一旦代码泄露,你的邮箱就完全暴露了。而授权码可以单独为“第三方应用”生成,即使泄露,你也可以随时在邮箱设置中将其禁用,而不影响邮箱主密码的安全。
准备好这些信息后,我们得到如下关键配置字典(请替换为你自己的信息):
# 配置信息 (请替换为你的实际信息) mail_config = { “qq”: { “smtp_server”: “smtp.qq.com”, “smtp_port”: 465, # SSL端口 “sender_email”: “your_qq@qq.com”, # 你的QQ邮箱 “password”: “your_authorization_code_here”, # 你的QQ邮箱授权码,不是密码! }, “163”: { “smtp_server”: “smtp.163.com”, “smtp_port”: 465, “sender_email”: “your_163@163.com”, “password”: “your_163_authorization_code_here”, } } # 收件人列表,可以是多个 receiver_emails = [“receiver1@example.com”, “receiver2@example.com”]4. 基础实战:发送一封纯文本邮件
现在,我们开始编写第一个脚本。我们将使用smtplib库来处理SMTP连接和发送,使用email库来构建一封格式正确的邮件。
4.1 构建邮件内容(MIME格式)
邮件不是简单的字符串,它有一套复杂的MIME格式标准,用来定义主题、发件人、收件人、正文类型(纯文本、HTML)、附件等。email.mime.text模块下的MIMEText类可以帮助我们轻松创建一封文本邮件。
from email.mime.text import MIMEText from email.header import Header def create_text_email(sender, receivers, subject, content): “”” 创建一封纯文本邮件 :param sender: 发件人邮箱 :param receivers: 收件人邮箱列表 :param subject: 邮件主题 :param content: 邮件正文内容 :return: 构建好的MIMEText对象 “”” # 创建邮件对象,指定内容为纯文本(‘plain’),编码为utf-8以支持中文 message = MIMEText(content, ‘plain’, ‘utf-8’) # 设置邮件头部信息 message[‘From’] = Header(f”发件人昵称 <{sender}>“, ‘utf-8’) # 可以自定义发件人显示名称 message[‘To’] = Header(“, “.join(receivers), ‘utf-8’) # 多个收件人用逗号分隔 message[‘Subject’] = Header(subject, ‘utf-8’) # 主题 return message关键点解析:
MIMEText(content, ‘plain’, ‘utf-8’):‘plain’表示这是纯文本。如果你想发送HTML格式的邮件,只需将‘plain’改为‘html’,并将content变量赋值为HTML字符串即可。‘utf-8’编码确保了中文字符不会乱码。- 头部字段
From,To,Subject必须按照标准设置。Header类用于正确处理包含非ASCII字符(如中文)的头部信息。
4.2 建立SMTP连接并发送
有了邮件内容对象,接下来就是通过SMTP协议把它送出去。
import smtplib from email.mime.text import MIMEText from email.header import Header def send_email_smtp_ssl(config, receiver_emails, subject, content): “”” 使用SSL加密方式发送邮件(推荐,更简单) :param config: 包含smtp_server, smtp_port, sender_email, password的字典 :param receiver_emails: 收件人列表 :param subject: 邮件主题 :param content: 邮件正文 “”” # 1. 构建邮件 message = create_text_email(config[“sender_email”], receiver_emails, subject, content) try: # 2. 创建SMTP_SSL对象,建立安全连接 # 在创建对象时直接传入服务器和端口,它会自动进行SSL握手 with smtplib.SMTP_SSL(config[“smtp_server”], config[“smtp_port”]) as server: # 3. 登录邮箱账户 server.login(config[“sender_email”], config[“password”]) print(“SMTP服务器登录成功!”) # 4. 发送邮件 # sendmail 参数:发件人,收件人列表(字符串),邮件内容(as_string()方法将对象转为标准邮件文本) server.sendmail(config[“sender_email”], receiver_emails, message.as_string()) print(f”邮件发送成功!收件人:{‘, ‘.join(receiver_emails)}“) except smtplib.SMTPAuthenticationError: print(“错误:认证失败。请检查邮箱地址和授权码(不是密码!)是否正确。”) except smtplib.SMTPException as e: print(f”SMTP错误发生:{e}“) except Exception as e: print(f”其他错误:{e}“) # 使用示例 config = mail_config[“qq”] # 使用QQ邮箱配置 subject = “【Python自动化测试】纯文本邮件” content = “”” 这是一封来自Python自动化脚本的测试邮件。 此邮件用于验证SMTP发送功能是否正常。 当前时间:可以在这里用datetime模块插入时间。 “”” send_email_smtp_ssl(config, receiver_emails, subject, content)代码逻辑与避坑点:
SMTP_SSLvsSMTP:SMTP_SSL在连接建立时就进行SSL加密,适用于465端口,代码更简洁。如果你的服务器使用587端口(STARTTLS),则需要使用SMTP类,然后调用starttls()方法。代码略有不同,但逻辑相似。with上下文管理器:使用with语句可以确保连接在使用完毕后被正确关闭,即使发生异常也是如此。这是一个好习惯。login方法:这里填入的password必须是前面获取的授权码,而不是你的邮箱登录密码。90%的初次失败都是因为这里填错了。sendmail方法:注意第二个参数是收件人列表。message.as_string()是将我们构建好的MIME对象转换成符合SMTP协议的标准字符串格式。- 异常处理:我们特别捕获了
SMTPAuthenticationError,这是认证失败的专用异常,可以给用户更清晰的错误提示。其他SMTP相关异常用SMTPException捕获。
5. 功能进阶:发送HTML邮件与带附件的邮件
只会发纯文本邮件显然不够用。工作报告、数据报表、系统通知往往需要更丰富的格式和内容。
5.1 发送HTML格式邮件
发送HTML邮件非常简单,只需在创建MIMEText对象时,将第二个参数从‘plain’改为‘html’。
from email.mime.text import MIMEText def create_html_email(sender, receivers, subject, html_content): “”” 创建一封HTML格式邮件 :param html_content: HTML格式的字符串 “”” message = MIMEText(html_content, ‘html’, ‘utf-8’) message[‘From’] = Header(f”系统通知 <{sender}>“, ‘utf-8’) message[‘To’] = Header(“, “.join(receivers), ‘utf-8’) message[‘Subject’] = Header(subject, ‘utf-8’) return message # HTML内容示例 html_content = “”” <!DOCTYPE html> <html> <head> <meta charset=”utf-8"> <title>系统日报</title> <style> body {{ font-family: Arial, sans-serif; }} .header {{ color: #333366; border-bottom: 2px solid #333366; }} .metric {{ background-color: #f0f0f0; padding: 10px; margin: 5px 0; border-radius: 5px; }} .good {{ color: green; font-weight: bold; }} .warning {{ color: orange; font-weight: bold; }} </style> </head> <body> <h1 class=”header”>📊 每日系统监控报告</h1> <p>生成时间:2023-10-27 08:00:00</p> <div class=”metric”> <span>CPU平均使用率:</span><span class=”good”>65%</span> </div> <div class=”metric”> <span>内存使用率:</span><span class=”warning”>85%</span> </div> <div class=”metric”> <span>磁盘空间剩余:</span><span class=”good”>120GB</span> </div> <p>详情请登录<a href=”http://your-monitor-system.com”>监控平台</a>查看。</p> </body> </html> “””.format() # 这里可以用变量动态填充内容 # 发送函数和之前一样,只需传入html_content和调用create_html_email config = mail_config[“163”] subject_html = “【系统监控】HTML日报” send_email_smtp_ssl(config, receiver_emails, subject_html, html_content)实战心得:HTML邮件的美观度很大程度上取决于CSS的支持情况。不同的邮件客户端(如Outlook、Gmail、手机邮箱App)对CSS的支持差异巨大。为了最好的兼容性,建议使用内联样式(style=””属性),并尽量使用简单的表格布局。复杂的Flexbox或Grid布局可能在很多客户端中显示错乱。
5.2 发送带附件的邮件
发送附件需要用到email.mime.multipart模块中的MIMEMultipart类。它就像一个“容器”,可以把文本正文和多个附件“装”在一起。
from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.application import MIMEApplication import os def create_email_with_attachment(sender, receivers, subject, text_content, attachment_paths): “”” 创建带附件的邮件 :param text_content: 正文文本(可以是纯文本或HTML) :param attachment_paths: 附件文件路径列表 “”” # 创建一个混合类型的MIME对象,可以包含多种内容 message = MIMEMultipart() message[‘From’] = Header(f”数据报告 <{sender}>“, ‘utf-8’) message[‘To’] = Header(“, “.join(receivers), ‘utf-8’) message[‘Subject’] = Header(subject, ‘utf-8’) # 1. 添加邮件正文 # 这里我们添加一个纯文本正文,你也可以添加一个HTML正文,或者两者都加 text_part = MIMEText(text_content, ‘plain’, ‘utf-8’) message.attach(text_part) # 2. 循环添加所有附件 for file_path in attachment_paths: if not os.path.isfile(file_path): print(f”警告:附件文件不存在,已跳过。{file_path}“) continue # 获取文件名 file_name = os.path.basename(file_path) # 以二进制模式读取文件 with open(file_path, ‘rb’) as f: file_data = f.read() # 创建MIMEApplication对象来表示附件 # MIMEApplication默认用于二进制数据流 attachment = MIMEApplication(file_data, Name=file_name) # 设置附件的头部信息,告诉邮件客户端这是一个附件,并指定文件名 # RFC 2231 编码用于处理包含非ASCII字符的文件名 attachment.add_header(‘Content-Disposition’, ‘attachment’, filename=Header(file_name, ‘utf-8’).encode()) # 将附件添加到邮件容器中 message.attach(attachment) print(f”已添加附件:{file_name}“) return message # 使用示例 config = mail_config[“qq”] subject_attach = “【数据导出】带附件的报告” text_content_attach = “您好,\n\n本次数据导出的报告文件详见附件,请查收。\n\n此致\n自动化脚本” # 假设当前目录下有这两个文件 attachment_files = [“monthly_report.pdf”, “data_export.xlsx”] message_with_attachments = create_email_with_attachment( config[“sender_email”], receiver_emails, subject_attach, text_content_attach, attachment_files ) # 发送函数保持不变,传入构建好的message对象 try: with smtplib.SMTP_SSL(config[“smtp_server”], config[“smtp_port”]) as server: server.login(config[“sender_email”], config[“password”]) server.sendmail(config[“sender_email”], receiver_emails, message_with_attachments.as_string()) print(“带附件的邮件发送成功!”) except Exception as e: print(f”发送失败:{e}“)关键技术细节:
MIMEMultipart():创建了一个多部分邮件对象。‘mixed’是默认的子类型,表示包含独立部分(如正文和附件)。如果正文是HTML和纯文本的替代版本,可以使用‘alternative’子类型。attach()方法:用于将各个MIME部分(正文、附件)添加到主容器中。- 附件文件名编码:
Header(file_name, ‘utf-8’).encode()这行代码至关重要。它确保了包含中文等字符的文件名在邮件客户端中能正确显示,而不是变成一堆乱码。这是处理附件时的一个经典坑点。 - 文件存在性检查:在尝试打开文件前进行检查,可以避免因文件路径错误导致整个脚本崩溃。
6. 生产环境实战:封装、调度与异常处理
一个能在生产环境稳定运行的自动发邮件脚本,绝不仅仅是把上面的代码跑通就行。它需要健壮性、可配置性和可维护性。下面我分享几个在实际项目中积累的关键经验。
6.1 封装成可配置的邮件发送类
将邮件发送功能封装成一个类,便于管理配置和复用。
import smtplib import os from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication from email.header import Header from typing import List, Optional class EmailSender: “””一个健壮的邮件发送器类“”” def __init__(self, smtp_server: str, smtp_port: int, sender_email: str, password: str, use_ssl: bool = True): “”” 初始化邮件发送器 :param use_ssl: True表示使用SMTP_SSL (端口465),False表示使用SMTP+STARTTLS (端口587) “”” self.smtp_server = smtp_server self.smtp_port = smtp_port self.sender_email = sender_email self.password = password self.use_ssl = use_ssl self.server = None def _connect_and_login(self): “””建立连接并登录,内部方法“”” try: if self.use_ssl: self.server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port, timeout=30) else: self.server = smtplib.SMTP(self.smtp_server, self.smtp_port, timeout=30) self.server.starttls() # 升级为TLS加密连接 self.server.login(self.sender_email, self.password) print(f”已成功连接到 {self.smtp_server}:{self.smtp_port}“) return True except smtplib.SMTPAuthenticationError: print(“错误:SMTP认证失败。请检查邮箱和授权码。”) return False except (smtplib.SMTPException, ConnectionRefusedError, TimeoutError) as e: print(f”网络或SMTP连接错误:{e}“) return False def send(self, receivers: List[str], subject: str, content: str, content_type: str = ‘plain’, attachments: Optional[List[str]] = None) -> bool: “”” 发送邮件的主方法 :param receivers: 收件人列表 :param subject: 主题 :param content: 正文内容 :param content_type: ‘plain’ 或 ‘html’ :param attachments: 附件文件路径列表 :return: 发送成功返回True,失败返回False “”” if not receivers: print(“错误:收件人列表为空。”) return False # 1. 构建邮件 if attachments: message = self._create_multipart_message(receivers, subject, content, content_type, attachments) else: message = self._create_simple_message(receivers, subject, content, content_type) if not message: return False # 2. 连接并发送 if not self._connect_and_login(): return False try: self.server.sendmail(self.sender_email, receivers, message.as_string()) print(f”邮件发送成功至 {‘, ‘.join(receivers)}“) return True except smtplib.SMTPRecipientsRefused as e: print(f”错误:收件人被拒绝。详情:{e}“) return False except smtplib.SMTPSenderRefused as e: print(f”错误:发件人被拒绝。详情:{e}“) return False except smtplib.SMTPDataError as e: print(f”错误:服务器拒绝邮件数据。可能内容被判定为垃圾邮件。详情:{e}“) return False except Exception as e: print(f”发送过程中发生未知错误:{e}“) return False finally: if self.server: try: self.server.quit() except: pass self.server = None def _create_simple_message(self, receivers, subject, content, content_type): “””创建简单邮件(无附件)“”” try: message = MIMEText(content, content_type, ‘utf-8’) message[‘From’] = Header(f”自动发送系统 <{self.sender_email}>“, ‘utf-8’) message[‘To’] = Header(“, “.join(receivers), ‘utf-8’) message[‘Subject’] = Header(subject, ‘utf-8’) return message except Exception as e: print(f”构建邮件内容时出错:{e}“) return None def _create_multipart_message(self, receivers, subject, content, content_type, attachments): “””创建带附件的复杂邮件“”” try: message = MIMEMultipart() message[‘From’] = Header(f”自动发送系统 <{self.sender_email}>“, ‘utf-8’) message[‘To’] = Header(“, “.join(receivers), ‘utf-8’) message[‘Subject’] = Header(subject, ‘utf-8’) # 添加正文 text_part = MIMEText(content, content_type, ‘utf-8’) message.attach(text_part) # 添加附件 for file_path in attachments: if os.path.isfile(file_path): self._attach_file(message, file_path) else: print(f”警告:附件文件不存在,已跳过。{file_path}“) return message except Exception as e: print(f”构建带附件邮件时出错:{e}“) return None def _attach_file(self, message, file_path): “””将单个文件添加为附件“”” file_name = os.path.basename(file_path) with open(file_path, ‘rb’) as f: part = MIMEApplication(f.read(), Name=file_name) part.add_header(‘Content-Disposition’, ‘attachment’, filename=Header(file_name, ‘utf-8’).encode()) message.attach(part) # 使用示例 if __name__ == “__main__”: # 从配置文件或环境变量读取敏感信息,不要硬编码在代码里! sender = EmailSender( smtp_server=”smtp.qq.com”, smtp_port=465, sender_email=”your_qq@qq.com”, password=”your_auth_code”, # 建议从环境变量读取 use_ssl=True ) success = sender.send( receivers=[“colleague@company.com”], subject=”自动化任务完成通知”, content=”您的数据处理任务已于 $(date) 成功完成。”, # 实际使用时用datetime替换 content_type=‘plain’, attachments=[“output/result.csv”] ) if success: print(“主流程:邮件通知已发送。”) else: print(“主流程:邮件发送失败,需要记录日志或触发其他告警。”)这个类的设计考量:
- 分离构建与发送:
_create_*方法负责构建MIME对象,_connect_and_login和send负责网络操作,职责清晰。 - 详尽的异常处理:针对SMTP协议的各种特定异常(如收件人拒绝、发件人拒绝、数据错误)进行了分别捕获,能提供更精准的错误信息,便于排查。
- 资源管理:在
finally块中确保quit()被调用,释放服务器连接。 - 可扩展性:如果需要添加CC(抄送)、BCC(密送)功能,只需在构建消息时添加相应的头部字段即可。
6.2 与定时任务结合(Cron, APScheduler)
自动发送邮件通常意味着定时发送。在Linux服务器上,最经典的方式是使用Cron。
假设你的脚本保存为send_report.py,并且已经配置好了类EmailSender。
- 编写可执行的Python脚本:确保脚本开头有
#!/usr/bin/env python3,并赋予执行权限 (chmod +x send_report.py)。 - 使用Cron定时调度:
添加一行,例如,每天上午9点执行:# 打开当前用户的cron编辑模式 crontab -e0 9 * * * /usr/bin/python3 /path/to/your/send_report.py >> /path/to/log/email_sender.log 2>&10 9 * * *表示分钟(0),小时(9),每天。>> ...log 2>&1将脚本的标准输出和错误输出都重定向到日志文件,便于后期排查问题。
如果你需要在Python程序内部进行更复杂的调度(比如每隔一段时间,或者在某个条件触发后),可以使用APScheduler这样的库。
from apscheduler.schedulers.blocking import BlockingScheduler from datetime import datetime # 假设上面定义的 EmailSender 类可用 def job_function(): “””定时执行的任务“”” print(f”[{datetime.now()}] 开始执行定时邮件发送任务...”) sender = EmailSender(...) # 初始化你的发送器 # ... 准备邮件内容和附件 ... success = sender.send(...) if not success: # 发送失败,可以记录到更详细的日志,或触发其他告警(如发短信) print(f”[{datetime.now()}] 警告:邮件发送失败!”) if __name__ == “__main__”: scheduler = BlockingScheduler() # 每天上午10:30执行 scheduler.add_job(job_function, ‘cron’, hour=10, minute=30) # 或者每2小时执行一次 # scheduler.add_job(job_function, ‘interval’, hours=2) print(“定时邮件发送服务已启动,按 Ctrl+C 退出。”) try: scheduler.start() except (KeyboardInterrupt, SystemExit): print(“\n服务已停止。”)6.3 生产环境避坑大全
以下是我在多年运维自动化邮件通知系统中踩过的坑和总结的经验:
- 授权码过期或错误:这是最常见的问题。授权码可能过期(如QQ邮箱的授权码有时效),或者复制时包含了空格。错误信息通常是
SMTPAuthenticationError。定期检查并更新授权码。 - 被识别为垃圾邮件:
- 内容:避免使用过于营销化的词汇(如“免费”、“赢取”、“点击这里”),减少感叹号,正文中最好包含收件人的称呼。
- 频率:短时间内向大量地址发送相同内容,极易被判定为垃圾邮件。对于群发,考虑使用邮件群发服务(如SendGrid, Amazon SES)而非个人SMTP。
- 发件人信誉:长期使用一个固定发件邮箱,并保持良好发送记录,有助于提升信誉。
- 附件大小限制:大多数免费邮箱服务商对附件有大小限制(如QQ邮箱50MB,163邮箱50MB)。发送大文件前先检查,或考虑使用云存储链接代替附件。
- 连接超时与重试机制:网络不稳定可能导致连接超时。在生产代码中,应该为关键的网络操作(如
login,sendmail)添加重试逻辑。import time def send_with_retry(email_sender, max_retries=3): for i in range(max_retries): try: return email_sender.send(...) except (smtplib.SMTPException, TimeoutError) as e: if i == max_retries - 1: raise # 最后一次重试失败,抛出异常 wait_time = 2 ** i # 指数退避 print(f”发送失败,{wait_time}秒后重试 ({i+1}/{max_retries})。错误:{e}“) time.sleep(wait_time) - 编码问题:确保整个流程(脚本文件、字符串处理、邮件头)都使用UTF-8编码。特别是在处理中文文件名和主题时,
Header类是必须的。 - 密码等敏感信息存储:绝对不要将邮箱密码或授权码硬编码在脚本中!应该使用环境变量或配置文件,并在版本控制中忽略这些文件。
# 在shell中设置环境变量 export EMAIL_PASSWORD=‘your_auth_code_here’# 在Python中读取 import os password = os.environ.get(‘EMAIL_PASSWORD’) if not password: raise ValueError(“请设置 EMAIL_PASSWORD 环境变量”) - 日志记录:完善的日志对于排查问题至关重要。记录每次发送的时间、收件人、主题、成功与否、错误信息等。可以使用Python标准的
logging模块。
7. 场景扩展:在常见自动化项目中的应用
掌握了核心技能后,我们来看看如何将它嵌入到具体的自动化场景中,让脚本真正“活”起来。
场景一:爬虫任务完成通知你的爬虫在深夜运行,抓取了最新数据。你希望早上一起床就能在邮箱里看到结果摘要和可能遇到的错误。
# 伪代码示例 def run_spider_and_notify(): try: data, stats, errors = run_my_spider() # 分析结果,生成报告内容 if errors: content = f”爬虫运行完成,但遇到 {len(errors)} 个错误。\n详情:\n” + “\n”.join(errors) subject = “【爬虫警报】运行完成但有错误” else: content = f”爬虫成功运行!共抓取 {stats[‘count’]} 条数据。” subject = “【爬虫通知】任务成功完成” # 可以附加日志文件 attachments = [“spider.log”] if os.path.exists(“spider.log”) else None email_sender.send([“yourself@email.com”], subject, content, attachments=attachments) except Exception as e: # 爬虫本身崩溃了 email_sender.send([“yourself@email.com”], “【爬虫严重错误】脚本崩溃”, f”错误信息:{str(e)}“)场景二:服务器监控报警监控脚本定期检查服务器指标,一旦超过阈值就触发报警邮件。
import psutil # 需要安装 psutil 库 def check_server_and_alert(): cpu_percent = psutil.cpu_percent(interval=1) memory = psutil.virtual_memory() disk = psutil.disk_usage(‘/’) alerts = [] if cpu_percent > 80: alerts.append(f”CPU使用率过高:{cpu_percent}%“) if memory.percent > 90: alerts.append(f”内存使用率过高:{memory.percent}%“) if disk.percent > 85: alerts.append(f”磁盘空间不足:剩余 {disk.free / (1024**3):.2f} GB”) if alerts: subject = “【服务器监控警报】” content = “\n”.join([“检测到以下问题:”] + alerts + [“\n请及时处理!”]) # 可以发送给运维团队邮箱列表 email_sender.send([“ops-team@company.com”, “admin@company.com”], subject, content)场景三:自动化报表推送使用pandas处理数据,生成Excel或PDF报告,然后通过邮件定时推送给相关人员。
import pandas as pd from datetime import datetime, timedelta def generate_and_send_daily_report(): # 1. 从数据库或API获取数据 # df = pd.read_sql_query(“SELECT * FROM sales WHERE date = …”, connection) # 这里用模拟数据 df = pd.DataFrame({‘产品’: [‘A’, ‘B’, ‘C’], ‘销售额’: [1000, 1500, 800]}) # 2. 生成Excel文件 report_date = (datetime.now() - timedelta(days=1)).strftime(‘%Y-%m-%d’) excel_filename = f”sales_report_{report_date}.xlsx” df.to_excel(excel_filename, index=False) # 3. 生成邮件正文(HTML格式,更美观) html_content = f””” <h2>每日销售报表 ({report_date})</h2> <p>总销售额:<strong>{df[‘销售额’].sum()}</strong></p> <p>详情请查看附件Excel文件。</p> “”” # 4. 发送 email_sender.send( receivers=[“manager@company.com”, “sales@company.com”], subject=f”每日销售报表 - {report_date}“, content=html_content, content_type=‘html’, attachments=[excel_filename] ) # 5. 可选:发送后删除本地临时文件 os.remove(excel_filename)通过这些场景,你可以看到,自动发送邮件不是一个孤立的功能,而是一个能够将各种自动化任务的结果“告知”现实世界的桥梁。它的稳定性和可靠性,直接影响到整个自动化流程的闭环质量。花时间把邮件发送模块封装好、测试充分,在未来的各种项目中都会让你事半功倍。