
本文详解在 streamlit 应用中使用 plotly + kaleido 导出 png 时卡死的问题,提供基于异步超时控制的稳定解决方案,并附可直接运行的完整代码示例。
本文详解在 streamlit 应用中使用 plotly + kaleido 导出 png 时卡死的问题,提供基于异步超时控制的稳定解决方案,并附可直接运行的完整代码示例。
在 Streamlit 中调用 plotly.io.write_image() 导出 PNG 时出现“卡死”(无报错、无响应、 spinner 长时间挂起),是开发者常见痛点。根本原因在于:Kaleido 的底层 Chromium 渲染引擎在 Streamlit 的同步执行上下文中可能因资源竞争、事件循环阻塞或初始化延迟而陷入不可中断的等待状态——这与本地脚本中直接运行无问题的现象完全一致(本地环境无 Web 框架调度干扰)。
✅ 推荐解决方案:采用 asyncio.wait_for() 显式设置超时,并将 write_image 封装为协程调用。该方式不修改 Kaleido 行为,而是通过 Python 异步机制主动兜底,避免界面无限阻塞。
以下是经过验证的完整可运行代码(兼容 Streamlit ≥ 1.20 + Plotly ≥ 5.15 + Kaleido ≥ 0.4.0):
#!/usr/bin/env streamlit run
import plotly.graph_objects as go
import plotly.io as pio
import streamlit as st
import asyncio
import os
# 确保 Kaleido 引擎可用(可选诊断)
try:
pio.kaleido.scope.is_connected()
except Exception as e:
st.warning(f"Kaleido 连接异常: {e}. 请确认已安装 kaleido>=0.4.0")
async def export_plot_to_png(fig: go.Figure, filename: str) -> bool:
"""异步封装 write_image,支持超时控制"""
loop = asyncio.get_event_loop()
# 在线程池中执行阻塞 IO,避免阻塞主事件循环
try:
await loop.run_in_executor(None, pio.write_image, fig, filename, "png", "kaleido")
return True
except Exception as e:
st.error(f"导出失败: {type(e).__name__}: {e}")
return False
def main():
st.title("? Plotly PNG 导出演示(带超时保护)")
# 构建示例图表
fig = go.Figure(
data=go.Scatter(x=[1, 2, 3], y=[3, 2, 1], mode='markers+lines', name="Sample Data"),
layout=go.Layout(title="Streamlit + Plotly PNG Export", height=400)
)
st.plotly_chart(fig, use_container_width=True)
if st.button("导出为 PNG 并下载"):
filename = "plotly_export.png"
with st.spinner("正在渲染图像(最长等待 15 秒)..."):
# 设置 15 秒超时,避免永久卡死
try:
success = asyncio.run(asyncio.wait_for(
export_plot_to_png(fig, filename), timeout=15.0
))
if success and os.path.exists(filename):
st.success("✅ PNG 导出成功!")
st.image(filename, caption="导出的图像预览", use_column_width=True)
with open(filename, "rb") as f:
st.download_button(
label="⬇️ 下载 PNG 文件",
data=f,
file_name=filename,
mime="image/png",
use_container_width=True
)
else:
st.error("❌ 文件未生成,请检查磁盘权限或 Kaleido 状态。")
except asyncio.TimeoutError:
st.error("⏰ 导出超时!Kaleido 渲染可能因环境限制未响应。建议:\n- 确认系统已安装 Chrome/Chromium\n- 尝试升级 kaleido (`pip install --upgrade kaleido`) \n- 或改用 `to_image()` 内存导出(见下方备选方案)")
except Exception as e:
st.error(f"⚠️ 未知错误: {e}")
# 【备选方案】若仍失败,可改用内存导出(无需写文件,更轻量)
if st.checkbox("启用内存导出模式(绕过文件 I/O)"):
st.info("此模式直接生成字节流,避免磁盘操作,适合容器/无写入权限环境。")
if st.button("内存导出 PNG"):
try:
img_bytes = pio.to_image(fig, format="png", engine="kaleido", width=800, height=600)
st.download_button(
label="⬇️ 下载内存生成的 PNG",
data=img_bytes,
file_name="plotly_inline.png",
mime="image/png"
)
except Exception as e:
st.error(f"内存导出失败: {e}")
if __name__ == "__main__":
main()
? 关键注意事项与最佳实践:
-
环境依赖:Kaleido 依赖系统级 Chromium。Linux 容器需额外安装
libxshmfence1 libgbm1 libasound2;Mac 用户若用 M1/M2 芯片,务必使用kaleido>=0.4.0(修复 Apple Silicon 兼容性)。 - 超时值设定:15 秒适用于大多数图表;复杂三维图可增至 30 秒,但不建议超过 60 秒(影响用户体验)。
-
文件清理:生产环境建议在
download_button触发后自动删除临时文件(如os.remove(filename)),防止磁盘占用累积。 -
替代引擎:若 Kaleido 持续失败,可尝试
engine="orca"(需单独安装 Orca 服务),但 Orca 已停止维护,强烈推荐优先排查 Kaleido 环境。
通过异步封装 + 显式超时 + 内存回退三重保障,即可在 Streamlit 中实现健壮、用户友好的 Plotly PNG 导出功能。











