必须使用授权码而非登录密码进行身份认证,并根据邮箱服务商选择正确的SMTP服务器地址与加密端口(如QQ用smtp.qq.com:465、163用smtp.163.com:465、Gmail用smtp.gmail.com:465)。

用 smtplib 发邮件前必须处理的认证和端口问题
直接调用 smtplib.SMTP() 很容易报 SMTPAuthenticationError 或连接超时,根本原因是多数邮箱(如 QQ、163、Gmail)已禁用明文密码登录,必须用「授权码」而非账户密码,且 SMTP 端口不是默认的 25。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- QQ 邮箱:开启「SMTP 服务」后获取授权码,用
smtplib.SMTP_SSL("smtp.qq.com", 465)或smtplib.SMTP("smtp.qq.com", 587)+starttls() - 163 邮箱:授权码在「POP3/SMTP/IMAP」设置页生成,必须用
SMTP_SSL连接smtp.163.com:465 - Gmail:需开启「两步验证」再生成应用专用密码,用
SMTP_SSL("smtp.gmail.com", 465) - 别硬编码密码或授权码——改用环境变量:
os.getenv("EMAIL_PASSWORD")
用 schedule 库实现每日定时但不阻塞主线程
schedule 本身不启动后台线程,如果只写 schedule.every().day.at("09:00").do(send_report) 再直接 schedule.run_pending(),脚本会立刻退出,根本不会等到 09:00。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 加一个死循环 + 休眠:
while True: schedule.run_pending(); time.sleep(60) - 避免每秒轮询浪费 CPU ——
time.sleep(60)足够,因为最小调度精度是分钟级 - 若部署在 Linux,更推荐用
cron替代schedule,比如0 9 * * * /usr/bin/python3 /path/to/report.py - Windows 下可用任务计划程序,避免让 Python 进程长期运行
构造带表格的邮件正文时别用纯字符串拼接
用 "<tr><td>" + data + "</td></tr>" 拼 HTML 表格极易出错:特殊字符未转义、中文乱码、样式错位。更糟的是,部分邮箱客户端(如 Outlook)对内联 CSS 支持极差。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
email.mime.multipart.MIMEMultipart("alternative")区分纯文本与 HTML 版本 - HTML 部分用
df.to_html(index=False, escape=False, classes="table")(Pandas DataFrame)生成基础表格,再套一层简洁<style></style>块控制边框和字体大小 - 避免使用外部 CSS 文件或 JS —— 邮件客户端基本不加载
- 测试时务必用真实邮箱(如 Gmail、Outlook)查收,别只看本地浏览器渲染
日志和异常必须捕获并记录,否则定时失败完全无感知
脚本在后台跑,一旦 smtplib 连接失败、附件路径错误或数据源查询超时,进程静默退出,你第二天才发现报告没发出去。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 整个发送逻辑包在
try...except Exception as e:中,记录完整 traceback 到文件 - 关键步骤打日志:
logging.info("Report generated for %s", date.today())、logging.error("Email failed: %s", str(e)) - 发信失败时可触发备用通知(比如写入本地文件 + 发送企业微信 webhook),但不要依赖另一个网络请求链路
- 定期清理日志文件,避免磁盘占满 —— 用
RotatingFileHandler控制大小和数量
最常被忽略的是授权码过期和邮箱配额限制:QQ 邮箱每月最多发 500 封,163 是 200 封,超限后 SMTP 会返回 (554, b'Dynamic request denied') 这类非标准错误,得靠日志里具体响应码识别。


















