1.购买服务器阿里云:服务器购买地址https://t.aliyun.com/U/Bg6shY若失效,可用地址
阿里云:
服务器购买地址
https://t.aliyun.com/U/Bg6shY若失效,可用地址
https://www.aliyun.com/daily-act/ecs/activity_selection?source=5176.29345612&userCode=49hts92d腾讯云:
https://curl.qcloud.com/wJpWmSfU若失效,可用地址
https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=ad201ee2ef3b771157f72ee5464b1fea&from=console华为云
https://activity.huaweicloud.com/cps.html?fromacct=64b5cf7cc11b4840bb4ed2ea0b2f4468&utm_source=V1g3MDY4NTY=&utm_medium=cps&utm_campaign=2019052.部署教程
3.代码如下
import timeimport randomfrom selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.common.action_chains import ActionChainsfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.common.exceptions import NoSuchElementExceptionfrom selenium.common.exceptions import TimeoutExceptionfrom selenium.webdriver.chrome.options import Optionsimport configparser# 读取配置文件config = configparser.ConfigParser()config.read("config.ini", encoding="utf-8")# 获取用户名和密码USERNAME = config.get("login", "username")PASSWORD = config.get("login", "password")# 获取学习课程类型course_type = config.get("course_type","type")url = config.get("url", "url")years = config.get("years","years")class AutoCourseBot:def __init__(self, username, password):# 配置Chrome选项,启用静音chrome_options = Options()chrome_options.add_argument("--mute-audio")self.driver = webdriver.Chrome(options=chrome_options)self.driver.maximize_window()self.wait = WebDriverWait(self.driver, 15)self.username = usernameself.password = password# ========== 工具函数 ==========# def handle_popup(self):# """处理学习过程中的弹窗"""# try:# popup_btn = self.driver.find_element(By.CSS_SELECTOR, "div.score-popup button.close-btn")# if popup_btn.is_displayed():# popup_btn.click()# print("⚡ 弹窗已关闭,继续学习")# time.sleep(3)# except NoSuchElementException:# passdef handle_popup(self):"""处理学习过程中的弹窗(优化版)"""try:# 关键:等待弹窗按钮出现(最多等10秒),确保弹窗已加载popup_btn = WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "div.score-popup button.close-btn")))# 确认按钮可见且可点击if popup_btn.is_displayed() and popup_btn.is_enabled():popup_btn.click()print("⚡ 弹窗已关闭,继续学习")# 等待弹窗消失(避免后续操作受影响)WebDriverWait(self.driver, 10).until(EC.invisibility_of_element_located((By.CSS_SELECTOR, "div.score-popup")))time.sleep(3)except Exception:passdef handle_leave_page_tip(self, wait_time=5):try:# 等待弹窗容器出现,最多等待wait_time秒modal_container = WebDriverWait(self.driver, wait_time).until(EC.presence_of_element_located((By.CSS_SELECTOR, "div.leave-page-tip-modal-container")))# 确认弹窗可见if modal_container.is_displayed():print("检测到学习状态中断提醒弹窗")# 查找并点击"继续学习"按钮continue_button = modal_container.find_element(By.CSS_SELECTOR, "button.button")continue_button.click()print("已点击继续学习按钮")# 等待弹窗消失WebDriverWait(self.driver, wait_time).until(EC.invisibility_of_element_located((By.CSS_SELECTOR, "div.leave-page-tip-modal-container")))except TimeoutException:pass# except Exception as e:# print(f"处理弹窗时发生错误: {str(e)}")try:play_btn = WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "xg-play .xgplayer-icon-play")))play_btn.click()except NoSuchElementException:print(f"出现中断弹窗后,点击播放时出现异常!")def click_ai_option(self):try:# 此try中的代码为规避系统检测# 找出AI题目标签quiz_main = self.driver.find_element(By.CSS_SELECTOR,"div.quiz-main")time.sleep(2)if quiz_main:# 获取所有选项标签options = quiz_main.find_elements(By.TAG_NAME, "li")# 随机选择一个选项random_option = random.choice(options)# 点击选项中的单选按钮(quiz-radio元素)# 优先点击单选按钮区域,更符合实际交互逻辑# radio_button = random_option.find_element(By.CLASS_NAME, "quiz-radio")random_option.click()except NoSuchElementException:passdef set_playback_rate_to_2x(self, wait_time=5):try:# 等待播放速度控制元素出现playbackrate_element = WebDriverWait(self.driver, wait_time).until(EC.presence_of_element_located((By.CSS_SELECTOR, "xg-playbackrate.xgplayer-playbackrate")))# 点击播放速度控制元素,展开速度选择列表playbackrate_element.click()print("已点击播放速度控制器,展开选择列表")# 查找并点击2倍速选项(cname="2"的li元素)two_x_element = WebDriverWait(self.driver, wait_time).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "xg-playbackrate.xgplayer-playbackrate li[cname='2']")))two_x_element.click()print("已设置播放速度为2倍速")except TimeoutException:print("超时:未找到播放速度控制元素或2倍速选项")except NoSuchElementException:print("未找到播放速度控制元素或2倍速选项")except Exception as e:print(f"设置播放速度时发生错误: {str(e)}")def have_cretificate(self):try:# 查找父标签parent_element = self.driver.find_element(By.CSS_SELECTOR,"div.course-left")try:# 在父标签下查找目标子标签parent_element.find_element(By.CSS_SELECTOR, "div.certInfo")# 如果找到子标签,返回Truereturn Trueexcept NoSuchElementException:# 未找到子标签return Falseexcept NoSuchElementException:# 未找到父标签return Falsedef is_video_playing_normally(self, timeout=5):try:pause_icon = WebDriverWait(self.driver, timeout).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "xg-play .xgplayer-icon .xgplayer-icon-pause") # 暂停图标))return pause_icon.is_displayed()except TimeoutException:# 超时说明任一条件未满足(加载中/未播放/控件未出现)return Falsedef get_progress(self):"""获取课程进度(已完成节数,总节数)"""try:finished_text = self.driver.find_element(By.CSS_SELECTOR, "span.section-finish").textfinished_num = int("".join(filter(str.isdigit, finished_text)))except:finished_num = 0total_text = self.driver.find_element(By.CSS_SELECTOR, "span.section-total").texttotal_num = int("".join(filter(str.isdigit, total_text)))return finished_num, total_num# ========== 核心功能 ==========def login(self):self.driver.get(url)username_input = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "input#rc_select_0")))username_input.send_keys(self.username)password_input = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "input.input-text[type='password']")))password_input.send_keys(self.password)login_btn = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button.confirm-btn")))login_btn.click()print("✅ 登录成功")def get_all_course_links(self, course_type):"""翻页获取所有课程链接"""card_modules = self.wait.until(EC.presence_of_all_elements_located ((By.CSS_SELECTOR, "div.card-module")))card_module = card_modules[int(years) - 1]time.sleep(5)# 找到课程卡片里的所有入口 <a class="card-item">course_links = card_module.find_elements(By.CSS_SELECTOR, "a.card-item")if course_links:# 0为通用 1为专业course_links[int(course_type) - 1].click()print("点击第一个课程入口,进入学习页面。")time.sleep(5)# 获取所有 a 标签all_links = [] # 存储所有 a 标签while True:self.wait.until(EC.presence_of_all_elements_located((By.TAG_NAME, "a")))a_tags = self.driver.find_elements(By.TAG_NAME, "a")for a in a_tags:href = a.get_attribute("href")text = a.text.strip()if href and "/course/" in href:all_links.append((text, href))# 翻页try:next_btn = self.driver.find_element(By.CLASS_NAME, "ant-pagination-next")if next_btn.get_attribute("aria-disabled") == "true":breakelse:next_btn.click()time.sleep(5)except:breakreturn all_linksdef study_chapter(self):"""学习单个章节/小节"""try:menu_container = self.driver.find_element(By.CSS_SELECTOR, "div.menu-container")try:chapters = menu_container.find_elements(By.CSS_SELECTOR, "div.chapter-container.chapter-item")except:chapters=[]try:sections = menu_container.find_elements(By.CSS_SELECTOR, "div.chapter-container.chapter-section-item")except:sections=[]# 判断是否是大章节(有子小节)if len(sections) > 0 :for sid, section in enumerate(sections, start=1):menu_container = self.driver.find_element(By.CSS_SELECTOR, "div.menu-container")sections = menu_container.find_elements(By.CSS_SELECTOR,"div.chapter-container.chapter-section-item")title = sections[sid - 1].find_element(By.CSS_SELECTOR, ".node-name-con").text.strip()print(f"📂 进入大章节: {title}")section_list = sections[sid - 1].find_elements(By.CSS_SELECTOR, ".section-container .node-item")time.sleep(1)self.driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});", sections[sid - 1])# 判断是否已点击if "chapter-active" not in sections[sid - 1].find_element(By.CSS_SELECTOR, "div.chapter-con").get_attribute("class"):# if "chapter-active" not in sections[sid - 1].get_attribute("class"):sections[sid - 1].click()# 找到子小节for sec_idx, section_sec in enumerate(section_list, start=1):menu_container = self.driver.find_element(By.CSS_SELECTOR, "div.menu-container")sections = menu_container.find_elements(By.CSS_SELECTOR,"div.chapter-container.chapter-section-item")sec_list = sections[sid - 1].find_elements(By.CSS_SELECTOR, ".section-container .node-item")status_list = sections[sid - 1].find_elements(By.CSS_SELECTOR, ".section-container .status-con")sec_title = sec_list[sec_idx - 1].find_element(By.CSS_SELECTOR, ".node-name-con").text.strip()if "section-finish" in status_list[sec_idx - 1].get_attribute("class") or "homework-finished-icon" in status_list[sec_idx - 1].get_attribute("class"):status = "已完成 ✅"print(sec_title, status)else:status = "未完成 ⭕"print(sec_title, status, "现在即将学习......")self.driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});", sec_list[sec_idx - 1])time.sleep(2)WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".section-container .node-item")))sec_list[sec_idx - 1].click()self.play_video(sec_title, 2, sec_idx, sid)if len(chapters) > 0:for idx, chapter in enumerate(chapters, 1):menu_container = self.driver.find_element(By.CSS_SELECTOR, "div.menu-container")chapters = menu_container.find_elements(By.CSS_SELECTOR, "div.chapter-container.chapter-item")title = chapters[idx - 1].find_element(By.CSS_SELECTOR, ".node-name-con").text.strip()# 判断是否完成if "chapter-finish" in chapters[idx - 1].get_attribute("class"):status = "已完成 ✅"print(title, status)else:status = "未完成 ⭕"print(title, status, "现在即将学习......")self.driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});", chapters[idx - 1])time.sleep(2)WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".chapter-container .node-item")))chapters[idx - 1].click()self.play_video(title, 1, idx, 0)except Exception as e:print(f"⚠️ 小节异常,跳过: {e}")def play_video(self, title, ctype, index, sid):"""通用视频播放逻辑"""try:# 点击播放按钮try:# 定位视频容器,确保其在可视区域内try:video_container = WebDriverWait(self.driver, 5).until(EC.presence_of_element_located((By.CSS_SELECTOR, "div.video-player-container")) # 视频容器选择器)# 滚动到视频容器可见(scrollIntoView() 会自动将元素滚动到可视区域)self.driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});",video_container)time.sleep(0.5) # 短暂等待滚动完成except:passplay_btn = WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "xg-start .xgplayer-icon-play")))play_btn.click()time.sleep(1)self.set_playback_rate_to_2x()except:play_btn = WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "xg-play .xgplayer-icon-play")))play_btn.click()time.sleep(1)self.set_playback_rate_to_2x()print(f"▶ {title} 播放中...")if not self.is_video_playing_normally():return# 模拟学习# 增加最大播放时长限制(避免无限循环,可根据视频实际时长调整)max_play_time = 1800 # 1小时超时start_time = time.time()while True:# 检查是否超时if time.time() - start_time > max_play_time:print(f"⏰ {title} 播放超时(超过{max_play_time}秒)")breaktime.sleep(random.uniform(2, 3))# 检测是否出现评价弹窗self.handle_popup()# 检测是否出现中断学习弹窗try:self.handle_leave_page_tip()except:pass# 如果有AI选择# self.click_ai_option()# 判断是什么类型的课程 section or chaptermenu_container = self.driver.find_element(By.CSS_SELECTOR, "div.menu-container")if ctype == 1:chapters = menu_container.find_elements(By.CSS_SELECTOR, "div.chapter-container")# if "chapter-finish" in chapters[index - 1].get_attribute("class"):if "finish" in chapters[index - 1].get_attribute("class"):print(f"✅ {title} 已完成")breakelse:sections = menu_container.find_elements(By.CSS_SELECTOR, "div.chapter-container.chapter-section-item")section_list = sections[sid - 1].find_elements(By.CSS_SELECTOR, ".section-container .node-item")# 检查是否完成if len(section_list[index - 1].find_elements(By.CSS_SELECTOR, "div.status-con.section-finish")) == 1 or len(section_list[index - 1].find_elements(By.CSS_SELECTOR, "div.status-con.homework-finished-icon")) == 1:print(f"✅ {title} 已完成")breakexcept Exception as e:print(f"⚠️ {title} 播放失败: {e}")def study_course(self, href):"""学习单个课程"""self.driver.execute_script("window.open(arguments[0]);", href)time.sleep(5)# 切换到新标签页操作self.driver.switch_to.window(self.driver.window_handles[-1])try:if self.have_cretificate():print("该课程已完成!")else:self.driver.switch_to.window(self.driver.window_handles[-1])h1_text = self.driver.find_element(By.TAG_NAME, "h1").textstudy_btn = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "a.course-study-button")))study_btn.click()print(f"🎓 开始学习课程: {h1_text}")time.sleep(5)finished_num, total_num = self.get_progress()if finished_num >= total_num:print(f"✅ 课程 {h1_text} 已完成")else:self.study_chapter()except Exception as e:print(f"⚠️ 课程异常,跳过: {href}, 错误: {e}")finally:self.driver.close()self.driver.switch_to.window(self.driver.window_handles[0])if __name__ == "__main__":bot = AutoCourseBot(USERNAME, PASSWORD)bot.login()links = bot.get_all_course_links(course_type)for i in (1, 3):for t, href in links:bot.study_course(href)
这是基于 Selenium + Chrome 的自动刷网课脚本,核心功能:
从
config.ini读取:登录账号/密码
课程入口类型(通用 / 专业等)
起始网址、学习年份分类等。
自动打开指定学习平台:
自动登录。
进入对应年度 + 对应课程类型的课程列表。
扫描课程列表,收集所有课程链接。
依次打开每门课程:
判断该课程是否已经拿证书 / 完成。
未完成则进入课程学习页面。
自动按章节 / 小节逐个播放视频,设置 2 倍速、处理各种弹窗。
持续监控学习进度,直到系统标记为"已完成"再切下一个。
简单说:用浏览器模拟人点网课、看视频、关弹窗、刷完所有课时。
主要方法
下面按类里方法来讲它们负责的功能。
1. __init__ 构造方法
创建 Chrome 浏览器实例,并开启静音(防止视频声音打扰)。
最大化窗口。
创建显式等待对象,用于后续元素查找。
保存用户名和密码。
作用:初始化浏览器环境和登录信息。
2. handle_popup
检测学习过程中弹出的「评价/积分」之类弹窗。
找到弹窗中的关闭按钮并点击。
等待弹窗彻底消失,避免后续点击被遮挡。
作用:自动关闭学习过程中的干扰弹窗。
3. handle_leave_page_tip
检测是否出现"离开页面/学习中断提醒"这类提示弹窗。
找到弹窗中的"继续学习"按钮并点击。
等待弹窗消失。
随后再尝试点击播放器的播放按钮,让视频继续播放。
作用:处理"你已停止学习,是否继续"这类中断提示,并恢复播放。
4. click_ai_option
当页面出现 AI 题目 / 小测验时:
找到题目区域。
获取选项列表。
随机点一个选项。
作用:模拟用户随机作答,规避系统检测(当前在主流程中是注释掉的,可按需启用)。
5. set_playback_rate_to_2x
找到播放器的倍速控制菜单。
展开倍速选项。
选择 2 倍速。
作用:把视频播放速度调到 2 倍速,加快刷课效率。
6. have_cretificate
在课程详情页区域内查找"证书信息"相关元素。
如果找到,说明课程已经学习完、拿到证书。
作用:判断一门课程是否已经完成,有证书就不再刷。
7. is_video_playing_normally
在播放器控制区域查找"暂停图标"。
如果能看到暂停图标,说明视频当前正在正常播放。
作用:确认视频是否真正处于"播放中"状态,避免页面看起来打开了实际上没播。
8. get_progress
读取页面上的"已完成章节数"和"总章节数"文本。
把里面的数字提取出来并转为整数。
返回
(已完成数, 总数)。
作用:获取当前课程整体进度,用来判断是否已经全部学完。
9. login
打开登录页面(从
config.ini读取的url)。定位账号输入框,填入用户名。
定位密码输入框,填入密码。
点击登录按钮。
输出"登录成功"。
作用:自动登录学习平台。
10. get_all_course_links(self, course_type)
这个是获取课程列表的关键方法:
找到页面上各个"年度模块卡片"(比如 2023、2024 这种)。
根据配置里的
years选择对应年份的卡片。在卡片里找到课程类型入口(比如"通用课/专业课"等),按
course_type索引点击。进入课程列表页后:
在当前页抓取所有
<a>标签。筛选 href 中包含
/course/的链接和文本,存入一个列表(标题, 链接).如果有下一页按钮且未禁用,就点击"下一页",重复抓取。
直到没有下一页为止。
返回所有课程的链接列表。
作用:翻页遍历,收集该年度 + 该类型下的所有课程链接。
11. study_chapter
这是 按章节 / 小节刷课 的主逻辑:
找到左侧课程目录树(菜单容器)。
分两种结构:
逐个章节遍历:
滚动到章节。
点击章节。
调用
play_video播放。如果该章节未完成:
逐个大章节遍历:
判断该小节状态是"已完成"还是"未完成"。
未完成时:
滚动到小节位置。
点击小节。
调用
play_video播放该小节对应的视频。如果大章节未展开则点击展开。
遍历每个子小节:
大章节下面还有小节(带
.chapter-section-item):只有单层章节(
.chapter-item):
作用:按照目录结构,逐个章节/小节触发播放,直到都被系统标记为完成。
12. play_video(self, title, ctype, index, sid)
这是 单个章节/小节的视频播放控制中心:
确保视频区域滚动到可视范围中间。
尝试找到播放按钮点击播放:
优先点击"起播按钮";
如失败,退而求其次点击"播放按钮"。
设置 2 倍速。
判断视频是否正常播放,不正常就直接返回。
进入循环模拟观看:
处理评价弹窗 (
handle_popup)。处理"学习中断/离开页面"弹窗 (
handle_leave_page_tip)。可选:处理 AI 随机答题(当前注释掉)。
根据
ctype判断当前章节是否已完成:如果是章节类型(无子小节),看章节元素是否带有
finish类。如果是小节,检查小节对应的状态图标是否变为"已完成/作业完成"。
设置一个最大播放时长(例如 1800 秒,防止死循环)。
每隔 2-3 秒:
一旦被判断为"已完成",即打印提示并
break。
作用:打开视频、播放、处理弹窗、持续检测完成状态,直到系统认定该节已学完。
13. study_course(self, href)
负责 处理一整门课程:
新开一个标签页打开课程链接。
切换到新标签。
判断这门课程是否已有证书(调用
have_cretificate):读取课程标题。
点击"开始学习/继续学习"按钮进入具体学习页面。
获取课程进度
(已完成节数, 总节数):若已全部完成:打印已完成。
否则调用
study_chapter逐章节刷完。若已完成:打印"该课程已完成",无需刷。
若未完成:
无论成功/异常,最后关闭当前标签页,切回原来的课程列表页。
作用:对单个课程做完整的"是否已完成 → 如未完成则学完 → 返回课程列表"的处理。
14. if __name__ == "__main__": 主流程
创建
AutoCourseBot实例,传入配置里的用户名、密码。调用
login()自动登录平台。调用
get_all_course_links(course_type)获取所有课程链接。外层
for i in (1, 3):循环(相当于跑两轮):内层遍历
links列表,依次对每个(标题, href)调用study_course(href)。
实际效果:登录 → 获取目标课程列表 → 将列表中的所有课程刷一遍(当前写法是循环两轮)。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论