最直接方式是用 win32com.client 调用本地 outlook 发信,需 windows 系统且已安装 outlook 客户端;安装 pywin32 后必须运行 pywin32_postinstall.py -install 注册 com,确保 outlook 进程常驻,附件用绝对路径并校验存在,定时任务应使用系统级调度(如 windows 任务计划程序或 cron)而非 python 循环。

用 win32com.client 发 Outlook 邮件最直接,但仅限 Windows + 安装了 Outlook 桌面客户端
Windows 上调用本地 Outlook 应用发信是目前最稳定、附件支持最完整的方式。它不依赖 SMTP 配置或邮箱密码,直接复用你已登录的 Outlook 账户和签名。
常见错误现象:pywin32 安装后仍报 ModuleNotFoundError: No module named 'win32com' —— 这是因为没运行 python Scripts/pywin32_postinstall.py -install(安装后需手动注册 COM 支持);或者 Outlook 进程被杀掉/未启动,导致 Dispatch("Outlook.Application") 报 pywintypes.com_error。
实操建议:
- 用
pip install pywin32安装后,必须执行python Scripts/pywin32_postinstall.py -install(路径在 Python 安装目录下) - 确保 Outlook 已启动且处于前台或后台运行(最小化也行),不能只靠“快捷方式”启动后立即关闭
- 附件路径必须是绝对路径,相对路径在定时任务中大概率找不到文件
- 如果 Excel 是动态生成的,建议先用
pandas.to_excel()保存到固定路径,再作为附件传入
Excel 附件要带格式?别用 openpyxl 直接改,先另存为 .xlsx 再 attach
Outlook 对附件内容不校验,但对扩展名敏感。如果你用 openpyxl 加载一个 .xls 文件再 save(),默认会变成 .xlsx 格式但文件头仍是旧格式,Outlook 可能拒绝加载或提示“文件损坏”。
使用场景:每日导出报表,需保留表格边框、颜色、公式结果(非公式本身)——这时必须输出标准 .xlsx,且由 Excel 引擎真实渲染过。
实操建议:
- 用
pandas生成数据后,直接df.to_excel("report.xlsx", index=False),这是最稳妥的起点 - 如需样式(如列宽、背景色),用
openpyxl加载刚生成的 .xlsx,操作workbook后再save()覆盖原文件 - 切勿用
xlwt或xlsxwriter写 .xls —— Outlook 在新版中对 .xls 兼容性下降,容易触发安全拦截 - 发送前加一句
os.path.exists("report.xlsx")校验,避免附件为空
定时执行不能只靠 time.sleep(),Windows 用任务计划程序,Linux/macOS 用 cron
脚本里写 while True: send_mail(); time.sleep(86400) 看似简单,实际极易失败:Python 进程崩溃、系统休眠、远程桌面断开都会中断循环,且无法精确到每天固定时间(比如总比设定时间晚几分钟)。
性能与可靠性影响:任务计划程序/cron 是系统级调度,不受用户登录状态影响(可配置“不管用户是否登录都运行”),还能记录执行日志、失败重试。
实操建议:
- Windows:用“任务计划程序”新建基本任务,触发器设为“每天”,操作设为“启动程序”,程序填
python.exe,参数填脚本绝对路径,起始于填脚本所在目录 - Linux/macOS:用
crontab -e添加0 9 * * * /usr/bin/python3 /path/to/send_mail.py(每天 9 点执行) - 所有定时环境都需确认 Python 路径正确(
which python3)、依赖已安装在对应环境中(虚拟环境需激活或用完整路径调用) - 在脚本开头加日志写入,例如
with open("send_log.txt", "a") as f: f.write(f"{datetime.now()} sent\n"),方便排查是否真正触发
附件名含中文或空格?Outlook 会自动编码,但最好用 os.path.basename() 提取干净文件名
直接传 "C:/Users/张三/Documents/日报 2024-05-20.xlsx" 给 mail.Attachments.Add() 通常能发出去,但某些 Exchange 服务器会因 URL 编码问题截断文件名,收件人看到的是 "?? 2024-05-20.xlsx" 或乱码。
为什么这样做:Outlook 的 COM 接口内部对路径字符串做 ANSI 转换,Windows 用户名含中文时,当前代码页(CP936)和 Python 默认 UTF-8 不一致,导致 basename 解析错位。
实操建议:
- 统一用
os.path.basename(absolute_path)提取文件名,不要自己切字符串 - 避免在文件名里用
/ \ : * ? " |,这些是 Windows 非法字符,也会让os.path.basename()行为异常 - 如果必须动态命名,用
datetime.now().strftime("%Y%m%d")替代中文,例如f"report_{today}.xlsx" - 测试时用邮件规则把这类邮件自动移到指定文件夹,确认附件名显示是否正常
脚本跑通的关键不在发信逻辑本身,而在于路径全用绝对、Outlook 进程常驻、定时器脱离 Python 进程独立运行——这三个点漏掉任何一个,都可能让“每天自动发”变成“偶尔发一次”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











