在指定的元素上window.open(),about:_blank 取消事先的链接

博客提及取消 src 的 about:_blank 事先链接的相关内容,与前端信息技术相关。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 src 的 about:_blank 取消事先的链接。

<script lang="ts" setup>

const openURL = ()=>{
    window.open("www.abc.jpg", 'frame', 'width=800px,height=800px')
}
</script>
<template>
<div name="frame">
 <iframe 
src="about:_blank" 
width="1000px" 
height="1000px" 
frameborder="1" 
scrolling="no" 
name="frame">112
</iframe>
</div>
    <el-button type="primary" :icon="Edit" 
@click="openURL">Edit Template</el-button>
</template>

import sys import json import os import warnings from datetime import datetime from PyQt5.QtCore import QUrl, Qt, QDateTime from PyQt5.QtWidgets import ( QApplication, QMainWindow, QLineEdit, QPushButton, QListWidget, QTabWidget, QVBoxLayout, QWidget, QHBoxLayout, QAction, QToolBar, QStatusBar, QProgressBar, QInputDialog, QFileDialog, QMessageBox ) from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage, QWebEngineSettings from PyQt5.QtNetwork import QNetworkProxy, QNetworkCookie # 修复代理设置导入问题 # 忽略弃用警告 warnings.filterwarnings("ignore", category=DeprecationWarning) class WebEnginePage(QWebEnginePage): def __init__(self, profile=None, parent=None): """修复参数传递问题:支持带profile和不带profile两种初始化方式""" if profile: super().__init__(profile, parent) # 使用profile初始化 else: super().__init__(parent) # 标准初始化 self.parent_window = parent.parent_window if parent and hasattr(parent, 'parent_window') else None def acceptNavigationRequest(self, url, _type, isMainFrame): # 检测视频流协议并调用播放器 if url.scheme() in ['rtmp', 'http-flv', 'ws-flv']: if self.parent_window: self.parent_window.play_with_jessibuca(url.toString()) return False return super().acceptNavigationRequest(url, _type, isMainFrame) def createWindow(self, type): # 在新标签页打开链接 if type == QWebEnginePage.WebBrowserTab: if self.parent_window: new_tab = self.parent_window.add_browser_tab("加载中...") return new_tab.page return super().createWindow(type) def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID): """修复:正确映射JavaScript控制台日志级别""" # 创建日志级别映射字典 level_map = { QWebEnginePage.InfoMessageLevel: "INFO", QWebEnginePage.WarningMessageLevel: "WARNING", QWebEnginePage.ErrorMessageLevel: "ERROR" } # 获取可读的日志级别名称 level_str = level_map.get(level, "UNKNOWN") log_msg = f"[JS {level_str}] {sourceID}:{lineNumber} - {message}" if self.parent_window: self.parent_window.status_bar.showMessage(log_msg, 5000) print(log_msg) # 同时输出到控制台 class WebEngineView(QWebEngineView): def __init__(self, parent=None): super().__init__(parent) self.parent_window = parent # 修复:正确传递profile参数 self.page = WebEnginePage(profile=self.parent_window.profile, parent=self) self.setPage(self.page) self.page.loadFinished.connect(self.inject_jessibuca) def inject_jessibuca(self): self.page.runJavaScript(""" if (typeof window.Jessibuca === 'undefined') { const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/jessibuca@latest/dist/jessibuca.js'; script.onerror = () => console.error('Jessibuca加载失败'); document.head.appendChild(script); const container = document.createElement('div'); container.id = 'jessibuca-container'; container.style.position = 'fixed'; container.style.zIndex = '99999'; container.style.top = '0'; container.style.left = '0'; container.style.width = '100%'; container.style.height = '100%'; container.style.backgroundColor = 'black'; container.style.display = 'none'; document.body.appendChild(container); } """) def createWindow(self, type): # 创建新标签页 if type == QWebEnginePage.WebBrowserTab: new_tab = WebEngineView(self.parent_window) # 修复:直接返回page属性而非调用page() return new_tab.page return super().createWindow(type) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("B站多账号浏览器") self.setMinimumSize(1200, 800) # 初始化数据存储 self.account_db = {} self.cookies_db = [] # 创建持久化Cookie配置 self.cookie_storage_path = "cookies_storage" os.makedirs(self.cookie_storage_path, exist_ok=True) self.profile = QWebEngineProfile("BiliCookieProfile", self) self.profile.setPersistentCookiesPolicy(QWebEngineProfile.ForcePersistentCookies) self.profile.setPersistentStoragePath(self.cookie_storage_path) # 关键性能优化1: 全局禁用网络代理[8](@ref) QNetworkProxy.setApplicationProxy(QNetworkProxy(QNetworkProxy.NoProxy)) # 关键性能优化2: 启用HTTP内存缓存[9](@ref) self.profile.setHttpCacheType(QWebEngineProfile.MemoryHttpCache) # 启用HTML5支持的设置 profile_settings = self.profile.settings() profile_settings.setAttribute(QWebEngineSettings.PlaybackRequiresUserGesture, False) profile_settings.setAttribute(QWebEngineSettings.FullScreenSupportEnabled, True) profile_settings.setAttribute(QWebEngineSettings.WebGLEnabled, True) profile_settings.setAttribute(QWebEngineSettings.Accelerated2dCanvasEnabled, True) profile_settings.setAttribute(QWebEngineSettings.PluginsEnabled, True) profile_settings.setAttribute(QWebEngineSettings.AllowRunningInsecureContent, True) # 主界面布局 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 导航工具栏 navbar = QToolBar("导航栏") navbar.setMovable(False) # 禁止工具栏拖动提升性能 self.addToolBar(navbar) back_btn = QAction("←", self) back_btn.triggered.connect(self.navigate_back) navbar.addAction(back_btn) forward_btn = QAction("→", self) forward_btn.triggered.connect(self.navigate_forward) navbar.addAction(forward_btn) reload_btn = QAction("↻", self) reload_btn.triggered.connect(self.reload_page) navbar.addAction(reload_btn) home_btn = QAction("🏠", self) home_btn.triggered.connect(self.navigate_home) navbar.addAction(home_btn) navbar.addSeparator() self.url_bar = QLineEdit("https://www.bilibili.com") self.url_bar.setPlaceholderText("输入网址或搜索内容...") self.url_bar.returnPressed.connect(self.load_url) navbar.addWidget(self.url_bar) # 浏览器标签页 self.browser_tabs = QTabWidget() self.browser_tabs.setTabsClosable(True) self.browser_tabs.tabCloseRequested.connect(self.close_tab) self.browser_tabs.currentChanged.connect(self.update_url_bar) # 关键性能优化3: 启用标签页滚动 self.browser_tabs.setUsesScrollButtons(True) main_layout.addWidget(self.browser_tabs, 8) # 添加初始标签页 self.add_browser_tab("首页", "https://www.bilibili.com") # 管理面板 self.tabs = QTabWidget() self.cookie_list = QListWidget() self.tabs.addTab(self.cookie_list, "Cookie列表") self.account_list = QListWidget() self.tabs.addTab(self.account_list, "账号切换") self.account_list.itemClicked.connect(self.switch_account) main_layout.addWidget(self.tabs, 2) # 功能按钮 btn_layout = QHBoxLayout() self.import_btn = QPushButton("导入Cookie") self.import_btn.clicked.connect(self.import_cookies) self.save_btn = QPushButton("保存账号") self.save_btn.clicked.connect(self.save_account) self.export_btn = QPushButton("导出Cookie") self.export_btn.clicked.connect(self.export_cookies) self.new_tab_btn = QPushButton("新建标签页") self.new_tab_btn.clicked.connect(self.create_new_tab) for btn in [self.import_btn, self.save_btn, self.export_btn, self.new_tab_btn]: btn_layout.addWidget(btn) main_layout.addLayout(btn_layout) # 状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.progress_bar = QProgressBar() self.progress_bar.setMaximum(100) self.progress_bar.setVisible(False) self.progress_bar.setFixedWidth(200) self.status_bar.addPermanentWidget(self.progress_bar) # 加载数据 self.load_accounts() def add_browser_tab(self, title, url=None): browser = WebEngineView(self) browser.titleChanged.connect(lambda title, b=browser: self.update_tab_title(b, title)) browser.loadProgress.connect(self.update_progress) browser.loadFinished.connect(self.on_load_finished) index = self.browser_tabs.addTab(browser, title) self.browser_tabs.setCurrentIndex(index) # 关键性能优化4: 延迟加载提升启动速度 if url: QApplication.processEvents() # 确保UI更新 browser.load(QUrl(url)) return browser def update_tab_title(self, browser, title): index = self.browser_tabs.indexOf(browser) if index != -1: self.browser_tabs.setTabText(index, title[:15] + "..." if len(title) > 15 else title) def update_progress(self, progress): self.progress_bar.setVisible(progress < 100) self.progress_bar.setValue(progress) def close_tab(self, index): """修复资源释放问题:移除多余的括号""" if self.browser_tabs.count() > 1: widget = self.browser_tabs.widget(index) # 关键修复:直接访问page属性而非调用page() page = widget.page if page: # 关键优化: 正确的资源释放顺序 profile = page.profile() profile.cookieStore().deleteAllCookies() # 先删除页面再删除视图 page.deleteLater() widget.deleteLater() self.browser_tabs.removeTab(index) else: self.create_new_tab() def create_new_tab(self): self.add_browser_tab("新标签页", "about:blank") def navigate_back(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.back() def navigate_forward(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.forward() def reload_page(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.reload() def navigate_home(self): self.load_url("https://www.bilibili.com") def on_load_finished(self, success): browser = self.sender() if browser and self.browser_tabs.currentWidget() == browser: current_url = browser.url().toString() self.url_bar.setText(current_url) self.status_bar.showMessage("页面加载完成" if success else "页面加载失败", 2000) # 关键优化: 登录页面自动处理 if "login" in current_url or "passport" in current_url: self.status_bar.showMessage("检测到登录页面,请完成登录", 5000) self.auto_handle_login_page(browser) def auto_handle_login_page(self, browser): """自动处理登录页面的JavaScript""" browser.page.runJavaScript(""" // 尝试自动填充已知的登录表单 const loginForm = document.querySelector('form[action*="login"]'); if (loginForm) { // 尝试填充测试账号 const usernameInput = loginForm.querySelector('input[name="username"], input[name="user"]'); const passwordInput = loginForm.querySelector('input[name="password"]'); if (usernameInput && passwordInput) { usernameInput.value = "test_account"; passwordInput.value = "test_password"; console.log("自动填充了登录表单"); } } """) def update_url_bar(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_url = current_browser.url().toString() self.url_bar.setText(current_url) # 登录页面特殊处理 if "login" in current_url or "passport" in current_url: self.url_bar.setStyleSheet("background-color: #FFF8E1;") else: self.url_bar.setStyleSheet("") def load_url(self, url_text=None): if url_text is None: url_text = self.url_bar.text().strip() if not url_text: return # 关键优化: 更智能的URL处理[10](@ref) if not url_text.startswith(("http://", "https://", "file://", "ftp://")): if "." in url_text: # 包含域名 url_text = "https://" + url_text else: # 可能是搜索内容 url_text = f"https://www.bilibili.com/search?keyword={url_text}" current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.load(QUrl(url_text)) self.status_bar.showMessage(f"正在加载: {url_text}", 3000) def play_with_jessibuca(self, stream_url): """使用Jessibuca播放视频流""" current_browser = self.browser_tabs.currentWidget() if not current_browser: return self.status_bar.showMessage("Jessibuca播放中...", 3000) current_browser.page.runJavaScript(f""" const container = document.getElementById('jessibuca-container'); if (container) {{ container.style.display = 'block'; if (!window.jessibucaPlayer) {{ window.jessibucaPlayer = new Jessibuca({{ container: container, videoBuffer: 0.2, isResize: true, text: '直播加载中...', decoder: 'ffmpeg.js', forceNoOffscreen: true }}); }} window.jessibucaPlayer.play('{stream_url}'); }} """) def import_cookies(self): """导入Cookie JSON文件""" file_path, _ = QFileDialog.getOpenFileName( self, "选择Cookie文件", "", "JSON文件 (*.json)" ) if file_path: try: with open(file_path, "r", encoding='utf-8') as f: cookies = json.load(f) if isinstance(cookies, list): self.cookies_db = cookies self.cookie_list.clear() self.cookie_list.addItems([f"{c['name']}: {c['value'][:10]}..." for c in cookies]) self.status_bar.showMessage(f"成功导入 {len(cookies)} 个Cookie", 3000) self.inject_cookies(cookies) else: QMessageBox.warning(self, "错误", "无效的Cookie格式") except Exception as e: QMessageBox.critical(self, "导入失败", f"错误: {str(e)}") def export_cookies(self): """导出Cookie到文件""" file_path, _ = QFileDialog.getSaveFileName( self, "保存Cookie文件", "", "JSON文件 (*.json)" ) if file_path: try: cookies = self.get_current_cookies() with open(file_path, "w", encoding='utf-8') as f: json.dump(cookies, f, indent=2, ensure_ascii=False) self.status_bar.showMessage("Cookie导出成功", 3000) except Exception as e: QMessageBox.critical(self, "导出失败", f"错误: {str(e)}") def get_current_cookies(self): """获取当前标签页的Cookie(模拟)""" # 实际实现需要异步获取,这里简化处理 return self.cookies_db if self.cookies_db else [] def inject_cookies(self, cookies): """将Cookie注入当前页面 - 修复登录问题[9](@ref)""" current_browser = self.browser_tabs.currentWidget() if current_browser: store = current_browser.page.profile().cookieStore() # 先清除现有Cookie store.deleteAllCookies() # 异步注入新的Cookie for cookie_data in cookies: # 创建Qt的Cookie对象 qt_cookie = QNetworkCookie( cookie_data['name'].encode('utf-8'), cookie_data['value'].encode('utf-8') ) # 设置Cookie属性 if 'domain' in cookie_data: qt_cookie.setDomain(cookie_data['domain']) if 'path' in cookie_data: qt_cookie.setPath(cookie_data['path']) if 'expiry' in cookie_data: # 转换为QDateTime expiry = QDateTime.fromSecsSinceEpoch(cookie_data['expiry']) qt_cookie.setExpirationDate(expiry) # 设置安全属性 qt_cookie.setSecure(cookie_data.get('secure', False)) qt_cookie.setHttpOnly(cookie_data.get('httpOnly', False)) # 注入Cookie store.setCookie(qt_cookie, QUrl(cookie_data.get('url', 'https://www.bilibili.com'))) self.status_bar.showMessage("Cookie注入成功,请刷新页面", 3000) def save_account(self): """保存当前账号配置""" account_name, ok = QInputDialog.getText( self, "保存账号", "输入账号名称:" ) if ok and account_name: cookies = self.get_current_cookies() if cookies: self.account_db[account_name] = { "cookies": cookies, "saved_date": datetime.now().isoformat() } self.account_list.addItem(account_name) # 持久化存储账号数据 try: with open("accounts.json", "w", encoding='utf-8') as f: json.dump(self.account_db, f, indent=2, ensure_ascii=False) QMessageBox.information(self, "成功", "账号保存成功") except Exception as e: QMessageBox.critical(self, "保存失败", f"账号保存失败: {str(e)}") else: QMessageBox.warning(self, "错误", "没有获取到有效Cookie") def switch_account(self, item): """切换账号""" account_name = item.text() if account_name in self.account_db: cookies = self.account_db[account_name].get("cookies", []) self.inject_cookies(cookies) self.status_bar.showMessage(f"已切换至账号: {account_name}", 3000) # 自动刷新当前页面 self.reload_page() else: QMessageBox.warning(self, "错误", "找不到该账号的Cookie信息") def load_accounts(self): """从文件加载保存的账号""" try: if os.path.exists("accounts.json"): with open("accounts.json", "r", encoding='utf-8') as f: self.account_db = json.load(f) self.account_list.addItems(self.account_db.keys()) except Exception as e: print(f"加载账号失败: {str(e)}") def closeEvent(self, event): """窗口关闭时释放全局资源[8](@ref)""" # 清理HTTP缓存和访问记录 self.profile.clearHttpCache() self.profile.clearAllVisitedLinks() # 保存账号数据 try: with open("accounts.json", "w", encoding='utf-8') as f: json.dump(self.account_db, f, indent=2, ensure_ascii=False) except Exception as e: print(f"保存账号失败: {str(e)}") super().closeEvent(event) if __name__ == "__main__": # 关键性能优化: 启用WebEngine调试日志[9](@ref) os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--enable-logging" app = QApplication(sys.argv) # 关键性能优化: 启用Qt内置的OpenGL渲染[7](@ref) app.setAttribute(Qt.AA_UseOpenGLES) window = MainWindow() window.show() sys.exit(app.exec_())
最新发布
07-09
<think>我们计划开发一个基于PyQt5的浏览器应用,用于管理B站账号的Cookie登录和视频流播放。主要功能包括: 1. 多账号Cookie管理:支持添加、删除、切换B站账号,保存和加载Cookie 2. 内置浏览器:支持使用Cookie登录B站,并保持登录状态 3. 视频播放:集成B站视频播放功能 技术选型: - 界面开发:PyQt5(包含QtWebEngine用于网页浏览和视频播放) - 网络请求:requests(用于测试Cookie有效性等后台操作) - Cookie管理:使用sqlite3数据库或JSON文件存储多个账号的Cookie 步骤: 1. 设计主界面 - 账号管理区域:列表显示已保存的账号,提供添加、删除、切换按钮 - 浏览器区域:使用QWebEngineView显示网页内容 - 视频播放区域:可以是一个独立的QWebEngineView用于播放视频(或者使用B站提供的播放器页面) 2. 实现Cookie管理 - 添加账号:弹出对话框让用户输入账号名称和Cookie(或者通过内置浏览器登录后自动获取) - 保存Cookie:将账号信息(包括Cookie)保存到本地数据库(或文件) - 加载Cookie:当用户选择一个账号时,将对应的Cookie设置到浏览器中 3. 内置浏览器集成 - 使用QWebEngineView作为浏览器组件 - 当用户通过内置浏览器登录B站时,我们可以捕获登录后的Cookie并保存 4. 视频播放功能 - 方案1:在浏览器中打开B站视频页面,使用B站网页播放器 - 方案2:解析视频流地址,使用本地播放器(如vlc)播放(但B站视频流有加密,且需要处理鉴权,较复杂) 考虑到B站视频播放的复杂性,我们选择方案1,即使用内置浏览器打开视频页面进行播放。 5. 自动登录 - 当用户切换账号时,自动将保存的Cookie注入到浏览器中,然后跳转到B站首页(或用户空间)以检查登录状态。 实现细节: 1. 使用QWebEngineProfile来管理每个账号的独立Cookie存储(或者每次切换账号时清除当前Cookie并设置新Cookie) 2. 使用QWebEngineCookieStore来操作Cookie 代码结构: - main.py: 主程序入口 - main_window.py: 主窗口类 - account_manager.py: 账号管理类(负责Cookie的存储和读取) - browser.py: 浏览器组件封装 步骤示例: 1. 创建主窗口,包含账号列表和浏览器视图 2. 实现账号添加功能:通过一个按钮触发,弹出对话框输入账号信息(或通过内置浏览器登录后捕获Cookie) 3. 保存账号信息(账号名,Cookie) 4. 切换账号时,将保存的Cookie设置到浏览器的CookieStore中 注意:由于B站的安全机制,直接使用Cookie登录可能存在风险,且Cookie会过期,需要重新登录。 具体实现: 1. 主窗口设计: ```python import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QSplitter, QListWidget, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QLabel from PyQt5.QtWebEngineWidgets import QWebEngineView from PyQt5.QtCore import QUrl, QTimer class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("B站多账号浏览器") self.resize(1200, 800) # 账号管理区域 self.account_list = QListWidget() self.account_list.itemClicked.connect(self.switch_account) self.account_name_input = QLineEdit() self.account_name_input.setPlaceholderText("输入账号名称") self.add_account_btn = QPushButton("添加账号") self.add_account_btn.clicked.connect(self.add_account) self.del_account_btn = QPushButton("删除账号") self.del_account_btn.clicked.connect(self.del_account) account_management_layout = QVBoxLayout() account_management_layout.addWidget(QLabel("账号管理")) account_management_layout.addWidget(self.account_name_input) account_management_layout.addWidget(self.add_account_btn) account_management_layout.addWidget(self.del_account_btn) account_management_layout.addWidget(self.account_list) account_management_widget = QWidget() account_management_widget.setLayout(account_management_layout) # 浏览器区域 self.browser = QWebEngineView() self.browser.load(QUrl("https://www.bilibili.com")) # 主布局 splitter = QSplitter() splitter.addWidget(account_management_widget) splitter.addWidget(self.browser) splitter.setSizes([200, 1000]) self.setCentralWidget(splitter) # 账号管理器 self.account_manager = AccountManager() # 加载已有账号 self.load_accounts() def load_accounts(self): """从存储中加载账号列表""" accounts = self.account_manager.get_accounts() self.account_list.clear() for account in accounts: self.account_list.addItem(account['name']) def add_account(self): """添加账号""" account_name = self.account_name_input.text().strip() if not account_name: return # 这里我们假设通过内置浏览器登录后,手动触发保存Cookie(后续改进:自动捕获登录后的Cookie) # 目前我们先保存账号名称,然后让用户手动登录,再通过捕获浏览器Cookie来保存 # 暂时先保存一个空Cookie,等登录后更新 self.account_manager.add_account(account_name, {}) self.load_accounts() # 切换到新账号,并跳转到登录页面 # 这里我们清空浏览器,然后跳转到登录页,登录后捕获Cookie self.browser.load(QUrl("https://passport.bilibili.com/login")) # 我们需要监听浏览器Cookie的变化,在登录成功后保存Cookie(后续实现) def del_account(self): """删除当前选中账号""" current_item = self.account_list.currentItem() if not current_item: return account_name = current_item.text() self.account_manager.del_account(account_name) self.load_accounts() def switch_account(self, item): """切换账号""" account_name = item.text() # 获取该账号的Cookie account = self.account_manager.get_account(account_name) # 将Cookie设置到浏览器 self.set_cookies(account['cookies']) def set_cookies(self, cookies): """将Cookie设置到浏览器""" # 首先清除所有现有Cookie cookie_store = self.browser.page().profile().cookieStore() cookie_store.deleteAllCookies() # 设置新的Cookie for name, value in cookies.items(): # 构造一个QNetworkCookie对象 from PyQt5.QtNetwork import QNetworkCookie cookie = QNetworkCookie(name.encode(), value.encode()) cookie.setDomain(".bilibili.com") cookie.setPath("/") cookie_store.setCookie(cookie) # 重新加载B站首页以应用Cookie self.browser.load(QUrl("https://www.bilibili.com")) ``` 2. 账号管理类(AccountManager)使用JSON文件存储账号信息: ```python import json import os class AccountManager: def __init__(self, filename='accounts.json'): self.filename = filename def get_accounts(self): if not os.path.exists(self.filename): return [] with open(self.filename, 'r', encoding='utf-8') as f: try: accounts = json.load(f) except: accounts = [] return accounts def add_account(self, name, cookies): accounts = self.get_accounts() # 如果账号已存在,则更新 for account in accounts: if account['name'] == name: account['cookies'] = cookies break else: accounts.append({'name': name, 'cookies': cookies}) with open(self.filename, 'w', encoding='utf-8') as f: json.dump(accounts, f, ensure_ascii=False, indent=4) def del_account(self, name): accounts = self.get_accounts() accounts = [account for account in accounts if account['name'] != name] with open(self.filename, 'w', encoding='utf-8') as f: json.dump(accounts, f, ensure_ascii=False, indent=4) def get_account(self, name): accounts = self.get_accounts() for account in accounts: if account['name'] == name: return account return None ``` 3. 捕获登录后的Cookie: 我们需要在用户登录成功后自动捕获Cookie并保存。我们可以监听浏览器的Cookie添加事件。 修改MainWindow,在初始化浏览器后设置CookieStore的信号: ```python def __init__(self): # ... 其他初始化代码 ... # 连接CookieStore的信号 self.browser.page().profile().cookieStore().cookieAdded.connect(self.on_cookie_added) # 当前正在添加的账号(用于在登录过程中记录账号名) self.current_adding_account = None def add_account(self): account_name = self.account_name_input.text().strip() if not account_name: return # 设置当前正在添加的账号 self.current_adding_account = account_name # 先保存一个空Cookie self.account_manager.add_account(account_name, {}) self.load_accounts() # 跳转到登录页面 self.browser.load(QUrl("https://passport.bilibili.com/login")) def on_cookie_added(self, cookie): """当有新的Cookie被添加时,如果当前正在添加账号,则捕获并保存""" if self.current_adding_account: # 获取当前账号的Cookie,并更新 account = self.account_manager.get_account(self.current_adding_account) cookies = account['cookies'] # 这是一个字典 # 将新添加的Cookie加入字典 cookie_name = cookie.name().data().decode() cookie_value = cookie.value().data().decode() cookies[cookie_name] = cookie_value # 检查是否已经包含了登录成功的标志(例如SESSDATA) if 'SESSDATA' in cookies: # 保存更新后的Cookie self.account_manager.add_account(self.current_adding_account, cookies) # 重置当前正在添加的账号 self.current_adding_account = None print(f"账号 {account_name} 的Cookie已保存") ``` 注意:上述方法会捕获所有Cookie,包括非bilibili的,但我们只关心.bilibili.com域下的。另外,我们以SESSDATA的出现作为登录成功的标志(因为这是B站登录后的关键Cookie)。 但是,这种方法可能会在登录过程中多次触发保存,我们可以在捕获到SESSDATA后停止捕获(将current_adding_account置为None)。 4. 视频播放功能: 我们不需要单独实现,因为内置浏览器打开视频页面(如https://www.bilibili.com/video/BV1xx411c7BF)就会自动播放。 5. 优化: - 可以添加一个状态栏显示当前账号 - 可以添加一个刷新账号列表的按钮 - 可以添加一个检查Cookie有效性的功能(通过访问用户空间API) 6. 测试: 由于B站登录可能需要验证码,所以自动捕获Cookie的功能在验证码出现时可能无法自动完成,需要用户手动输入。 7. 多账号独立存储: 上述代码中,每个账号的Cookie是独立保存的,切换账号时清除当前浏览器的Cookie并设置新账号的Cookie。 8. 注意事项: - 保存的Cookie包含敏感信息,建议对文件进行加密(可选) - 由于B站的安全机制,Cookie可能会过期,需要重新登录 这是一个基础版本,后续可以扩展功能,如: - 多标签浏览 - 视频下载 - 账号状态监控 运行主程序: ```python if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) ``` 由于代码较长,以上是一个简化版本,实际开发中需要根据测试进行调整。 注意:PyQt5的QWebEngineCookieStore在不同线程中操作,需要注意线程安全。 以上代码为示例,可能需要根据实际情况调试。 参考文献: [^1]: 关于PyQt5的WebEngineView使用,参考官方文档 [^2]: 关于Cookie的操作,参考Qt文档 [^4]: 关于B站登录,参考B站登录流程 相关问题: 1. 如何加密存储Cookie文件? 2. 如何自动检测Cookie是否过期? 3. 如何实现多标签浏览? 4. 如何实现视频下载功能?</think># 基于PyQt5的B站多账号浏览器应用 我将设计一个完整的Python应用,使用PyQt5实现支持多账号Cookie管理和视频播放的B站浏览器。这个应用将包含账号管理、Cookie保存/加载、内置浏览器和视频播放功能。 ## 系统设计 ### 核心功能 1. **多账号Cookie管理**:添加、删除、切换B站账号 2. **Cookie持久化**:安全存储Cookie信息 3. **内置浏览器**:集成WebEngineView显示B站内容 4. **视频播放**:支持B站视频播放功能 5. **账号状态检测**:验证Cookie有效性 ### 技术栈 - **PyQt5**:主界面和浏览器组件 - **QtWebEngine**:内置浏览器引擎 - **SQLite**:本地存储账号信息 - **Requests**:后台验证Cookie有效性 ## 完整实现代码 ```python import sys import json import sqlite3 import requests from PyQt5.QtWidgets import (QApplication, QMainWindow, QSplitter, QListWidget, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QLabel, QMessageBox, QStatusBar) from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile from PyQt5.QtCore import QUrl, Qt, QTimer from PyQt5.QtNetwork import QNetworkCookie class AccountManager: """账号管理类,处理账号的存储和加载""" def __init__(self, db_name='bilibili_accounts.db'): self.conn = sqlite3.connect(db_name) self.cursor = self.conn.cursor() self._create_table() def _create_table(self): self.cursor.execute(''' CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, cookies TEXT NOT NULL ) ''') self.conn.commit() def add_account(self, name, cookies): """添加新账号""" try: self.cursor.execute( "INSERT INTO accounts (name, cookies) VALUES (?, ?)", (name, json.dumps(cookies)) ) self.conn.commit() return True except sqlite3.IntegrityError: return False def get_accounts(self): """获取所有账号""" self.cursor.execute("SELECT id, name, cookies FROM accounts") return [ {'id': row[0], 'name': row[1], 'cookies': json.loads(row[2])} for row in self.cursor.fetchall() ] def delete_account(self, account_id): """删除账号""" self.cursor.execute("DELETE FROM accounts WHERE id=?", (account_id,)) self.conn.commit() def __del__(self): self.conn.close() class BilibiliBrowser(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("B站多账号浏览器") self.resize(1200, 800) # 初始化账号管理器 self.account_manager = AccountManager() # 创建UI self.init_ui() # 加载账号列表 self.load_accounts() # 设置状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("就绪", 3000) def init_ui(self): """初始化用户界面""" # 主分割布局 main_splitter = QSplitter(Qt.Horizontal) # 左侧账号管理区域 account_widget = QWidget() account_layout = QVBoxLayout() # 账号列表 account_layout.addWidget(QLabel("账号列表")) self.account_list = QListWidget() self.account_list.itemClicked.connect(self.switch_account) account_layout.addWidget(self.account_list) # 添加账号区域 add_account_layout = QHBoxLayout() self.account_name_input = QLineEdit() self.account_name_input.setPlaceholderText("输入账号名称") add_account_layout.addWidget(self.account_name_input) add_btn = QPushButton("添加账号") add_btn.clicked.connect(self.add_account) add_account_layout.addWidget(add_btn) account_layout.addLayout(add_account_layout) # 删除账号按钮 del_btn = QPushButton("删除选中账号") del_btn.clicked.connect(self.delete_account) account_layout.addWidget(del_btn) # 检测账号状态按钮 check_btn = QPushButton("检测账号状态") check_btn.clicked.connect(self.check_account_status) account_layout.addWidget(check_btn) account_widget.setLayout(account_layout) # 右侧浏览器区域 browser_widget = QWidget() browser_layout = QVBoxLayout() # 导航栏 nav_layout = QHBoxLayout() self.url_bar = QLineEdit() self.url_bar.setPlaceholderText("输入网址或搜索内容") self.url_bar.returnPressed.connect(self.navigate_to_url) nav_layout.addWidget(self.url_bar) home_btn = QPushButton("首页") home_btn.clicked.connect(self.go_home) nav_layout.addWidget(home_btn) refresh_btn = QPushButton("刷新") refresh_btn.clicked.connect(self.refresh_page) nav_layout.addWidget(refresh_btn) browser_layout.addLayout(nav_layout) # 浏览器视图 self.browser = QWebEngineView() self.browser.load(QUrl("https://www.bilibili.com")) self.browser.urlChanged.connect(self.update_url_bar) browser_layout.addWidget(self.browser) browser_widget.setLayout(browser_layout) # 添加组件到主分割器 main_splitter.addWidget(account_widget) main_splitter.addWidget(browser_widget) main_splitter.setSizes([300, 900]) self.setCentralWidget(main_splitter) def load_accounts(self): """加载所有账号到列表""" self.account_list.clear() accounts = self.account_manager.get_accounts() for account in accounts: self.account_list.addItem(f"{account['name']} (ID:{account['id']})") def add_account(self): """添加新账号""" account_name = self.account_name_input.text().strip() if not account_name: QMessageBox.warning(self, "输入错误", "请输入账号名称") return # 获取当前浏览器的Cookie cookie_store = self.browser.page().profile().cookieStore() cookies = [] # 使用闭包捕获cookies列表 def capture_cookies(cookie): cookies.append({ 'name': cookie.name().data().decode(), 'value': cookie.value().data().decode(), 'domain': cookie.domain(), 'path': cookie.path(), 'expirationDate': cookie.expirationDate().toSecsSinceEpoch() if cookie.expirationDate().isValid() else None }) # 连接信号捕获Cookie cookie_store.cookieAdded.connect(capture_cookies) # 设置一个定时器来等待Cookie收集完成 QTimer.singleShot(1000, lambda: self.finalize_account_creation(account_name, cookies, cookie_store)) # 提示用户登录 self.browser.load(QUrl("https://passport.bilibili.com/login")) self.status_bar.showMessage("请在浏览器中登录B站账号...", 5000) def finalize_account_creation(self, account_name, cookies, cookie_store): """完成账号创建过程""" # 断开信号连接 cookie_store.cookieAdded.disconnect() # 过滤B站相关Cookie bili_cookies = [c for c in cookies if 'bilibili' in c['domain']] if not bili_cookies: QMessageBox.warning(self, "添加失败", "未检测到有效的B站Cookie") return # 保存账号 if self.account_manager.add_account(account_name, bili_cookies): self.load_accounts() self.account_name_input.clear() self.status_bar.showMessage(f"账号 '{account_name}' 添加成功", 3000) else: QMessageBox.warning(self, "添加失败", "账号名称已存在") def delete_account(self): """删除选中账号""" current_item = self.account_list.currentItem() if not current_item: return account_id = int(current_item.text().split("ID:")[1].strip(")")) self.account_manager.delete_account(account_id) self.load_accounts() self.status_bar.showMessage("账号已删除", 3000) def switch_account(self, item): """切换到选中的账号""" account_id = int(item.text().split("ID:")[1].strip(")")) accounts = self.account_manager.get_accounts() account = next((a for a in accounts if a['id'] == account_id), None) if account: self.apply_cookies(account['cookies']) self.browser.load(QUrl("https://www.bilibili.com")) self.status_bar.showMessage(f"已切换到账号: {account['name']}", 3000) def apply_cookies(self, cookies): """应用Cookie到浏览器""" profile = QWebEngineProfile.defaultProfile() cookie_store = profile.cookieStore() cookie_store.deleteAllCookies() # 清除现有Cookie for cookie_data in cookies: cookie = QNetworkCookie() cookie.setName(cookie_data['name'].encode()) cookie.setValue(cookie_data['value'].encode()) cookie.setDomain(cookie_data['domain']) cookie.setPath(cookie_data['path']) if cookie_data['expirationDate']: cookie.setExpirationDate(cookie_data['expirationDate']) cookie_store.setCookie(cookie) def check_account_status(self): """检查选中账号的状态""" current_item = self.account_list.currentItem() if not current_item: return account_id = int(current_item.text().split("ID:")[1].strip(")")) accounts = self.account_manager.get_accounts() account = next((a for a in accounts if a['id'] == account_id), None) if account: # 使用requests验证Cookie有效性 session = requests.Session() for cookie in account['cookies']: session.cookies.set( cookie['name'], cookie['value'], domain=cookie['domain'], path=cookie['path'] ) try: response = session.get("https://api.bilibili.com/x/web-interface/nav") data = response.json() if data['code'] == 0: uname = data['data']['uname'] self.status_bar.showMessage(f"账号有效: {uname}", 5000) else: self.status_bar.showMessage("账号已失效,请重新登录", 5000) except Exception as e: self.status_bar.showMessage(f"检测失败: {str(e)}", 5000) def navigate_to_url(self): """导航到输入的URL""" url = self.url_bar.text().strip() if not url.startswith(('http://', 'https://')): url = 'https://' + url self.browser.load(QUrl(url)) def update_url_bar(self, q): """更新URL地址栏""" self.url_bar.setText(q.toString()) def go_home(self): """返回B站首页""" self.browser.load(QUrl("https://www.bilibili.com")) def refresh_page(self): """刷新当前页面""" self.browser.reload() if __name__ == "__main__": app = QApplication(sys.argv) browser = BilibiliBrowser() browser.show() sys.exit(app.exec_()) ``` ## 功能说明 1. **账号管理** - 添加账号:通过内置浏览器登录B站后自动捕获Cookie - 删除账号:移除选中的账号信息 - 切换账号:一键切换不同B站账号 2. **Cookie处理** - 自动捕获登录后的Cookie - 安全存储到SQLite数据库 - 应用Cookie到浏览器会话 3. **浏览器功能** - 内置浏览器支持B站所有功能 - 地址栏导航和历史记录 - 刷新和首页按钮 4. **账号状态检测** - 使用B站API验证Cookie有效性 - 显示账号用户名或失效提示 ## 使用说明 1. **添加账号**: - 在左侧输入账号名称 - 点击"添加账号"按钮 - 在浏览器中完成B站登录 - 系统自动捕获并保存Cookie 2. **切换账号**: - 在账号列表中选择目标账号 - 系统自动应用该账号的Cookie - 浏览器刷新显示该账号的B站内容 3. **检测账号状态**: - 选中账号后点击"检测账号状态" - 系统会验证Cookie是否有效 - 状态栏显示检测结果 4. **浏览B站**: - 在地址栏输入网址或搜索内容 - 使用导航按钮进行刷新或返回首页 - 视频播放功能与普通浏览器一致 ## 安全注意事项 1. **Cookie加密存储**(扩展功能): ```python from cryptography.fernet import Fernet class SecureAccountManager(AccountManager): def __init__(self, key_file='key.key', db_name='bilibili_accounts.db'): # 生成/加载加密密钥 if not os.path.exists(key_file): with open(key_file, 'wb') as f: f.write(Fernet.generate_key()) with open(key_file, 'rb') as f: self.cipher = Fernet(f.read()) super().__init__(db_name) def add_account(self, name, cookies): encrypted = self.cipher.encrypt(json.dumps(cookies).encode()) # 存储加密后的数据... def get_accounts(self): # 获取数据后解密... decrypted = self.cipher.decrypt(encrypted_data) ``` 2. **敏感信息处理**: - 避免在日志中输出Cookie信息 - 使用SQLite参数化查询防止注入 - 定期清理过期Cookie ## 扩展功能建议 1. **多标签浏览**:实现标签页功能,支持同时打开多个B站页面 2. **视频下载**:集成B站视频下载功能 3. **自动签到**:定时执行B站每日签到任务 4. **消息通知**:显示未读消息和动态更新 5. **代理支持**:添加代理设置选项 这个应用提供了完整的B站多账号管理解决方案,通过PyQt5实现了直观的用户界面和强大的功能集成。用户可以在不同B站账号间无缝切换,享受个性化的浏览体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值