SMTP登录失败主因是邮箱服务商禁用密码登录,需用授权码;邮件乱码因未设UTF-8编码及MIMEMultipart封装;定时发送应弃用time.sleep而用schedule或系统调度器;pandas.to_html需配置escape=False等参数防转义与样式缺失。

SMTP连接失败:账号密码对不上,或服务商禁用了密码登录
很多新手卡在第一步——smtplib.SMTP_SSL 报 SMTPAuthenticationError 或直接超时。根本原因不是代码写错了,而是邮箱服务商(比如 QQ、163、Gmail)早就不让用明文密码登录 SMTP 了,必须用「授权码」或「应用专用密码」。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- QQ 邮箱去「设置 → 账户 → POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务」开启 SMTP,并生成「POP3/SMTP服务」的授权码(不是登录密码)
- 163 邮箱同理,开启 SMTP 后获取「客户端授权码」,且必须用
smtp.163.com和端口465(别用25) - Gmail 要先开「两步验证」,再进「Google 账户 → 安全 → 应用专用密码」生成 16 位密码,用
smtp.gmail.com+587+smtplib.SMTP(非 SSL),再调starttls() - 测试连通性最简单的方法是先不发邮件,只做登录:
server.login("your@163.com", "your_app_password"),报错就停在这步排查
邮件内容乱码 / 附件打不开:email.mime 模块构造不完整
用 email.mime.text.MIMEText 发纯文本没问题,但加表格、中文标题、Excel 附件后,收件人看到一堆问号或附件损坏——这是没设对 charset,也没用 MIMEMultipart 统一包装。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 所有含中文的
MIMEText必须显式指定charset="utf-8":MIMEText(html_content, "html", "utf-8") - 只要带附件或混合类型(HTML + 附件),就必须用
MIMEMultipart("related")或"mixed"作根容器,再把正文和附件.attach()进去 - 附件要用
MIMEApplication(不是MIMEBase),且记得设add_header("Content-Disposition", "attachment", filename=...) - 别用
msg.as_string()直接发,它可能丢编码头;改用msg.as_bytes()传给sendmail()
定时发送总漏发:用 time.sleep() 做“定时”根本不靠谱
写个 while 循环加 time.sleep(86400) 看似每天跑一次,实际非常脆弱:程序崩溃、服务器重启、系统休眠都会导致中断,而且无法精确到某分钟触发。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 开发阶段用
schedule库最轻量:schedule.every().monday.at("09:00").do(send_weekly_report),配合一个死循环schedule.run_pending(); time.sleep(60) - 生产环境必须交给系统级调度器:
cron(Linux/macOS)或 Windows 任务计划程序;cron示例:0 9 * * 1 /usr/bin/python3 /path/to/report.py(每周一 9 点) - 避免在定时脚本里读相对路径文件(如
"data/report.xlsx"),一律用os.path.dirname(__file__)拼绝对路径 - 加日志:用
logging.basicConfig(filename="/var/log/report.log")记每次发送时间、成功与否,否则出问题根本不知道断在哪天
数据周报内容动态拼接:pandas.to_html() 的坑比想象中多
想把 DataFrame 直接转成 HTML 表格塞进邮件?df.to_html() 默认不带边框、无样式、中文列名还可能被转义,更别说空值显示为 NaN 这种体验极差的问题。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 加参数:
df.to_html(index=False, na_rep="-", escape=False, classes="table"),其中escape=False才能保留 HTML 标签(比如你用<b>高亮</b>) - 加内联样式比依赖外部 CSS 更稳:
df.to_html(..., table_id="weekly-table", render_links=True),再用<style>#weekly-table { border-collapse: collapse; }</style>包裹 - 如果周报要汇总多个表,别拼字符串,用
io.StringIO构造完整 HTML 文档结构,确保<html><body>闭合 - 敏感字段(如用户手机号)务必脱敏处理后再进表格,
df["phone"] = df["phone"].str[:3] + "****" + df["phone"].str[-4:]
真正难的不是写完能发邮件的脚本,而是让它连续三个月不掉链子:授权码过期没人通知、Excel 路径突然变更、周五下班前临时改报表格式、甚至邮箱服务商悄悄调整了 SMTP 策略。这些地方没有银弹,只能靠日志+监控+定期手动抽检。










