分享一份智慧树刷课脚本(包括直播课)

这个脚本的根本原则是完全的自动化。除了第一次使用需要花些时间配置,后续就只要点击运行即可,登陆、跳转、关闭等等功能都会自动完成。

为了达成这个效果,主要实现了以下功能:
基础部分:
 
1. 首次配置脚本
分享一份智慧树刷课脚本(包括直播课)
配置内容包括账号、密码、课程链接和部分Chromedriver configs(静音、无头模式)
第一次配置完成后,输入的信息会被保存在同目录下的zhh_config.txt文件中,后续只要这个文件存在,就不再需要任何输入。如果想更改信息,直接更改txt文件即可。
2. 自动输入账号(手机号)和密码并登录
 
分享一份智慧树刷课脚本(包括直播课)

这个不多说了,模拟登陆都实现不了的话还谈什么刷课。

3. 根据输入的URL自动判断类型

对于输入的URL,脚本会自动判断类型(目前只有Lesson课程和Live直播两种),根据不同的类型调用不同的类进行处理。


针对不同的视频类型,进行以下处理:
课程视频:
 
1. 自动跳过警告和学前必读
 
分享一份智慧树刷课脚本(包括直播课)
分享一份智慧树刷课脚本(包括直播课)
这个智慧树警告我觉得挺有毒的...而且还每次都会跳出来,无语,所以在开始刷课之前必须要检测窗口,然后关掉。
2. 自动点击并关闭视频中跳出的问题界面(答对是不可能的)
 
分享一份智慧树刷课脚本(包括直播课)

手动挂机刷智慧树视频不可行,就在于视频中经常会跳出不计分的问题,且关闭问题之后,视频并不会自动继续播放。

所以脚本就很有必要了,利用脚本,可以实时监控跳出的问题,并点击第一个选项(反正不计分)后关闭,再自动点击播放按钮实现不间断后台刷课。

3. 自动续播
 
分享一份智慧树刷课脚本(包括直播课)

这听起来是一个很蠢的功能,但实际上,智慧树的课程视频播放结束后并不会自动下一节。这大概率也是为了防止挂机有意为之,所以脚本加入这个功能也是必须的。

3. 自动关闭视频中跳出的今日时长提示
 

分享一份智慧树刷课脚本(包括直播课)

 

我没有具体计时,但这个跳出的时间应该远远大于25分钟,一般出现这个的话就可以关了。但如果想要继续看视频,就仍然需要用脚本把这个提示框关掉,再点击视频播放按钮。

4. 自动定时30分钟关闭

虽然可以在跳出今日时长提示后就关闭,但为了防止时间不够,我还是设置了定时半小时关闭。

5. 在console显示

(1)当前视频标题

(2)实时进度更新

(3)观看时间提示(3分钟1次)

分享一份智慧树刷课脚本(包括直播课)

直播视频:
1. 自动关闭提示
 
分享一份智慧树刷课脚本(包括直播课)

无语+1,这些提示为什么每次都要跳出来啊?

 
 
2. 跟踪并显示签到进度
分享一份智慧树刷课脚本(包括直播课)

在直播页面的下方有一个蓝色的签到进度,显示了这堂直播课已经观看的百分比。这对于下一个功能的实现有极大帮助。

3. 自动前进至签到上次位置

一节直播课时间很长,大概2个多小时,因此很难一次刷完。但和课程视频不同,直播视频每次重新进入都会从00:00:00开始,非常影响效率。

为了解决这个问题,我设置了自动加速,当视频位置在签到位置之前,视频会以每秒2分钟速度加速播放,到了签到位置后则恢复正常速度,因为没有视频,所以只能意会了...这部分也是最复杂的,我想了很多方法,最后找到了一个可以用的JS指令。


部分功能实现方法说明:
全说篇幅太长了,稍微挑几个重要的。

1. 实时进度条:

以Live.show_progress()为例:
def show_progress(self):   progress=self.dr.find_element_by_xpath('//*[@id="container"]/div[1]/div/div[2]/div[1]/p[2]/span').get_attribute('textContent')   print("目前签到进度:"+progress+"%")   while True:       time.sleep(1)#每间隔1秒检测视频是否播放完       try:       #该视频的总时间           total_time=self.dr.find_element_by_xpath('//*[@id="vjs_forFollowBackDiv"]/div[10]/div[4]/span[2]').get_attribute('textContent')                 #获取当前播放的进度           current_time=self.dr.find_element_by_xpath('//*[@id="vjs_forFollowBackDiv"]/div[10]/div[4]/span[1]').get_attribute('textContent')                                  print("进度:{0}/{1}".format(current_time,total_time), end="r") 
          if current_time!='00:00:00' and total_time!='00:00:00':               if current_time[3:5]==total_time[3:5] and int(current_time[6:])>=(int(total_time[6:])-2):                   print('n')                   print('已完成本次直播',end='n')                   self.dr.quit() #退出                   quit() 
      except:          current_time = '00:00'           total_time = '00:05' #随意填,两个不同就行
并不复杂,主要就是每隔一秒,找到网页中的 current_time 和 total_time 两个元素提取出文本,然后按格式 print 出来。
唯一需要注意的是 print 最后的 end=’r’ ,这表示回车,回到某一行的开头,这样可以覆盖前一秒的进度,看起来就像是实时更新了。

2. 直播加速播放:

先看实现的代码:

def play_time(self):   while True:       time.sleep(0.5)       try:           progress=self.dr.find_element_by_xpath('//*[@id="container"]/div[1]/div/div[2]/div[1]/p[2]/span').get_attribute('textContent'                    total_time=self.dr.find_element_by_xpath('//*[@id="vjs_forFollowBackDiv"]/div[10]/div[4]/span[2]').get_attribute('textContent')             total_sec=Live.str2sec(total_time)  
          current_time=self.dr.find_element_by_xpath('//*[@id="vjs_forFollowBackDiv"]/div[10]/div[4]/span[1]').get_attribute('textContent')                                 current_sec=Live.str2sec(current_time) 
          sign_sec=int(total_sec*int(progress)/100) #最后签到的时间           iterate=int((sign_sec-current_sec)/60)           for i in range(iterate):               self.dr.execute_script("document.getElementsByTagName('video')[0].currentTime = document.getElementsByTagName('video')[0].currentTime + 60")               time.sleep(1)               i+=1               if i==iterate:                   print('n')                  print('已到达上次观看位置', end='n')                   break       except:          pass
这是一个障眼法,实际并不是加速播放,而是每隔0.5秒将进度条向右拖1分钟,实现方法是这样一个JavaScript:
document.getElementsByTagName('video')[0].currentTime =  document.getElementsByTagName('video')[0].currentTime + 60 
只要进行条件判断,符合条件的话就不间断执行这条脚本,看起来就像是加速播放了。
3. 多线程:
上述功能几乎都是并行的,仅靠一个线程难以实现,所以我使用了多线程,这可以从主函数中看出:
def main():     configs=config()     configs=[i.strip('n') for i in configs]     logins=login(configs)     driver=logins[0]     class_type=logins[1] 
    threads=[]     if class_type=='lesson':         Zhihuishu=Lesson(driver)        threads.append(threading.Thread(target=Zhihuishu.is_exist))        threads.append(threading.Thread(target=Zhihuishu.show_progress))        threads.append(threading.Thread(target=Zhihuishu.close_web))        threads.append(threading.Thread(target=Zhihuishu.close_timeup))
    elif class_type=='live':         Zhihuishu=Live(driver)        threads.append(threading.Thread(target=Zhihuishu.show_progress))        threads.append(threading.Thread(target=Zhihuishu.play_time))
    else:         raise Exception('请检查输入的链接,需包含http头') 
    for thr in threads:         thr.setDaemon(True)         thr.start() 
    for thr in threads:         if thr.isAlive: #阻止主线程直接结束             thr.join()

首先进行视频类型的判断。

对于课程视频(Lesson类),我定义了五个函数实现功能,自动跳过警告和学前必读的 skip_iknow() 是最先触发的,因此在 __init__() 中运行。

而剩下四个是同时进行的,为了防止它们相互干扰,我建立了4个子线程并行处理。直播视频(Live类)也是类似。
对子线程的要求是:每个子线程既可以完整运行(即主线程不会提前结束),又可以在主线程结束后同时结束。
 
对于第一点,考虑使用 Thread.isAlive 判断每个子线程是否活跃,如果活跃,则利用 Thread.join() 将该子线程再次并入,保证主线程始终处于阻塞状态,不会自动终止;
对于第二点,只要将每个子线程都设为守护线程,即可保证当主线程终止时,子线程也全都结束。

一个提问:

身边有很多人也在学Python,所以这里留一个问题,有兴趣的可以想想:
下面这段是Live类中的 _init_ 和 is_element_present 两个方法:
class Live():    def __init__(self, dr):        self.dr=dr        self.dr.implicitly_wait(10)        time.sleep(2)        WebDriverWait(self.dr, 10, 0.5).until_not(EC.presence_of_element_located((By.XPATH,'//*[@id="popbox_title"]')))        if not Live.is_element_present(self.dr, By.XPATH, '//*[@id="popbox_title"]'):            #首次设置流畅            self.dr.execute_script('document.querySelector("#vjs_forFollowBackDiv > div.controlsBar > div.definiBox > div > b.line1bq.switchLine").click()')            title=self.dr.find_element_by_xpath('//*[@id="wh_live_name"]').get_attribute('textContent')            print("正在播放回放:"+title)
    def is_element_present(driver, by, value): #判断网页元素是否存在        try:            element = driver.find_element(by=by, value=value)        except NoSuchElementException:            return False        return True
其中第7行,进行判断时使用了
Live.is_element_present(self.dr, By.XPATH, '//*[@id="popbox_title"]'):
这个语句,其中调用了 is_element_present 这个方法。

那么问题来了,

为什么使用的是Live.is_element_present() 而不是 self.is_element_present() 呢?

 
能回答出来这个问题的话,就说明对类和类方法的理解还是比较深入的。

最后:
这个脚本使用的第三方库只有 selenium 和 webdriver_manager 两个
from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.common.exceptions import NoSuchElementExceptionfrom webdriver_manager.chrome import ChromeDriverManagerimport time, threading, re, os
webdriver_manager 的用处是自动配置 ChromeDriver 简化操作。如果有人需要这个脚本的话,可以找我。
电脑里有 Python 3.8+环境和 Chrome,再安装这两个库应该就可以跑了,当然肯定还有Bug。
至于源码...太长这次就不发了,反正发了也没人看。
温馨提示:本站提供的一切软件、教程和内容信息都来自网络收集整理,仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负,版权争议与本站无关。用户必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。如果您喜欢该程序和内容,请支持正版,购买注册,得到更好的正版服务。我们非常重视版权问题,如有侵权请邮件与我们联系处理。敬请谅解!
学习资讯智慧树热门课程

智慧树刷课代码整合包下载

2025-4-9 9:57:30

智慧树热门课程

快速刷满智慧树互动分的方法,亲测有效

2025-4-9 10:05:26

搜索