2024/3/15 rebuild-3.8 更新
  • 优化了进度条判定逻辑, 当右侧圆环进度为100%时判定为已完成

  • 播放时自动设置为1.5倍速并切换流畅画质

此次更新应当能解决”切换小节时提示课程已完成”的Bug, 建议更新此版本 !


Autovisor

智慧树视频课辅助工具,开启挂机摸鱼时代~

新学期必备干货, 建议收藏备用 !!

项目主页:CXRunfree/Autovisor(github.com)(不妨点个star吧)

程序介绍

这是一个可无人监督的自动化程序,由Python和JavaScript编写而成。相对于油猴脚本,本程序可有效防止被网页检测。

核心原理是使浏览器模拟用户的点击操作, 不会导致封号等问题

程序功能

  • 可以快速登录

  • 自动播放和切换下一集

  • 跳过弹窗和弹出的题目

  • 自动静音、调整1.5倍速和流畅画质

  • 检测视频是否暂停并续播(不用担心视频意外暂停了~)

  • 检测当前学习进度并后台实时更新

  • 根据当前时间自动设置背景颜色(白昼/暗夜)

  • 加入了定时模拟鼠标滑动功能 (减少被检测到的概率)
  • 完成章节时将提示已刷课时长

使用须知(重要 !!)

1.请确保系统为windows10及以上

  • 默认启动Edge(win10以上系统会自带);
  • 请确保Edge或Chrome安装在系统默认位置

​2.文件夹内有account.json文件(可能没显示.json后缀名),请用文本编辑器打开;

3.填写配置文件

  • “User”:输入你的账户名
  • “Password”:输入你的密码
  • “Driver”:指定启动的浏览器(可选Chrome);
  • “Url”:输入网课的具体网址,保存后关闭,例如:

注意:

  • 此脚本仅支持共享课视频, 网址格式与需下面一致, 填入时请看仔细。

  • 只能使用英文标点

​4.运行程序,会自动打开浏览器界面,滑块验证时请稍等片刻,进入网课界面后就可以自动刷课啦~ (~ ^-^ ~)

​ 注: 登录界面的滑块验证请手动完成


发行版下载:

Github: Releases · CXRunfree/Autovisor (github.com) (留下一个免费的star吧” />[蓝奏云] Autovisor-for-windows 密码:492l

为便于阅读, 源码已放在文末

如有疑问,可以在评论区留言, 每条留言作者都会认真看的 !

(报错问题请附上报错信息,在log.txt文件内)

常见问题

1.为什么会出现一个命令行黑框?

  • 这是程序运行的后台,你可以查看当前运行的状态

2.为什么网页一片空白/无法加载课程界面,一段时间后程序就退出了?

  • 大概率你没有在account文件里填入课程的网址;

    此外从登录完成后到进入课程界面的过程不需要鼠标点击

3.为什么运行程序只出现后台却没出现浏览器界面?

  • 只要后台未异常退出就不必担心; 如果出错可能是你的浏览器安装路径有问题

已知Bug:

  • 长时间挂机有概率弹出人机验证, 如果一个半小时内未通过验证, 程序将自动结束进程;
  • 若出现其他异常崩溃,请在Github提交issue并附上日志文件log.txt的信息;

碎碎念:

觉得体验还不错? 来给项目发电支持一下吧~!

(其实作者也要吃饭的 ^-^)

注意:本程序只可用于学习和研究计算机原理(你懂的)

还等什么” /># encoding=utf-8import asyncioimport osimport reimport tracebackimport jsonimport timefrom json import JSONDecodeErrorfrom playwright.sync_api import sync_playwrightfrom playwright._impl._errors import TargetClosedError, TimeoutError# constantslogin_url = “https://passport.zhihuishu.com/login”# Xpathoption1 = ‘//*[@id=”playTopic-dialog”]/div/div[2]/div/div[1]/div/div/div[2]/ul/li[1]/div[2]’option2 = ‘//*[@id=”playTopic-dialog”]/div/div[2]/div/div[1]/div/div/div[2]/ul/li[2]/div[2]’# javascript# 登录login_js = ”’document.getElementsByClassName(“wall-sub-btn”)[0].click();”’block_js = ”’return document.getElementsByClassName(“yidun_jigsaw”)[0].src”’bg_js = ”’return document.getElementsByClassName(“yidun_bg-img”)[0].src”’# 弹窗pop_js = ”’document.getElementsByClassName(“iconfont iconguanbi”)[0].click();”’# pop2_js = ”’document.evaluate(‘//*[@id=”app”]/div/div[1]/div[1]/span/a’,document).iterateNext().click();”’# 其他night_js = ”’document.getElementsByClassName(“Patternbtn-div”)[0].click()”’def auto_login(_user, _pwd):if not user or not pwd:raise UserWarningpage.goto(login_url)page.locator(‘#lUsername’).fill(_user)page.locator(‘#lPassword’).fill(_pwd)page.wait_for_timeout(500)page.evaluate(login_js)def init_page():# 启动自带浏览器if driver == “Chrome”:print(“正在启动Chrome浏览器…”)browser = p.chromium.launch(channel=”chrome”, headless=False)else:print(“正在启动Edge浏览器…”)browser = p.chromium.launch(channel=”msedge”, headless=False)context = browser.new_context()page = context.new_page()# 设置程序超时时限page.set_default_timeout(300 * 1000 * 1000)# 设置浏览器视口大小viewsize = page.evaluate(”'() => { return {width: window.screen.availWidth,height: window.screen.availHeight};}”’)viewsize[“height”] -= 50page.set_viewport_size(viewsize)return pagedef optimize_page():# 关闭学习须知page.evaluate(pop_js)# 根据当前时间切换夜间模式hour = time.localtime().tm_hourif hour >= 18 or hour < 7:page.wait_for_selector(".Patternbtn-div")page.evaluate(night_js)try:# 关闭上方横幅page.wait_for_selector(".exploreTip", timeout=500)page.query_selector('a:has-text("不再提示")').click()finally:returndef get_lesson_name():title_ele = page.wait_for_selector("#lessonOrder")page.wait_for_timeout(500)title_ = title_ele.get_attribute("title")return title_def move_mouse(elem):elem.hover()pos = elem.bounding_box()# 计算移动的目标位置target_x = pos['x'] + 30target_y = pos['y'] + 30page.mouse.move(target_x, target_y)def get_progress():curt = "0%"canvas = page.wait_for_selector(".videoArea")move_mouse(canvas)progress = page.query_selector(".current_play").query_selector(".progress-num")if not progress:finish = page.query_selector(".current_play").query_selector(".time_icofinish")if finish:curt = "100%"else:curt = progress.text_content()return curtdef check_play():canvas = page.wait_for_selector(".videoArea")move_mouse(canvas)canvas.click()def video_optimize():canvas = page.wait_for_selector(".videoArea")move_mouse(canvas)page.wait_for_selector(".volumeBox").click()# 设置静音page.wait_for_selector(".definiBox").hover()# 切换流畅画质low_quality = page.query_selector(".line1bq")low_quality.hover()low_quality.click()page.wait_for_selector(".speedBox").hover()# 切换1.5倍速max_speed = page.query_selector(".speedTab15")max_speed.hover()max_speed.click()def play_next():canvas = page.wait_for_selector(".videoArea")move_mouse(canvas)next_but = page.wait_for_selector("#nextBtn")page.wait_for_timeout(200)next_but.click()def skip_question():try:page.wait_for_selector(".topic-item", timeout=2000)choices = page.query_selector_all(".topic-item")choices[0].click()choices[1].click()page.wait_for_timeout(500)page.query_selector_all(".btn")[3].click()except TimeoutError:returndef main_function():# 进行登录print("等待登录完成…")auto_login(user, pwd)# 等待完成滑块验证,已设置5min等待时间page.wait_for_selector(".wall-main", state="hidden")# 遍历所有课程,加载网页for course_url in urls:id_pat = re.compile("recruitAndCourseId=[a-zA-Z0-9]+")matched = re.findall(id_pat, course_url)if not matched:print(f"\"{course_url.strip()}\"\n不是一个有效网址,即将自动跳过!")continueprint("开始加载播放页…")page.goto(course_url)page.wait_for_selector(".studytime-div")# 关闭弹窗,优化页面体验optimize_page()# 获取当前课程名course_title = page.wait_for_selector(".source-name").text_content()print(f"当前课程:<>”)start_time = time.time()# 记录开始学习时间while True:# 获取课程小节名title = get_lesson_name()print(“正在学习:%s” % title)# 根据进度条判断播放状态curtime = get_progress()check_play()# 开始播放video_optimize()# 对播放页进行初始化配置page.set_default_timeout(2000)while curtime != “100%”:try:skip_question()# 跳过中途弹题(只支持选择题)playBut = page.query_selector_all(“.pauseButton”)curtime = get_progress()if not playBut and curtime != “100%”:check_play()print(“当前小节未刷满,将继续播放..”)title = get_lesson_name()print(“正在学习:%s” % title)else:print(‘完成进度:%s’ % curtime)page.wait_for_timeout(2000)except TimeoutError:input(“进度获取超时,可能存在安全验证?\n按Enter继续:”)page.set_default_timeout(300 * 1000 * 1000)title = get_lesson_name()play_next()# 进度100%时开始下一集time_period = (time.time() – start_time) / 60if time_period >= 1:# 每完成一节提示一次时间print(“本次课程已学习:%.1f min” % time_period)# 如果当前小节是最后一节代表课程学习完毕all_class = page.query_selector_all(“.clearfix.video”)class_name = all_class[-1].get_attribute(‘class’)if “current_play” in class_name:print(“已学完本课程全部内容!”)print(“==” * 10)breakelse:# 否则为完成当前课程的一个小节print(f”\”{title}\” Done !”)page.wait_for_timeout(1000)if __name__ == “__main__”:print(“===== Runtime Log =====”)try:print(“正在载入数据…”)with open(“account.json”, “r”, encoding=”utf-8″) as f:account = json.loads(f.read())user = account[“User”].strip()pwd = account[“Password”].strip()driver = account[“Driver”].strip()urls = account[“Url”]if not isinstance(urls, list):print(‘[Error]”Url”项格式错误!’)raise KeyErrorwith sync_playwright() as p:page = init_page()main_function()print(“==” * 10)print(“所有课程学习完毕!”)input()except Exception as e:if isinstance(e, JSONDecodeError):print(“[Error]account文件内容有误!”)elif isinstance(e, KeyError):print(“[Error]可能是account文件的配置出错!”)elif isinstance(e, UserWarning):print(“[Error]是不是忘记填账号密码了?”)elif isinstance(e, FileNotFoundError):print(“[Error]程序缺失依赖文件,请重新安装程序!”)elif isinstance(e, TargetClosedError):print(“[Error]糟糕,网页关闭了!”)elif isinstance(e, TimeoutError):print(“[Error]网页长时间无响应,自动退出…”)else:print(f”[Error]{e}”)with open(“log.txt”, “w”, encoding=”utf-8″) as doc:doc.write(traceback.format_exc())print(“错误日志已保存至:log.txt”)print(“系统出错,要不重启一下?”)input()