前言

再次临近升学考试,网课学习占总成绩的30%,需要耗费大量的时间精力来挂课,故编写该脚本来平替人工刷课,如果你也是刷的青岛理工大学继续教育学院的课程,可以直接拿来使用,如果用不着,就当看个乐子,我也只当做留存,以后有代码可以复用的地方可以再回来参考。

运行环境

  • Win 10
  • Chrome 浏览器 版本 125.0.6422.142(正式版本) (64 位)
  • Python 3.12.3

上面只是我本地开发时用到的环境,没有做太多的适配,理论上只要版本不要过低应该问题不大,不需要刻意指定浏览器和python版本(3.7+)

运行效果

image-1718356341239
1718071489682

代码

下面的代码没有太高的技术难度可言,细节部分没有过多的优化,不想浪费太多时间在这上面

import time
from datetime import datetime

from selenium import webdriver
from selenium.common import NoSuchElementException, TimeoutException
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait


def start():
    # 设置Chrome选项
    chrome_options = Options()
    # 设置不自动关闭浏览器
    chrome_options.add_experimental_option('detach', True)
    # 实例化Chrome驱动并设置选项
    driver = webdriver.Chrome(options=chrome_options)
    # 打开网页并执行操作...
    driver.get('http://cj.qutjxjy.cn/login')
    # 校验页面是否跳转
    checkPageHasJump(driver)
    # 开始刷课
    goToPlay(driver)
    print("开始刷课")
    while True:
        currentTab = getCurrentTab(driver)
        if currentTab.get_attribute("id") == "dct3":
            driver.execute_script("arguments[0].click();", getNextBtn(driver))
        # 判断视频是否暂停播放
        if (currentTab.get_attribute("id") == "dct1") & ("vjs-paused" in getVideoDivClass(driver)):
            print("当前视频暂停播放")
            # 切换到顶部DOM
            driver.switch_to.default_content()
            # 定位到有video的iframe
            driver.switch_to.frame(
                WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "iframe"))))
            print("切换到id=iframe的iframe")
            # 继续钻入
            driver.switch_to.frame(0)
            print("切换到id=iframe的子iframe")
            time.sleep(5)
            # 执行JavaScript代码,点击播放按钮,并以2.0倍速播放
            print("尝试通过js脚本播放视频")
            driver.execute_script("""
            var video = $(document).find("video")[0]
            if (!!video && video.playbackRate) {
                if (!video.paused) { // 如果视频当前没有暂停,则先暂停再播放
                    video.pause();
                    video.muted = true;//静音
                }
                video.playbackRate = 2.0; // 设置播放速度为2.0倍
                video.play(); // 开始播放视频
            }
            """)
            try:
                palyBtn = WebDriverWait(driver, 10).until(
                    EC.presence_of_element_located((By.CSS_SELECTOR, ".vjs-big-play-button")))
                driver.execute_script("arguments[0].click();", palyBtn)
            except TimeoutException:
                print("没有找到播放按钮,")
            print("继续播放当前视频")
        # 校验当前视频是否播放完成
        if checkVideoIsPalyCompleted(driver):
            print("当前视频播放完成")
            # 判断视频是否全部播放完成
            if checkAllVideoIsComplete(driver):
                print("当前课程所有视频都已播放完成,准备回到首页,切换别的课程学习")
                # 关闭当前窗口
                driver.close()
                print("关闭当前窗口")
                # 切换到最近的窗口
                driver.switch_to.window(driver.window_handles[-1])
                print("回到首页")
                # 刷新当前页面
                driver.refresh()
                print("刷新首页")
                # 开始刷课
                goToPlay(driver)
                print("开始刷课")
            # 获取【下一节】按钮并点击
            driver.execute_script("arguments[0].click();", getNextBtn(driver))
            print("点击【下一节】按钮")
        # 视频未播放完成
        else:
            print(datetime.now().strftime('%Y-%m-%d %H:%M:%S') + ":当前视频暂未播放完成,继续等待...")
            time.sleep(5)
        # 如果有弹窗,则点击弹窗里的【去学习】按钮
        isOpenModal(driver)

def getCurrentTab(driver):
    driver.switch_to.default_content()
    return WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, 'span.currents')))

def isOpenModal(driver):
    # 切换到顶部DOM
    driver.switch_to.default_content()
    print("切换到顶部DOM")
    try:
        modal = driver.find_element(By.ID, "jobFinishTip")
    except NoSuchElementException:
        print("没找到弹窗")
        return
    display_style = modal.value_of_css_property("display")
    isOpen = display_style != "none"
    print("当前是否弹窗:" + ("是" if isOpen else "否"))
    if isOpen:
        # 尝试查找单个匹配的'a.nextbutton'元素
        next_button = driver.find_element(By.CSS_SELECTOR, "a.nextChapter")
        # 如果找到元素,则点击它
        driver.execute_script("arguments[0].click();", next_button)
        print("找到了弹窗里的【去学习】按钮,并点击了它")


def goToPlay(driver):
    # 等待首页-菜单栏【课程】按钮加载完成并点击
    WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "icon-kc"))).click()
    print("点击【首页】-【菜单栏】-【课程】按钮")
    # 定位到iframe并切换(由于【进入学习】按钮在内嵌的iframe中,故需要切换一下iframe】)
    driver.switch_to.frame(WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "frame_content"))))
    print("切换到id=frame_content的iframe")
    # 等待页面中【进入学习】按钮加载完成并点击,点击后会新开tab页面,下面需要切换到新开的窗口
    getJoinStudyBtn(driver).click()
    print("点击【右侧内容区】-【进入学习】按钮")
    # 获取所有窗口句柄,通常初始页面的句柄为0,新打开的页面句柄为1,依此类推
    all_windows = driver.window_handles
    # 切换到最新打开的窗口,即索引为-1的窗口
    driver.switch_to.window(all_windows[-1])
    print("跳转到【进入学习】的新开页")
    assert "-首页" in driver.title
    print("新开页加载完成")
    # 获取首页-课程详情页面(【进入学习】页)的,列表中第一个黄色小圆点
    em = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, ".orange")))
    # 点击黄色小圆点上层的a标签
    getA(em).click()
    print("点击课程列表中第一个小黄点的链接")


# 查找第一个【进入学习】按钮
def getJoinStudyBtn(driver):
    # 查找所有class为"percent"的div元素
    percent_divs = WebDriverWait(driver, 10).until(
        EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'div.percent')))

    # 遍历每一个找到的percent_div
    for div in percent_divs:
        # 在当前div下查找title不包含"100"的span元素
        try:
            span = div.find_element(By.XPATH, './/span[not(contains(@title, "学习进度:100.0"))]')
            # 定位到该span的父级的父级的父级元素
            third_parent = span.find_element(By.XPATH, 'ancestor::div[2]')

            # 在这个父元素下查找样式为"study ddsubstyle"的a标签
            return third_parent.find_element(By.CSS_SELECTOR, 'a.study.ddsubstyle')

        except NoSuchElementException:
            print("未找到符合条件的span元素或a标签。")
            continue


def checkVideoIsPalyCompleted(driver):
    # 定位到顶部
    driver.switch_to.default_content()
    # 定位到有video的iframe
    driver.switch_to.frame(WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "iframe"))))
    return "ans-job-finished" in WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, ".ans-attach-ct"))).get_attribute("class").split()


def getVideoDivClass(driver):
    # 定位到顶部
    driver.switch_to.default_content()
    print("切换到顶层DOM")
    # 定位到有video的iframe
    driver.switch_to.frame(WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "iframe"))))
    print("切换到id=iframe的iframe")
    # 继续钻入
    driver.switch_to.frame(0)
    print("切换到id=iframe的子iframe")
    # 找到视频div
    try:
        video = WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.ID, "video")))
        return video.get_attribute("class").split()
    except TimeoutException:
        print("当前不是教学视频tab。")
        return ""


def getNextBtn(driver):
    driver.switch_to.default_content()
    try:
        otherVideoBtn = WebDriverWait(driver, 5).until(
            EC.presence_of_element_located((By.XPATH, "// *[starts-with(@id, 'dct')][last()]"))
        )
        driver.execute_script("arguments[0].click();", otherVideoBtn)
        print("切换到最后一个tab页")
    except TimeoutException:
        print("没找到其他视频按钮,准备【教学视频】tab下的【下一节】按钮")
    return WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "right1")))

def checkAllVideoIsComplete(driver):
    driver.switch_to.default_content()
    return len(driver.find_elements(By.CSS_SELECTOR, "span.orange01")) < 1


def getA(em):
    # 获取 .orange 元素的父元素链路
    parent_elements = em.find_elements(By.XPATH, "../..")
    # 遍历父元素,直到找到一个 <a> 标签为止
    for parent in parent_elements:
        if parent.tag_name == 'a':
            return parent
    # 确保找到了 <a> 标签的父元素
    if 'a' not in locals():
        raise Exception("没有找到<em class='orange'>元素上级的<a>标签")


def checkPageHasJump(driver):
    # 定义等待时间和间隔时间
    wait = WebDriverWait(driver, 10)  # 最大等待时间为10秒
    # 每隔一秒检查页面是否跳转
    while True:
        try:
            # 使用WebDriverWait来检查页面标题是否包含特定关键字,这里以"example"为例
            wait.until(EC.title_contains("青岛理工大学继教学院"))
            print("登录成功,页面已跳转...")
            break
        except:
            print("等待用户完成登录...")
            # 每隔1秒检查一次
            time.sleep(1)


# 按装订区域中的绿色按钮以运行脚本。
if __name__ == '__main__':
    start()

# 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助

打包成可执行文件

  1. 安装 pyinstaller
pip install pyinstaller
  1. 打包:
    • –onefile: 这是一个选项,它告诉 PyInstaller 将所有的内容(包括你的脚本、依赖的库等)打包到一个单独的可执行文件中。如果不使用这个选项,PyInstaller 通常会创建一个包含多个文件和目录的文件夹。
    • –debug=all: 这是一个带有参数的选项,其中 --debug 表示你想要启用调试模式,而 all 是这个选项的参数,表示你想要启用所有的调试信息。这通常用于在打包过程中或运行打包后的程序时获取更多的调试输出,以帮助诊断问题。
    • .\main.py: 这是你想要打包的 Python 脚本的路径。. 表示当前目录,\main.py 是你的脚本文件名。注意在 Unix-like 系统(如 Linux 或 macOS)中,路径分隔符是 / 而不是 \,但在 Windows 中你可以使用 \ 或 /。
    • -p “C:\Users\Administrator\AppData\Local\Programs\Python\Python311\Lib\site-packages”: 这是一个选项,用于指定额外的 Python 搜索路径。-p 或 --paths 选项后面跟着的是路径列表,这些路径会被添加到 Python 的 sys.path 中,这样 PyInstaller 就能在这些路径下找到你的脚本所依赖的库。在这个例子中,你指定了 Python 3.11 的 site-packages 目录作为额外的搜索路径。
pyinstaller --onefile --debug=all .\main.py -p "C:\Users\Administrator\AppData\Local\Programs\Python\Python311\Lib\site-packages"

注意事项

此脚本仅供交流学习使用,严禁商业用途

切勿随意点击页面

当脚本运行起来后,出了登录,不建议再有其他操作,比如点击页面的各个按钮,可能会导致脚本操作逻辑混乱

包含测验题的课程

部分课程可能包含测验题,这个时候脚本会默认先将所有课程视频刷完,如果这个时候测验题没有答完,脚本是不会继续刷下一节课的,这里需要注意一下!!

程序调用浏览器驱动超时

由于较新的谷歌浏览器默认开启自动释放内存模式,针对了打开单未使用的网站会自动清理该页签的内存,故,如果遇到脚本报错,打开Chrome浏览器按照如下方式配置即可:
1718069970057

脚本更新

关于该网课的网站,就目前来说更新频率是很低的,至少在我上这两年网课期间没有见到过什么大的更新,脚本也很简单,就算到时候有更新,看看代码稍作修改即可