如何在cefpython3中嵌入离线Plotly图表并实现单脚本双服务运行
问题根源
你当前代码无法同时运行的核心原因是app.run_server()为阻塞调用,启动后会独占主线程,导致后续的CEF消息循环cef.MessageLoop()无法执行,因此两个服务只能二选一。
解决方案1:多线程运行Dash服务(保留Dash完整交互能力)
将Dash服务放到独立的守护线程中启动,不阻塞主线程的CEF逻辑执行,修改后的完整代码如下:
from cefpython3 import cefpython as cef import platform import sys import threading #dash (plotly)--------------------- import dash from dash import dcc from dash import html import plotly.express as px import pandas as pd #--------------------------------- def main(): check_versions() sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error cef.Initialize() cef.CreateBrowserSync(url="http://127.0.0.1:8050/", window_title="Test") # 启动Dash服务到独立守护线程 dash_thread = threading.Thread(target=DashApp, daemon=True) dash_thread.start() cef.MessageLoop() cef.Shutdown() def check_versions(): ver = cef.GetVersion() print("[dashtest.py] CEF Python {ver}".format(ver=ver["version"])) print("[dashtest.py] Chromium {ver}".format(ver=ver["chrome_version"])) print("[dashtest.py] CEF {ver}".format(ver=ver["cef_version"])) print("[dashtest.py] Python {ver} {arch}".format( ver=platform.python_version(), arch=platform.architecture()[0])) assert cef.__version__ >= "57.0", "CEF Python v57.0+ required to run this" def DashApp(): app = dash.Dash() df = pd.DataFrame({ "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"], "Amount": [4, 1, 2, 2, 4, 5], "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"] }) fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group") app.layout = html.Div(children=[ html.H1(children='Hello Dash'), html.Div(children=''' Dash: A web application framework for your data. '''), dcc.Graph( id='example-graph', figure=fig ) ]) # 关闭use_reloader避免多线程冲突 app.run_server(debug=False, use_reloader=False) if __name__ == '__main__': main()
注意事项:app.run_server要加use_reloader=False参数,避免Flask的重载机制触发多线程冲突。
解决方案2:静态HTML直接加载(更适合纯离线场景)
如果不需要Dash的服务端交互能力,完全可以跳过启动本地服务的步骤,直接将Plotly图表生成静态HTML字符串交给CEF加载,性能更高、完全离线可用,无需占用本地端口,代码示例如下:
from cefpython3 import cefpython as cef import platform import sys import plotly.express as px import pandas as pd def main(): check_versions() sys.excepthook = cef.ExceptHook cef.Initialize() browser = cef.CreateBrowserSync(window_title="离线Plotly示例") # 生成Plotly图表 df = pd.DataFrame({ "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"], "Amount": [4, 1, 2, 2, 4, 5], "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"] }) fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group") # 转成完整HTML字符串 html_content = f""" <html> <head> <meta charset="utf-8"> <title>离线Plotly</title> </head> <body> <h1>Hello 离线Plotly</h1> {fig.to_html(full_html=False, include_plotlyjs='cdn')} <!-- 如果需要完全离线,把上面的include_plotlyjs参数改为本地plotly.min.js的路径即可 --> </body> </html> """ # 直接加载HTML内容,无需启动服务 browser.LoadString(html_content, "http://local/") cef.MessageLoop() cef.Shutdown() def check_versions(): ver = cef.GetVersion() print("[dashtest.py] CEF Python {ver}".format(ver=ver["version"])) print("[dashtest.py] Chromium {ver}".format(ver=ver["chrome_version"])) print("[dashtest.py] CEF {ver}".format(ver=ver["cef_version"])) print("[dashtest.py] Python {ver} {arch}".format( ver=platform.python_version(), arch=platform.architecture()[0])) assert cef.__version__ >= "57.0", "CEF Python v57.0+ required to run this" if __name__ == '__main__': main()
如果需要完全离线不依赖网络,只要提前下载plotly.min.js到本地,将include_plotlyjs参数替换为本地文件路径即可。
实时刷新实现说明
如果需要图表实时更新:
- 用方案1的话,可以在CEF和Dash之间通过接口请求、WebSocket等方式做数据同步,也可以直接在Dash侧做定时回调更新数据
- 用方案2的话,可以通过CEF的JS绑定功能,将Python侧的数据注入到前端JS,调用Plotly的JS接口更新图表,性能更优。
内容的提问来源于stack exchange,提问作者like2think
相关产品推荐
相关产品推荐

