web页面中按钮执行处理时间过长,添加div等待层

本文介绍了一种使用JavaScript实现的网页加载等待层方法,通过创建一个覆盖整个屏幕的半透明div来提示用户当前正在进行处理操作,提升用户体验。该方法包括显示和隐藏等待层的功能,并考虑了浏览器窗口尺寸的变化。

web页面中按钮执行处理时间过长,添加div等待层,提升用户体验

javascript 文件:


var coverLayer = {   
 divObj : null,
 _coverTime : null,
 
// _coverRe : function()
// {//刷新遮盖层
//   this.divObj.style.width = window.screen.availWidth + "px";
//   this.divObj.style.height = window.screen.availHeight + "px";
// },
 
 on : function(noSave)
 { //打开遮盖层
  if(this.divObj == null)
  {
   this.divObj = document.createElement("div");
   this.divObj.style.zIndex = 10000;
   this.divObj.style.left = '0px';;
   this.divObj.style.top = '0px';;
   this.divObj.style.position = "absolute";
   this.divObj.style.backgroundColor = "#DDDDDD"; 
   this.divObj.style.fontSize = "x-large";
   this.divObj.style.textAlign ="center";
 
    this.divObj.innerText="处理中……";
   
    var tempFrame = document.createElement("iframe");
    tempFrame.style.filter = "Alpha(Opacity=0)";
    tempFrame.frameBorder=0;
    tempFrame.scrolling="no";
    tempFrame.style.width = "100%";
    tempFrame.style.height = "100%";
    this.divObj.appendChild(tempFrame);
    this.divObj.style.filter = "Alpha(Opacity=80)";
 
    document.body.appendChild(this.divObj);
  };

   this.divObj.style.width =  document.documentElement.clientWidth + "px";
   this.divObj.style.height =  document.documentElement.clientHeight + "px";
   this.divObj.style.paddingTop =document.documentElement.clientHeight / 2 + "px";

  
  this.divObj.style.display = "block";
 // clearInterval(this._coverTime);
 //this._coverTime = setInterval("coverLayer._coverRe()",1);
 },
 
 off : function(noSave)
 { //关闭遮盖层
  if(this.divObj){this.divObj.style.display = "none"};
  //clearInterval(this._coverTime);
 }
}

 

Page_Load初期处理中添加:


 If Not IsPostBack Then

     ...

     myBtn.OnClientClick = "coverLayer.on();"

     myLinkButton.OnClientClick = "coverLayer.on();"

    ...

End IF

这个代码报错:import sys from PyQt6.QtCore import QUrl, QEvent from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QLineEdit, QPushButton, QHBoxLayout from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWebEngineCore import QWebEnginePage, QWebEngineSettings, QWebEngineProfile class WebEnginePage(QWebEnginePage): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.javaScriptConsoleMessage = self.handle_console_message def handle_console_message(self, level, message, line, source): print(f"JS Console: {message}") def acceptNavigationRequest(self, url, type_, isMainFrame): # 允许所有导航请求 print(f"导航请求: {url.toString()}, 类型: {type_}, 主框架: {isMainFrame}") return True def certificateError(self, error): # 忽略证书错误 error.ignoreCertificateError() return True class WebEngineView(QWebEngineView): def createWindow(self, type_): # 处理新窗口创建(如链接打开新标签页) new_view = WebEngineView() new_page = WebEnginePage(self) new_view.setPage(new_page) # 将新视图添加到主窗口 if isinstance(self.parent(), QMainWindow): self.parent().add_new_view(new_view) return new_view class SimpleBrowser(QMainWindow): def __init__(self): super().__init__() # 创建主窗口 self.setWindowTitle("PyQt6简易浏览器") self.setGeometry(100, 100, 800, 600) # 创建中央部件和布局 central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # 创建顶部控制栏 top_layout = QHBoxLayout() # 创建地址栏和按钮 self.url_bar = QLineEdit("https://lzfr.luxshare.com.cn/webroot/decision/login") self.go_button = QPushButton("前往") self.go_button.clicked.connect(self.load_url) # 创建自动阅读按钮 self.auto_read_button = QPushButton("开始自动阅读") self.auto_read_button.clicked.connect(self.start_auto_read) self.auto_read_button.setEnabled(False) # 初始禁用,页面加载完成后再启用 # 添加到顶部布局 top_layout.addWidget(self.url_bar) top_layout.addWidget(self.go_button) top_layout.addWidget(self.auto_read_button) # 创建网页视图 self.web_view = WebEngineView(self) self.web_page = WebEnginePage(self.web_view) self.web_view.setPage(self.web_page) # 设置WebEngineProfile以允许弹出窗口 profile = self.web_page.profile() profile.setHttpCacheType(QWebEngineProfile.HttpCacheType.DiskHttpCache) profile.setPersistentCookiesPolicy(QWebEngineProfile.PersistentCookiesPolicy.AllowPersistentCookies) profile.setPersistentStoragePath("web_storage") # 获取设置并启用各种功能 settings = self.web_page.settings() # 启用所有必要的设置 settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptCanOpenWindows, True) settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard, True) settings.setAttribute(QWebEngineSettings.WebAttribute.LocalStorageEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.WebGLEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.PluginsEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.AutoLoadImages, True) settings.setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, True) settings.setAttribute(QWebEngineSettings.WebAttribute.AllowWindowActivationFromJavaScript, True) settings.setAttribute(QWebEngineSettings.WebAttribute.AllowRunningInsecureContent, True) settings.setAttribute(QWebEngineSettings.WebAttribute.AllowGeolocationOnInsecureOrigins, True) settings.setAttribute(QWebEngineSettings.WebAttribute.PlaybackRequiresUserGesture, False) settings.setAttribute(QWebEngineSettings.WebAttribute.FullScreenSupportEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.FocusOnNavigationEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.PrintElementBackgrounds, True) settings.setAttribute(QWebEngineSettings.WebAttribute.AllowWindowActivationFromJavaScript, True) settings.setAttribute(QWebEngineSettings.WebAttribute.ShowScrollBars, True) settings.setAttribute(QWebEngineSettings.WebAttribute.DnsPrefetchEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.XSSAuditingEnabled, False) settings.setAttribute(QWebEngineSettings.WebAttribute.SpatialNavigationEnabled, False) settings.setAttribute(QWebEngineSettings.WebAttribute.LinksIncludedInFocusChain, True) settings.setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, True) settings.setAttribute(QWebEngineSettings.WebAttribute.HyperlinkAuditingEnabled, False) # 删除了不存在的 JavaEnabled 属性 settings.setAttribute(QWebEngineSettings.WebAttribute.ErrorPageEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.SiteSpecificQuirksEnabled, True) # 设置用户代理 profile.setHttpUserAgent( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") # 页面加载完成后启用自动阅读按钮 self.web_page.loadFinished.connect(self.on_page_loaded) # 将部件添加到主布局 layout.addLayout(top_layout) layout.addWidget(self.web_view) # 绑定回车键事件 self.url_bar.returnPressed.connect(self.load_url) # 安装事件过滤器以捕获所有事件 self.web_view.installEventFilter(self) def eventFilter(self, obj, event): # 捕获鼠标点击事件,确保链接能被点击 if obj is self.web_view and event.type() == QEvent.Type.MouseButtonRelease: print(f"鼠标点击事件: {event.pos()}") return super().eventFilter(obj, event) def add_new_view(self, new_view): # 添加新视图到主窗口 new_view.setWindowTitle("新窗口") new_view.setGeometry(self.geometry().x() + 50, self.geometry().y() + 50, 800, 600) new_view.show() def load_url(self): """加载输入的URL""" url = self.url_bar.text().strip() if not url.startswith(('http://', 'https://')): url = 'https://' + url print(f"加载URL: {url}") self.web_view.load(QUrl(url)) self.auto_read_button.setEnabled(False) # 加载新页面时禁用按钮 def on_page_loaded(self, success): """页面加载完成后启用自动阅读按钮""" if success: print("页面加载完成") self.auto_read_button.setEnabled(True) # 注入JavaScript以确保链接可点击 self.inject_link_fix_script() else: print("页面加载失败") self.auto_read_button.setEnabled(False) def inject_link_fix_script(self): """注入JavaScript代码以确保链接可点击""" script = """ // 确保所有链接都能正常工作 document.querySelectorAll('a').forEach(link => { // 移除可能阻止点击的事件监听器 const clone = link.cloneNode(true); link.parentNode.replaceChild(clone, link); // 添加点击事件处理 clone.addEventListener('click', function(e) { e.preventDefault(); console.log('链接被点击:', this.href); window.location.href = this.href; }); }); // 确保所有按钮都能正常工作 document.querySelectorAll('button').forEach(button => { // 移除可能阻止点击的事件监听器 const clone = button.cloneNode(true); button.parentNode.replaceChild(clone, button); }); // 确保所有可点击元素都能正常工作 document.querySelectorAll('[onclick], [role="button"], [role="link"]').forEach(element => { // 移除可能阻止点击的事件监听器 const clone = element.cloneNode(true); element.parentNode.replaceChild(clone, element); }); console.log('链接修复脚本已注入'); """ self.web_page.runJavaScript(script) def start_auto_read(self): """开始自动阅读""" print("开始执行自动阅读脚本...") self.auto_read_button.setEnabled(False) # 执行期间禁用按钮 # 整合完整的JavaScript代码 js_code = """ (async function() { // 配置参数 const config = { batchSize: 5, // 每批处理的课程数量 batchInterval: 5000, // 每批之间的间隔时间(毫秒) countdownInterval: 5000, // 倒计时更新间隔(毫秒) defaultDuration: 60, // 默认课程时(秒) extraBuffer: 30, // 额外缓冲时间(秒) pageLoadWait: 3000, // 页面加载等待时间(毫秒) returnWait: 2000, // 返回列表页等待时间(毫秒) paginationWait: 4000, // 分页加载等待时间(毫秒) maxRetries: 3, // 最大重试次数 emptyPageWait: 2000 // 空页面额外等待时间(毫秒) }; // 1. 获取分页组件 const paginationContainer = document.querySelector('#yxtBizNavMain > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div > div'); if (!paginationContainer) { console.error('未找到分页组件,将只处理当前页课程'); await processCurrentPage(); return; } // 获取"下一页"按钮 const nextButton = paginationContainer.querySelector('.btn-next'); if (!nextButton) { console.error('未找到"下一页"按钮,将只处理当前页课程'); await processCurrentPage(); return; } console.log('检测到分页组件,将使用"下一页"按钮逐页翻页'); // 2. 逐页处理课程 let currentPage = 1; let hasMorePages = true; let consecutiveEmptyPages = 0; // 连续空页面计数 const maxEmptyPages = 159; // 最大连续空页面数 while (hasMorePages) { console.log(`\\n========== 开始处理第 ${currentPage} 页 ==========`); // 处理当前页的课程 const coursesProcessed = await processCurrentPage(); // 如果当前页没有课程需要处理,增加空页面计数 if (coursesProcessed === 0) { consecutiveEmptyPages++; console.log(`当前页没有需要处理的课程,连续空页面数: ${consecutiveEmptyPages}/${maxEmptyPages}`); // 如果连续空页面达到上限,停止翻页 if (consecutiveEmptyPages >= maxEmptyPages) { console.log(`连续${maxEmptyPages}页没有需要处理的课程,可能已到达课程末尾,停止翻页`); hasMorePages = false; break; } // 空页面额外等待 console.log(`等待${config.emptyPageWait}ms后尝试翻页...`); await new Promise(resolve => setTimeout(resolve, config.emptyPageWait)); } else { // 重置空页面计数 consecutiveEmptyPages = 0; } // 检查是否还有下一页 if (nextButton.disabled || nextButton.hasAttribute('disabled')) { console.log('已到达最后一页'); hasMorePages = false; } else { console.log(`第 ${currentPage} 页处理完成,准备翻到下一页...`); // 尝试翻页 let retryCount = 0; let pageNavigated = false; while (retryCount < config.maxRetries && !pageNavigated) { try { // 重新获取分页组件和按钮(因为DOM可能已更新) const updatedPaginationContainer = document.querySelector('#yxtBizNavMain > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div > div'); const updatedNextButton = updatedPaginationContainer?.querySelector('.btn-next'); if (!updatedNextButton) { throw new Error('未找到"下一页"按钮'); } if (updatedNextButton.disabled || updatedNextButton.hasAttribute('disabled')) { console.log('已到达最后一页'); hasMorePages = false; pageNavigated = true; break; } console.log('点击"下一页"按钮'); updatedNextButton.click(); // 等待页面加载 await new Promise(resolve => setTimeout(resolve, config.paginationWait)); // 验证是否成功翻页 const activePage = updatedPaginationContainer.querySelector('.yxtf-pager .number.active'); if (activePage) { const newPageNum = parseInt(activePage.textContent); if (newPageNum > currentPage) { currentPage = newPageNum; pageNavigated = true; console.log(`成功翻到第 ${currentPage} 页`); } else { // 尝试备选翻页方法:点击下一个页码按钮 console.log('使用"下一页"按钮翻页失败,尝试点击页码按钮...'); pageNavigated = await tryPageNumberNavigation(currentPage + 1); if (pageNavigated) { currentPage++; console.log(`通过页码按钮成功翻到第 ${currentPage} 页`); } else { throw new Error('页面未更新'); } } } else { throw new Error('未找到激活的页码'); } } catch (error) { retryCount++; console.error(`翻页失败 (尝试 ${retryCount}/${config.maxRetries}):`, error.message); if (retryCount < config.maxRetries) { console.log(`等待 ${config.paginationWait}ms 后重试...`); await new Promise(resolve => setTimeout(resolve, config.paginationWait)); } else { console.error('翻页重试次数已达上限,尝试备选翻页方法...'); pageNavigated = await tryPageNumberNavigation(currentPage + 1); if (pageNavigated) { currentPage++; console.log(`通过备选方法成功翻到第 ${currentPage} 页`); } else { console.error('所有翻页方法均失败,停止翻页'); hasMorePages = false; } } } } // 如果成功翻页,等待一段时间处理 if (pageNavigated && hasMorePages) { console.log(`等待 ${config.batchInterval / 1000} 秒后处理下一页...`); await new Promise(resolve => setTimeout(resolve, config.batchInterval)); // 执行垃圾回收(如果浏览器支持) if (window.gc) { console.log('执行垃圾回收...'); window.gc(); } } } } console.log('\\n========== 所有分页处理完成!=========='); // 备选翻页方法:尝试点击页码按钮 async function tryPageNumberNavigation(targetPage) { try { const paginationContainer = document.querySelector('#yxtBizNavMain > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div > div'); const pageButtons = paginationContainer.querySelectorAll('.yxtf-pager .number'); for (let btn of pageButtons) { const pageNum = parseInt(btn.textContent); if (pageNum === targetPage) { console.log(`点击页码按钮: 第${targetPage}页`); btn.click(); // 等待页面加载 await new Promise(resolve => setTimeout(resolve, config.paginationWait)); // 验证是否成功翻页 const activePage = paginationContainer.querySelector('.yxtf-pager .number.active'); if (activePage && parseInt(activePage.textContent) === targetPage) { return true; } break; } } return false; } catch (error) { console.error('备选翻页方法失败:', error); return false; } } // 处理当前页课程的函数 async function processCurrentPage() { // 获取课程列表容器 const courseListContainer = document.querySelector('#yxtBizNavMain > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > ul'); if (!courseListContainer) { console.error('未找到课程列表容器'); return 0; } // 获取当前页的所有课程项 const courseItems = courseListContainer.querySelectorAll('li'); console.log(`当前页找到 ${courseItems.length} 个课程项`); // 筛选需要处理的课程 const coursesToClick = []; courseItems.forEach((item, index) => { const statusTags = item.querySelectorAll('.kng-list-new__tag'); let shouldSkip = false; statusTags.forEach(tag => { const tagText = tag.textContent.trim(); if (tagText === '学习中' || tagText === '已学完') { shouldSkip = true; } }); if (!shouldSkip) { coursesToClick.push({ element: item, index: index + 1, title: item.querySelector('.ellipsis')?.textContent.trim() || '无标题' }); } }); console.log(`当前页需要点击的课程数量: ${coursesToClick.length}`); // 如果没有课程需要处理,直接返回0 if (coursesToClick.length === 0) { return 0; } // 分批处理当前页的课程 const totalBatches = Math.ceil(coursesToClick.length / config.batchSize); for (let batch = 0; batch < totalBatches; batch++) { const startIdx = batch * config.batchSize; const endIdx = Math.min(startIdx + config.batchSize, coursesToClick.length); const batchCourses = coursesToClick.slice(startIdx, endIdx); console.log(`开始处理第 ${batch + 1}/${totalBatches} 批课程(${startIdx + 1}-${endIdx})`); // 处理当前批次的课程 for (let i = 0; i < batchCourses.length; i++) { const course = batchCourses[i]; const globalIndex = startIdx + i; try { // 简化高亮效果 course.element.style.border = '2px solid #4CAF50'; // 使用平滑滚动 course.element.scrollIntoView({ behavior: 'smooth', block: 'center' }); console.log(`正在处理课程 ${globalIndex + 1}/${coursesToClick.length}: ${course.title}`); // 点击课程进入详情页 course.element.click(); // 等待页面跳转 await new Promise(resolve => setTimeout(resolve, config.pageLoadWait)); // 获取课程时 const durationElement = document.querySelector('#yxtBizNavMain > div > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div > span > header > div > div:nth-child(2) > div:nth-child(2) > span:nth-child(2) > span:nth-child(1)'); let durationSeconds = config.defaultDuration; if (durationElement) { const durationText = durationElement.textContent.trim(); console.log(`找到课程时信息: ${durationText}`); const minuteMatch = durationText.match(/(\\d+\\.?\\d*)/); if (minuteMatch) { const minutes = parseFloat(minuteMatch[1]); durationSeconds = Math.ceil(minutes * 60); console.log(`课程时: ${minutes}分钟 = ${durationSeconds}秒`); } else { console.warn('无法解析课程时,使用默认值'); } } else { console.warn('未找到课程时元素,使用默认值'); } // 增加缓冲时间 durationSeconds += config.extraBuffer; console.log(`增加${config.extraBuffer}秒缓冲时间,总等待时间: ${durationSeconds}秒`); // 点击开始学习按钮 const startButton = document.querySelector('#yxtBizNavMain > div > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div > span > div > main > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div > div > div:nth-child(2) > button'); if (startButton) { console.log('点击"开始学习"按钮'); startButton.click(); // 等待课程时 console.log(`等待课程完成,需要等待 ${durationSeconds} 秒...`); // 使用较少的倒计时更新 let remainingSeconds = durationSeconds; const countdownInterval = setInterval(() => { remainingSeconds -= config.countdownInterval / 1000; const minutes = Math.floor(remainingSeconds / 60); const seconds = Math.floor(remainingSeconds % 60); console.log(`课程倒计时: ${minutes}分${seconds}秒`); if (remainingSeconds <= 0) { clearInterval(countdownInterval); } }, config.countdownInterval); await new Promise(resolve => setTimeout(resolve, durationSeconds * 1000)); clearInterval(countdownInterval); console.log('课程学习时间结束'); } else { console.error(`未找到"开始学习"按钮等待${config.defaultDuration + config.extraBuffer}秒后继续`); await new Promise(resolve => setTimeout(resolve, (config.defaultDuration + config.extraBuffer) * 1000)); } // 返回课程列表页 console.log('返回课程列表页'); window.history.back(); await new Promise(resolve => setTimeout(resolve, config.returnWait)); // 移除高亮 course.element.style.border = ''; } catch (error) { console.error(`处理课程 ${course.title} 时出错:`, error); // 尝试返回课程列表页 try { window.history.back(); await new Promise(resolve => setTimeout(resolve, config.returnWait)); } catch (e) { console.error('返回课程列表页失败:', e); } } } // 如果不是最后一批,等待一段时间处理下一批 if (batch < totalBatches - 1) { console.log(`第 ${batch + 1} 批课程处理完成,等待 ${config.batchInterval / 1000} 秒后处理下一批...`); await new Promise(resolve => setTimeout(resolve, config.batchInterval)); } } return coursesToClick.length; } })(); """ self.web_page.runJavaScript(js_code) if __name__ == "__main__": app = QApplication(sys.argv) browser = SimpleBrowser() browser.show() sys.exit(app.exec()),import sys from PyQt6.QtCore import QUrl, QEvent from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QLineEdit, QPushButton, QHBoxLayout from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWebEngineCore import QWebEnginePage, QWebEngineSettings, QWebEngineProfile class WebEnginePage(QWebEnginePage): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.javaScriptConsoleMessage = self.handle_console_message def handle_console_message(self, level, message, line, source): print(f"JS Console: {message}") def acceptNavigationRequest(self, url, type_, isMainFrame): # 允许所有导航请求 print(f"导航请求: {url.toString()}, 类型: {type_}, 主框架: {isMainFrame}") return True def certificateError(self, error): # 忽略证书错误 error.ignoreCertificateError() return True class WebEngineView(QWebEngineView): def createWindow(self, type_): # 处理新窗口创建(如链接打开新标签页) new_view = WebEngineView() new_page = WebEnginePage(self) new_view.setPage(new_page) # 将新视图添加到主窗口 if isinstance(self.parent(), QMainWindow): self.parent().add_new_view(new_view) return new_view class SimpleBrowser(QMainWindow): def __init__(self): super().__init__() # 创建主窗口 self.setWindowTitle("PyQt6简易浏览器") self.setGeometry(100, 100, 800, 600) # 创建中央部件和布局 central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # 创建顶部控制栏 top_layout = QHBoxLayout() # 创建地址栏和按钮 self.url_bar = QLineEdit("https://lzfr.luxshare.com.cn/webroot/decision/login") self.go_button = QPushButton("前往") self.go_button.clicked.connect(self.load_url) # 创建自动阅读按钮 self.auto_read_button = QPushButton("开始自动阅读") self.auto_read_button.clicked.connect(self.start_auto_read) self.auto_read_button.setEnabled(False) # 初始禁用,页面加载完成后再启用 # 添加到顶部布局 top_layout.addWidget(self.url_bar) top_layout.addWidget(self.go_button) top_layout.addWidget(self.auto_read_button) # 创建网页视图 self.web_view = WebEngineView(self) self.web_page = WebEnginePage(self.web_view) self.web_view.setPage(self.web_page) # 设置WebEngineProfile以允许弹出窗口 profile = self.web_page.profile() profile.setHttpCacheType(QWebEngineProfile.HttpCacheType.DiskHttpCache) profile.setPersistentCookiesPolicy(QWebEngineProfile.PersistentCookiesPolicy.AllowPersistentCookies) profile.setPersistentStoragePath("web_storage") # 获取设置并启用各种功能 settings = self.web_page.settings() # 启用所有必要的设置 settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptCanOpenWindows, True) settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard, True) settings.setAttribute(QWebEngineSettings.WebAttribute.LocalStorageEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.WebGLEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.PluginsEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.AutoLoadImages, True) settings.setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, True) settings.setAttribute(QWebEngineSettings.WebAttribute.AllowWindowActivationFromJavaScript, True) settings.setAttribute(QWebEngineSettings.WebAttribute.AllowRunningInsecureContent, True) settings.setAttribute(QWebEngineSettings.WebAttribute.AllowGeolocationOnInsecureOrigins, True) settings.setAttribute(QWebEngineSettings.WebAttribute.PlaybackRequiresUserGesture, False) settings.setAttribute(QWebEngineSettings.WebAttribute.FullScreenSupportEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.FocusOnNavigationEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.PrintElementBackgrounds, True) settings.setAttribute(QWebEngineSettings.WebAttribute.AllowWindowActivationFromJavaScript, True) settings.setAttribute(QWebEngineSettings.WebAttribute.ShowScrollBars, True) settings.setAttribute(QWebEngineSettings.WebAttribute.DnsPrefetchEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.XSSAuditingEnabled, False) settings.setAttribute(QWebEngineSettings.WebAttribute.SpatialNavigationEnabled, False) settings.setAttribute(QWebEngineSettings.WebAttribute.LinksIncludedInFocusChain, True) settings.setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, True) settings.setAttribute(QWebEngineSettings.WebAttribute.HyperlinkAuditingEnabled, False) # 删除了不存在的 JavaEnabled 属性 settings.setAttribute(QWebEngineSettings.WebAttribute.ErrorPageEnabled, True) settings.setAttribute(QWebEngineSettings.WebAttribute.SiteSpecificQuirksEnabled, True) # 设置用户代理 profile.setHttpUserAgent( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") # 页面加载完成后启用自动阅读按钮 self.web_page.loadFinished.connect(self.on_page_loaded) # 将部件添加到主布局 layout.addLayout(top_layout) layout.addWidget(self.web_view) # 绑定回车键事件 self.url_bar.returnPressed.connect(self.load_url) # 安装事件过滤器以捕获所有事件 self.web_view.installEventFilter(self) def eventFilter(self, obj, event): # 捕获鼠标点击事件,确保链接能被点击 if obj is self.web_view and event.type() == QEvent.Type.MouseButtonRelease: print(f"鼠标点击事件: {event.pos()}") return super().eventFilter(obj, event) def add_new_view(self, new_view): # 添加新视图到主窗口 new_view.setWindowTitle("新窗口") new_view.setGeometry(self.geometry().x() + 50, self.geometry().y() + 50, 800, 600) new_view.show() def load_url(self): """加载输入的URL""" url = self.url_bar.text().strip() if not url.startswith(('http://', 'https://')): url = 'https://' + url print(f"加载URL: {url}") self.web_view.load(QUrl(url)) self.auto_read_button.setEnabled(False) # 加载新页面时禁用按钮 def on_page_loaded(self, success): """页面加载完成后启用自动阅读按钮""" if success: print("页面加载完成") self.auto_read_button.setEnabled(True) # 注入JavaScript以确保链接可点击 self.inject_link_fix_script() else: print("页面加载失败") self.auto_read_button.setEnabled(False) def inject_link_fix_script(self): """注入JavaScript代码以确保链接可点击""" script = """ // 确保所有链接都能正常工作 document.querySelectorAll('a').forEach(link => { // 移除可能阻止点击的事件监听器 const clone = link.cloneNode(true); link.parentNode.replaceChild(clone, link); // 添加点击事件处理 clone.addEventListener('click', function(e) { e.preventDefault(); console.log('链接被点击:', this.href); window.location.href = this.href; }); }); // 确保所有按钮都能正常工作 document.querySelectorAll('button').forEach(button => { // 移除可能阻止点击的事件监听器 const clone = button.cloneNode(true); button.parentNode.replaceChild(clone, button); }); // 确保所有可点击元素都能正常工作 document.querySelectorAll('[onclick], [role="button"], [role="link"]').forEach(element => { // 移除可能阻止点击的事件监听器 const clone = element.cloneNode(true); element.parentNode.replaceChild(clone, element); }); console.log('链接修复脚本已注入'); """ self.web_page.runJavaScript(script) def start_auto_read(self): """开始自动阅读""" print("开始执行自动阅读脚本...") self.auto_read_button.setEnabled(False) # 执行期间禁用按钮 # 整合完整的JavaScript代码 js_code = """ (async function() { // 配置参数 const config = { batchSize: 5, // 每批处理的课程数量 batchInterval: 5000, // 每批之间的间隔时间(毫秒) countdownInterval: 5000, // 倒计时更新间隔(毫秒) defaultDuration: 60, // 默认课程时(秒) extraBuffer: 30, // 额外缓冲时间(秒) pageLoadWait: 3000, // 页面加载等待时间(毫秒) returnWait: 2000, // 返回列表页等待时间(毫秒) paginationWait: 4000, // 分页加载等待时间(毫秒) maxRetries: 3, // 最大重试次数 emptyPageWait: 2000 // 空页面额外等待时间(毫秒) }; // 1. 获取分页组件 const paginationContainer = document.querySelector('#yxtBizNavMain > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div > div'); if (!paginationContainer) { console.error('未找到分页组件,将只处理当前页课程'); await processCurrentPage(); return; } // 获取"下一页"按钮 const nextButton = paginationContainer.querySelector('.btn-next'); if (!nextButton) { console.error('未找到"下一页"按钮,将只处理当前页课程'); await processCurrentPage(); return; } console.log('检测到分页组件,将使用"下一页"按钮逐页翻页'); // 2. 逐页处理课程 let currentPage = 1; let hasMorePages = true; let consecutiveEmptyPages = 0; // 连续空页面计数 const maxEmptyPages = 159; // 最大连续空页面数 while (hasMorePages) { console.log(`\\n========== 开始处理第 ${currentPage} 页 ==========`); // 处理当前页的课程 const coursesProcessed = await processCurrentPage(); // 如果当前页没有课程需要处理,增加空页面计数 if (coursesProcessed === 0) { consecutiveEmptyPages++; console.log(`当前页没有需要处理的课程,连续空页面数: ${consecutiveEmptyPages}/${maxEmptyPages}`); // 如果连续空页面达到上限,停止翻页 if (consecutiveEmptyPages >= maxEmptyPages) { console.log(`连续${maxEmptyPages}页没有需要处理的课程,可能已到达课程末尾,停止翻页`); hasMorePages = false; break; } // 空页面额外等待 console.log(`等待${config.emptyPageWait}ms后尝试翻页...`); await new Promise(resolve => setTimeout(resolve, config.emptyPageWait)); } else { // 重置空页面计数 consecutiveEmptyPages = 0; } // 检查是否还有下一页 if (nextButton.disabled || nextButton.hasAttribute('disabled')) { console.log('已到达最后一页'); hasMorePages = false; } else { console.log(`第 ${currentPage} 页处理完成,准备翻到下一页...`); // 尝试翻页 let retryCount = 0; let pageNavigated = false; while (retryCount < config.maxRetries && !pageNavigated) { try { // 重新获取分页组件和按钮(因为DOM可能已更新) const updatedPaginationContainer = document.querySelector('#yxtBizNavMain > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div > div'); const updatedNextButton = updatedPaginationContainer?.querySelector('.btn-next'); if (!updatedNextButton) { throw new Error('未找到"下一页"按钮'); } if (updatedNextButton.disabled || updatedNextButton.hasAttribute('disabled')) { console.log('已到达最后一页'); hasMorePages = false; pageNavigated = true; break; } console.log('点击"下一页"按钮'); updatedNextButton.click(); // 等待页面加载 await new Promise(resolve => setTimeout(resolve, config.paginationWait)); // 验证是否成功翻页 const activePage = updatedPaginationContainer.querySelector('.yxtf-pager .number.active'); if (activePage) { const newPageNum = parseInt(activePage.textContent); if (newPageNum > currentPage) { currentPage = newPageNum; pageNavigated = true; console.log(`成功翻到第 ${currentPage} 页`); } else { // 尝试备选翻页方法:点击下一个页码按钮 console.log('使用"下一页"按钮翻页失败,尝试点击页码按钮...'); pageNavigated = await tryPageNumberNavigation(currentPage + 1); if (pageNavigated) { currentPage++; console.log(`通过页码按钮成功翻到第 ${currentPage} 页`); } else { throw new Error('页面未更新'); } } } else { throw new Error('未找到激活的页码'); } } catch (error) { retryCount++; console.error(`翻页失败 (尝试 ${retryCount}/${config.maxRetries}):`, error.message); if (retryCount < config.maxRetries) { console.log(`等待 ${config.paginationWait}ms 后重试...`); await new Promise(resolve => setTimeout(resolve, config.paginationWait)); } else { console.error('翻页重试次数已达上限,尝试备选翻页方法...'); pageNavigated = await tryPageNumberNavigation(currentPage + 1); if (pageNavigated) { currentPage++; console.log(`通过备选方法成功翻到第 ${currentPage} 页`); } else { console.error('所有翻页方法均失败,停止翻页'); hasMorePages = false; } } } } // 如果成功翻页,等待一段时间处理 if (pageNavigated && hasMorePages) { console.log(`等待 ${config.batchInterval / 1000} 秒后处理下一页...`); await new Promise(resolve => setTimeout(resolve, config.batchInterval)); // 执行垃圾回收(如果浏览器支持) if (window.gc) { console.log('执行垃圾回收...'); window.gc(); } } } } console.log('\\n========== 所有分页处理完成!=========='); // 备选翻页方法:尝试点击页码按钮 async function tryPageNumberNavigation(targetPage) { try { const paginationContainer = document.querySelector('#yxtBizNavMain > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div > div'); const pageButtons = paginationContainer.querySelectorAll('.yxtf-pager .number'); for (let btn of pageButtons) { const pageNum = parseInt(btn.textContent); if (pageNum === targetPage) { console.log(`点击页码按钮: 第${targetPage}页`); btn.click(); // 等待页面加载 await new Promise(resolve => setTimeout(resolve, config.paginationWait)); // 验证是否成功翻页 const activePage = paginationContainer.querySelector('.yxtf-pager .number.active'); if (activePage && parseInt(activePage.textContent) === targetPage) { return true; } break; } } return false; } catch (error) { console.error('备选翻页方法失败:', error); return false; } } // 处理当前页课程的函数 async function processCurrentPage() { // 获取课程列表容器 const courseListContainer = document.querySelector('#yxtBizNavMain > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > ul'); if (!courseListContainer) { console.error('未找到课程列表容器'); return 0; } // 获取当前页的所有课程项 const courseItems = courseListContainer.querySelectorAll('li'); console.log(`当前页找到 ${courseItems.length} 个课程项`); // 筛选需要处理的课程 const coursesToClick = []; courseItems.forEach((item, index) => { const statusTags = item.querySelectorAll('.kng-list-new__tag'); let shouldSkip = false; statusTags.forEach(tag => { const tagText = tag.textContent.trim(); if (tagText === '学习中' || tagText === '已学完') { shouldSkip = true; } }); if (!shouldSkip) { coursesToClick.push({ element: item, index: index + 1, title: item.querySelector('.ellipsis')?.textContent.trim() || '无标题' }); } }); console.log(`当前页需要点击的课程数量: ${coursesToClick.length}`); // 如果没有课程需要处理,直接返回0 if (coursesToClick.length === 0) { return 0; } // 分批处理当前页的课程 const totalBatches = Math.ceil(coursesToClick.length / config.batchSize); for (let batch = 0; batch < totalBatches; batch++) { const startIdx = batch * config.batchSize; const endIdx = Math.min(startIdx + config.batchSize, coursesToClick.length); const batchCourses = coursesToClick.slice(startIdx, endIdx); console.log(`开始处理第 ${batch + 1}/${totalBatches} 批课程(${startIdx + 1}-${endIdx})`); // 处理当前批次的课程 for (let i = 0; i < batchCourses.length; i++) { const course = batchCourses[i]; const globalIndex = startIdx + i; try { // 简化高亮效果 course.element.style.border = '2px solid #4CAF50'; // 使用平滑滚动 course.element.scrollIntoView({ behavior: 'smooth', block: 'center' }); console.log(`正在处理课程 ${globalIndex + 1}/${coursesToClick.length}: ${course.title}`); // 点击课程进入详情页 course.element.click(); // 等待页面跳转 await new Promise(resolve => setTimeout(resolve, config.pageLoadWait)); // 获取课程时 const durationElement = document.querySelector('#yxtBizNavMain > div > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div > span > header > div > div:nth-child(2) > div:nth-child(2) > span:nth-child(2) > span:nth-child(1)'); let durationSeconds = config.defaultDuration; if (durationElement) { const durationText = durationElement.textContent.trim(); console.log(`找到课程时信息: ${durationText}`); const minuteMatch = durationText.match(/(\\d+\\.?\\d*)/); if (minuteMatch) { const minutes = parseFloat(minuteMatch[1]); durationSeconds = Math.ceil(minutes * 60); console.log(`课程时: ${minutes}分钟 = ${durationSeconds}秒`); } else { console.warn('无法解析课程时,使用默认值'); } } else { console.warn('未找到课程时元素,使用默认值'); } // 增加缓冲时间 durationSeconds += config.extraBuffer; console.log(`增加${config.extraBuffer}秒缓冲时间,总等待时间: ${durationSeconds}秒`); // 点击开始学习按钮 const startButton = document.querySelector('#yxtBizNavMain > div > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div > span > div > main > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div > div > div:nth-child(2) > button'); if (startButton) { console.log('点击"开始学习"按钮'); startButton.click(); // 等待课程时 console.log(`等待课程完成,需要等待 ${durationSeconds} 秒...`); // 使用较少的倒计时更新 let remainingSeconds = durationSeconds; const countdownInterval = setInterval(() => { remainingSeconds -= config.countdownInterval / 1000; const minutes = Math.floor(remainingSeconds / 60); const seconds = Math.floor(remainingSeconds % 60); console.log(`课程倒计时: ${minutes}分${seconds}秒`); if (remainingSeconds <= 0) { clearInterval(countdownInterval); } }, config.countdownInterval); await new Promise(resolve => setTimeout(resolve, durationSeconds * 1000)); clearInterval(countdownInterval); console.log('课程学习时间结束'); } else { console.error(`未找到"开始学习"按钮等待${config.defaultDuration + config.extraBuffer}秒后继续`); await new Promise(resolve => setTimeout(resolve, (config.defaultDuration + config.extraBuffer) * 1000)); } // 返回课程列表页 console.log('返回课程列表页'); window.history.back(); await new Promise(resolve => setTimeout(resolve, config.returnWait)); // 移除高亮 course.element.style.border = ''; } catch (error) { console.error(`处理课程 ${course.title} 时出错:`, error); // 尝试返回课程列表页 try { window.history.back(); await new Promise(resolve => setTimeout(resolve, config.returnWait)); } catch (e) { console.error('返回课程列表页失败:', e); } } } // 如果不是最后一批,等待一段时间处理下一批 if (batch < totalBatches - 1) { console.log(`第 ${batch + 1} 批课程处理完成,等待 ${config.batchInterval / 1000} 秒后处理下一批...`); await new Promise(resolve => setTimeout(resolve, config.batchInterval)); } } return coursesToClick.length; } })(); """ self.web_page.runJavaScript(js_code) if __name__ == "__main__": app = QApplication(sys.argv) browser = SimpleBrowser() browser.show() sys.exit(app.exec())
最新发布
09-07
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值