这个代码报错: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())
最新发布