PyQt5 QtWebEngineView加载网页时Python崩溃、视频加载失败问题
运行环境信息
- 操作系统:Windows 10
- Python版本:3.9.6
- PyQt5版本:5.15.6
- PyQtWebEngine版本:5.15.5
- 所有依赖包均通过pip工具安装
故障表现
访问Twitter站点时存在两类异常:
- 页面内图片可正常加载,但视频无法加载,视频区域显示错误提示
- 仅能滚动浏览少量帖子,就会触发Python进程无任何报错直接崩溃:并非浏览器组件自行关闭,而是整个Python进程直接退出。最初在VS Code终端运行时未捕获到任何错误输出,切换到CMD终端运行后捕获到如下错误日志:
[1436:6100:0617/142809.070:ERROR:cache_util_win.cc(21)] Unable to move the cache: AccÞs refusÚ. (0x5) [1436:6100:0617/142809.084:ERROR:cache_util.cc(139)] Unable to move cache folder C:\Users\fabie\AppData\Local\python\QtWebEngine\Default\GPUCache to C:\Users\fabie\AppData\Local\python\QtWebEngine\Default\old_GPUCache_000 [1436:6100:0617/142809.096:ERROR:disk_cache.cc(184)] Unable to create cache [1436:6100:0617/142809.096:ERROR:shader_disk_cache.cc(606)] Shader Cache Creation failed: -2 [1436:6100:0617/142812.075:ERROR:service_worker_storage.cc(1575)] Failed to delete the database: Database IO error [1436:1208:0617/142837.652:ERROR:leveldb_factory.cc(91)] Failed to open LevelDB database from C:\Users\fabie\AppData\Local\python\QtWebEngine\Default\IndexedDB\https_twitter.com_0.indexeddb.leveldb,IO error: C:\Users\fabie\AppData\Local\python\QtWebEngine\Default\IndexedDB\https_twitter.com_0.indexeddb.leveldb/LOCK: File currently in use. (ChromeMethodBFE: 15::LockFile::2) [1436:1208:0617/142839.674:ERROR:leveldb_factory.cc(91)] Failed to open LevelDB database from C:\Users\fabie\AppData\Local\python\QtWebEngine\Default\IndexedDB\https_twitter.com_0.indexeddb.leveldb,IO error: C:\Users\fabie\AppData\Local\python\QtWebEngine\Default\IndexedDB\https_twitter.com_0.indexeddb.leveldb/LOCK: File currently in use. (ChromeMethodBFE: 15::LockFile::2)
视频加载错误效果如下:
初步排查线索
故障疑似与本地存储/localForage相关:项目中另外运行了一个QWebEngineView实例,用于加载TradingView提供的高级行情图表组件,该实例抛出如下JS警告/错误,与前述故障存在关联:
js: 2022-06-29T15:25:55.559Z:Common.LocalForage:IndexedDB is NOT available js: 2022-06-29T15:25:55.816Z:ChartApi.Core:Cannot get studies_metadata, reason: Error: No available storage method found. js: 2022-06-29T15:25:56.679Z:ChartApi.Core:Cannot update studies_metadata cache, reason: Error: No available storage method found.
复现代码
import sys from PyQt5.QtWidgets import * from PyQt5 import QtCore, QtGui, QtWebEngineWidgets class BrowserWindow(QMainWindow): def __init__(self, *args, **kwargs): super(BrowserWindow, self).__init__(*args, **kwargs) # ADD WINDOW ELEMENTS # ADD TAB WIGDETS TO DISPLAY WEB TABS self.tabs = QTabWidget() self.tabs.setDocumentMode(True) self.tabs.setTabsClosable(True) self.setCentralWidget(self.tabs) # ADD DOUBLE CLICK EVENT LISTENER self.tabs.tabBarDoubleClicked.connect(self.tab_open_doubleclick) # ADD TAB CLOSE EVENT LISTENER self.tabs.tabCloseRequested.connect(self.close_current_tab) # ADD ACTIVE TAB CHANGE EVENT LISTENER self.tabs.currentChanged.connect(self.current_tab_changed) # ADD NAVIGATION TOOLBAR navtb = QToolBar("Navigation") navtb.setIconSize(QtCore.QSize(16, 16)) self.addToolBar(navtb) # ADD BUTTONS TO NAVIGATION TOOLBAR # PREVIOUS WEB PAGE BUTTON back_btn = QAction(QtGui.QIcon('arrow back.png'), "Back", self) back_btn.setStatusTip("Back to previous page") navtb.addAction(back_btn) # NAVIGATE TO PREVIOUS PAGE back_btn.triggered.connect(lambda: self.tabs.currentWidget().back()) # NEXT WEB PAGE BUTTON next_btn = QAction(QtGui.QIcon('arrow next.png'), "Forward", self) next_btn.setStatusTip("Forward to next page") navtb.addAction(next_btn) # NAVIGATE TO NEXT WEB PAGE next_btn.triggered.connect(lambda: self.tabs.currentWidget().forward()) # REFRESH WEB PAGE BUTTON reload_btn = QAction(QtGui.QIcon('refresh.png'), "Reload", self) reload_btn.setStatusTip("Reload page") navtb.addAction(reload_btn) # RELOAD WEB PAGE reload_btn.triggered.connect(lambda: self.tabs.currentWidget().reload()) # HOME PAGE BUTTON home_btn = QAction(QtGui.QIcon('home.png'), "Home", self) home_btn.setStatusTip("Go home") navtb.addAction(home_btn) # NAVIGATE TO DEFAULT HOME PAGE home_btn.triggered.connect(self.navigate_home) # ADD SEPARATOR TO NAVIGATION BUTTONS navtb.addSeparator() # ADD LINE EDIT TO SHOW AND EDIT URLS self.urlbar = QLineEdit() navtb.addWidget(self.urlbar) # LOAD URL WHEN ENTER BUTTON IS PRESSED self.urlbar.returnPressed.connect(self.navigate_to_url) self.urlbar.mousePressEvent = lambda _ : self.urlbar.selectAll() # ADD STYLESHEET TO CUSTOMIZE YOUR WINDOWS self.setStyleSheet("""QWidget{ background-color: white; color: rgb(48, 48, 48); } QTabWidget::pane { /* The tab widget frame */ border-top: 2px solid rgb(255, 255, 255); position: absolute; color: rgb(90, 90, 90); padding: 5px; } QTabWidget::tab-bar { alignment: left; } /* Style the tab using the tab sub-control. Note that it reads QTabBar _not_ QTabWidget */ QLabel, QToolButton { background: rgb(255, 255, 255); border: 2px solid rgb(255, 255, 255); /*border-bottom-color: #C2C7CB; /* same as the pane color */ border-radius: 3px; min-width: 8ex; padding: 5px; margin-right: 2px; color: rgb(50, 50, 50); } QLabel:hover, QToolButton::hover { background: white; border: 2px solid rgb(0, 36, 36); background-color: white; } QTabBar::tab{ border-radius: 3px; min-width: 8ex; padding: 5px; margin-right: 2px; color: rgb(50, 50, 50); background:#e0e0e0; } QTabBar::tab:selected, QTabBar::tab:hover:selected{ background:white; background-color:white; border-bottom-width:0px; border-bottom-left-radius:0px; border-bottom-right-radius:0px; } QTabBar::tab:hover { background:#ededed; background-color:#ededed; border-color:#ededed; } QLineEdit { border: 2px solid rgb(0, 36, 36); border-radius: 3px; padding: 5px; background-color: white; color: rgb(50, 50, 50); } QLineEdit:hover { border: 2px solid rgb(0, 66, 124); } QLineEdit:focus{ border: 2px solid rgb(0, 136, 255); } QPushButton{ background: rgb(49, 49, 49); border: 2px solid rgb(0, 36, 36); background-color: rgb(0, 36, 36); padding: 5px; border-radius: 10px; }""") #label = Homepage self.add_new_tab(QtCore.QUrl('https://duckduckgo.com/'), 'Homepage') # SHOW MAIN WINDOW self.show() # ############################################ # FUNCTIONS ############################################## # ADD NEW WEB TAB def add_new_tab(self, qurl=None, label="Blank"): # Check if url value is blank if qurl is None: qurl = QtCore.QUrl('')#pass empty string to url # Load the passed url browser = QtWebEngineWidgets.QWebEngineView() browser.setUrl(qurl) # ADD THE WEB PAGE TAB i = self.tabs.addTab(browser, label) self.tabs.setCurrentIndex(i) # ADD BROWSER EVENT LISTENERS # On URL change browser.urlChanged.connect(lambda qurl, browser=browser: self.update_urlbar(qurl, browser)) # On loadfinished browser.loadFinished.connect(lambda _, i=i, browser=browser: self.tabs.setTabText(i, browser.page().title())) # ADD NEW TAB ON DOUBLE CLICK ON TABS def tab_open_doubleclick(self, i): if i == -1: # No tab under the click self.add_new_tab() # CLOSE TABS def close_current_tab(self, i): if self.tabs.count() < 2: #Only close if there is more than one tab open return self.tabs.removeTab(i) # UPDATE URL TEXT WHEN ACTIVE TAB IS CHANGED def update_urlbar(self, q, browser=None): #q = QURL if browser != self.tabs.currentWidget(): # If this signal is not from the current tab, ignore return self.urlbar.setText(q.toString()) self.urlbar.setCursorPosition(0) # ACTIVE TAB CHANGE ACTIONS def current_tab_changed(self, i): # i = tab index # GET CURRENT TAB URL qurl = self.tabs.currentWidget().url() # UPDATE URL TEXT self.update_urlbar(qurl, self.tabs.currentWidget()) # UPDATE WINDOWS TITTLE self.update_title(self.tabs.currentWidget()) # UPDATE WINDOWS TITTLE def update_title(self, browser): if browser != self.tabs.currentWidget(): # If this signal is not from the current ACTIVE tab, ignore return title = self.tabs.currentWidget().page().title() self.setWindowTitle(title) # NAVIGATE TO PASSED URL def navigate_to_url(self): # Does not receive the Url # GET URL TEXT q = QtCore.QUrl(self.urlbar.text()) if q.scheme() == "": q = QtCore.QUrl('https://www.google.com/search?q='+self.urlbar.text().replace(' ', '+')) self.tabs.currentWidget().setUrl(q) # NAVIGATE TO DEFAULT HOME PAGE def navigate_home(self): self.tabs.currentWidget().setUrl(QtCore.QUrl("https://duckduckgo.com/")) app = QApplication(sys.argv) w = BrowserWindow() app.exec_()
根因定位与解决方案
核心根因
- 多实例存储锁冲突:所有默认创建的QWebEngineView实例会共享同一个全局Default配置文件,共用同一份磁盘缓存、IndexedDB、GPUCache存储路径。同时运行浏览器标签页和TradingView组件两个QWebEngineView实例时,多个渲染进程并发读写同一份存储文件,触发LevelDB文件锁占用错误,直接导致IndexedDB不可用,页面本地存储读写失败。
- GPU缓存权限异常:PyQtWebEngine 5.15.x在Windows下默认存储路径位于用户AppData目录,普通权限下偶尔会出现缓存目录移动、创建失败的问题,GPU Shader缓存初始化失败会直接导致硬件视频解码异常,出现视频加载失败。
- 版本不匹配:当前PyQt5版本为5.15.6,PyQtWebEngine版本为5.15.5,小版本不一致会提升渲染进程崩溃概率。
- 存储文件锁冲突、缓存初始化失败会触发QtWebEngine渲染进程的未捕获异常,直接导致整个Python进程无报错退出。
修复方案
按优先级执行即可:
- 对齐依赖版本:执行命令将PyQtWebEngine版本升级到和PyQt5一致的5.15.6:
pip install --upgrade PyQtWebEngine==5.15.6 - 清理损坏缓存:手动删除目录
C:\Users\fabie\AppData\Local\python\QtWebEngine,重启程序后QtWebEngine会重建全新的缓存目录,可临时解决缓存损坏导致的启动报错。 - 为每个QWebEngine实例配置独立存储Profile:修改
add_new_tab方法以及创建TradingView实例的代码,不要使用默认共享的Default Profile,为每个实例分配独立的存储路径,彻底解决文件锁冲突。参考修改代码:
TradingView的QWebEngineView实例也需要用独立的Profile创建,不要用默认配置。def add_new_tab(self, qurl=None, label="Blank"): if qurl is None: qurl = QtCore.QUrl('') # 为每个标签页创建独立Profile,避免锁冲突 profile = QtWebEngineWidgets.QWebEngineProfile(f"browser_tab_{self.tabs.count()}", self) # 可自定义缓存路径到项目目录,避免系统目录权限问题 # profile.setPersistentStoragePath("./cache/tab_"+str(self.tabs.count())) # profile.setCachePath("./cache/tab_"+str(self.tabs.count())) browser = QtWebEngineWidgets.QWebEngineView() page = QtWebEngineWidgets.QWebEnginePage(profile, browser) browser.setPage(page) browser.setUrl(qurl) # 后续原有逻辑不变 i = self.tabs.addTab(browser, label) self.tabs.setCurrentIndex(i) browser.urlChanged.connect(lambda qurl, browser=browser: self.update_urlbar(qurl, browser)) browser.loadFinished.connect(lambda _, i=i, browser=browser: self.tabs.setTabText(i, browser.page().title())) - 无持久化需求可直接使用无痕模式:如果不需要保留登录状态、浏览记录、本地缓存,可以创建无持久化的离屏Profile,完全不读写磁盘存储,从根源上避免缓存和锁问题:
# 不传存储名即为无痕模式,不会写入磁盘数据 profile = QtWebEngineWidgets.QWebEngineProfile(self)
内容的提问来源于stack exchange,提问作者3V's
相关产品推荐
相关产品推荐

