真正能稳定落地的方案是纯HTML/CSS实现的「伪图表」:用table+bgcolor模拟柱状图,所有内容内联、无外链、100%可渲染;matplotlib/PNG/base64方案因邮件客户端屏蔽图片或不支持data URI而不可靠。

直接用 Python 生成带图表的 HTML 邮件发给运维同事,关键不是“能不能画图”,而是“邮件客户端能不能显示图”——绝大多数企业邮箱(包括 Outlook、腾讯企业邮、Gmail)会屏蔽外部图片、拒绝执行 JS,matplotlib 或 plotly 生成的 PNG/SVG 如果走外链或 base64 嵌入,极大概率被拦截或显示为空白。真正能稳定落地的方案,是纯 HTML/CSS 实现的「伪图表」:用表格+背景色+内联样式模拟柱状图/饼图,所有内容都在一个 HTML 字符串里,无依赖、无外链、100% 可渲染。
为什么不能用 matplotlib + savefig 发 PNG 再嵌入?
看似合理,但实际踩坑密集:
-
smtplib发送时若把 PNG 作为image/png附件嵌入,Gmail 和 Outlook 会把它当“可疑富媒体”,降权进推广栏甚至直接过滤 - 用
base64编码后塞进<img src="data:image/png;base64,...">,部分客户端(尤其是 Outlook 桌面版)根本不解析 data URI - 即使显示出来,图片尺寸难控制、无响应式、无法随数据动态缩放,比如磁盘使用率从 95% → 98%,你得手动调 height 值
- HTML 邮件对 CSS 支持极弱:
flex、grid、@media全部失效,只能靠<table>和内联style
用 table + bgcolor 实现可伸缩柱状图(含 0% 边界处理)
核心逻辑:把百分比转成 <td> 的 height,再用两个嵌套 <tr> 分别表示「已用」和「剩余」区域,避免 0% 时空白 td 被渲染成默认高度(这是原始 demo 最常出错的地方)。
示例函数:
立即学习“Python免费学习笔记(深入)”;
def render_bar(percent, width=120, height=20, color="#4CAF50"):
# 强制 0% → height="0%",100% → height="100%"
used_height = f"{int(percent)}%" if 0 <= percent <= 100 else "0%"
empty_height = f"{100 - int(percent)}%" if 0 <= percent <= 100 else "100%"
return f'''
<table border="0" cellpadding="0" cellspacing="0" width="{width}" height="{height}">
<tr>
<td height="{used_height}" bgcolor="{color}" style="font-size:12px;color:white;text-align:center;line-height:{height}px;">{percent}%</td>
</tr>
<tr>
<td height="{empty_height}" bgcolor="#f1f1f1"></td>
</tr>
</table>
'''
调用:render_bar(87) 输出一个 120×20 像素、绿色占 87% 高度、右下角标 “87%” 的柱子,0% 时第二行占满、第一行 collapse 为 0 高度,不会留白。
拼接完整 HTML 邮件时必须绕开的 MIME 陷阱
用 yagmail 或 smtplib 发送前,HTML 字符串本身没问题,但容器结构错了就进垃圾箱:
- 别用
MIMEText(html_str, "html")单层发送 —— Gmail 会认为“没正文”,直接标记低可信度 - 必须用
MIMEMultipart("alternative")包一层,并附上纯文本备选(哪怕只写一行"请使用 HTML 邮件客户端查看") - 如果同时带附件(如导出的 CSV),根容器得是
MIMEMultipart("mixed"),HTML 部分再嵌套在"alternative"子部件里,顺序不能颠倒 - 中文标题必须用
Header(subject, "utf-8").encode()编码,否则收件箱显示乱码或被拒收
真实巡检场景下的最小可行 HTML 结构
服务器磁盘巡检邮件不需要炫技,只要三块信息:主机名、路径、使用率柱图。下面这个片段可直接插入 <body> 中,兼容所有主流邮箱客户端:
<table border="0" cellpadding="4" cellspacing="0" style="font-family:Arial,sans-serif;font-size:14px;">
<tr>
<th align="left">主机</th>
<th align="left">挂载点</th>
<th align="left">使用率</th>
</tr>
<tr>
<td>10.1.1.1</td>
<td>/</td>
<td>{render_bar(72)}</td>
</tr>
<tr>
<td>10.1.1.1</td>
<td>/opt/data01</td>
<td>{render_bar(94, color="#f44336")}</td>
</tr>
</table>
注意:render_bar 返回的是完整 HTML 片段,直接用 f-string 插入即可;红色阈值(>90%)单独传参改色,不用额外判断逻辑。
最易被忽略的一点:所有 <table> 必须带 cellpadding="0" cellspacing="0",否则 Outlook 会自动加白边,柱图之间出现难看缝隙。这不是样式问题,是 Outlook 渲染引擎的硬规则。



















