PyQt6 WebEngineView无法加载本地Bokeh服务器嵌入图表问题
问题分析与解决方案
问题概述
开发PyQt6应用,支持用户选择CSV文件并指定列生成图表:
- 启动本地Bokeh服务器,将图表嵌入PyQt6的WebEngineView新窗口
- 本地Bokeh地址
http://localhost:6005/base无法在WebEngineView完成加载,不触发loadFinished信号;加载http://google.com正常 - 终端出现
'doh set to "" -- SystemOnly'提示,不确定是否相关 - 使用
show(p)时图表能在浏览器正常显示,需确认该方案可行性,以及是否应改用components实现(要求100%离线)
核心问题原因
- Bokeh文档更新逻辑错误:当前代码在PyQt主线程直接调用
modify_doc,而非在Bokeh服务器的回调上下文内执行,导致生成的图表未同步到服务器的文档实例,WebEngineView请求页面时服务器返回空文档,加载流程挂起。 - 信号连接时机滞后:设置URL之后才连接
loadStarted/loadFinished信号,可能错过事件触发节点。
两种实现方案
方案一:修复Bokeh服务器嵌入方案(可行,保留交互性)
若需要Bokeh的动态交互特性(如数据更新、工具操作),该方案可正常运行,只需修正以下几点:
代码修改要点
def create_bokeh_server(self): from bokeh.application import Application from bokeh.application.handlers.function import FunctionHandler from bokeh.server.server import Server from threading import Thread def bokeh_app(doc): # 初始化空文档,后续通过会话更新 doc.title = "CSV Plot" self.bokeh_doc = doc doc.add_root(figure(title="Waiting for data...", height=400, width=600)) handler = FunctionHandler(bokeh_app) bokeh_application = Application(handler) bokeh_server = Server({"/base": bokeh_application}, port=6005) bokeh_server.start() # 启动服务器后台线程,避免阻塞PyQt主线程 Thread(target=bokeh_server.io_loop.start, daemon=True).start() self.url = f"http://localhost:{bokeh_server.port}/base" print(f"Bokeh server running at: {self.url}") return bokeh_server def plot_data(self): from PyQt6.QtCore import QUrl if not self.file_path: self.label.setText("Please choose a CSV file first.") return column_name, ok = QInputDialog.getText(self, "Enter Column", "Enter column name:") if not ok: return self.column_name = column_name try: df = pd.read_csv(self.file_path) if self.column_name not in df.columns: self.label.setText(f"Column '{self.column_name}' not found in the CSV file.") return # 在Bokeh服务器IO循环中更新文档,确保上下文正确 def update_doc(): self.bokeh_doc.clear() plot_title = f"Plot of {self.column_name} from {self.file_path}" source = ColumnDataSource(df) x_values = [str(val) for val in df.index] p = figure( title=plot_title, x_range=x_values, height=400, width=600, toolbar_location="right", tools="pan,box_zoom,reset" ) p.line(x="index", y=self.column_name, source=source, line_width=2) self.bokeh_doc.add_root(p) self.bokeh_server.io_loop.add_callback(update_doc) # 先连接信号再设置URL,避免错过事件 self.bokeh_plot_window.web_view.loadStarted.connect(lambda: self.test("loadStarted")) self.bokeh_plot_window.web_view.loadProgress.connect(lambda: self.test("loadProgress")) self.bokeh_plot_window.web_view.loadFinished.connect(lambda: self.test("loadFinished")) self.bokeh_plot_window.web_view.setUrl(QUrl(self.url)) self.bokeh_plot_window.show() except Exception as e: import traceback traceback.print_exc() self.label.setText(f"Error plotting data: {str(e)}")
方案二:改用components实现(100%离线,推荐满足离线需求时使用)
若无需Bokeh服务器的动态交互,直接生成HTML/JS代码嵌入WebEngineView,完全无需启动本地服务器:
实现代码示例
def plot_data(self): from PyQt6.QtCore import QUrl from bokeh.embed import components if not self.file_path: self.label.setText("Please choose a CSV file first.") return column_name, ok = QInputDialog.getText(self, "Enter Column", "Enter column name:") if not ok: return self.column_name = column_name try: df = pd.read_csv(self.file_path) if self.column_name not in df.columns: self.label.setText(f"Column '{self.column_name}' not found in the CSV file.") return plot_title = f"Plot of {self.column_name} from {self.file_path}" source = ColumnDataSource(df) x_values = [str(val) for val in df.index] p = figure( title=plot_title, x_range=x_values, height=400, width=600, toolbar_location="right", tools="pan,box_zoom,reset" ) p.line(x="index", y=self.column_name, source=source, line_width=2) # 生成HTML组件 script, div = components(p) # 构建完整HTML,替换为本地资源实现离线 html = f""" <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="./bokeh.min.css" type="text/css" /> <script src="./bokeh.min.js"></script> {script} </head> <body> {div} </body> </html> """ # 加载HTML到WebEngineView self.bokeh_plot_window.web_view.setHtml(html) self.bokeh_plot_window.show() except Exception as e: import traceback traceback.print_exc() self.label.setText(f"Error plotting data: {str(e)}")
离线化步骤
- 从Bokeh官网下载对应版本的
bokeh.min.js和bokeh.min.css - 将文件放到应用目录下,替换HTML中的CDN链接为本地路径
关于'doh set to "" -- SystemOnly'提示
该提示是Qt WebEngine的DNS-over-HTTPS配置信息,与当前加载问题无关,可忽略。
内容的提问来源于stack exchange,提问作者Tristan D
相关产品推荐
相关产品推荐

