“我不是课神”V1.5.2——学习通刷课工具

        相信大家已经看过标题了,“我不是课神”V1.5.2,我花了两天时间,做了这个刷网课的“工具”。我们在最开头还是要呼吁大家,如果说一门网课真的对自己很重要,我们还是不要动什么歪心思,我的初衷和出发点还是以学习为主,做这样的小项目就是我的自我时间。我也在网上查阅了不少资料,了解了这样做的后果和背后的法律标准。所以我非常非常建议各位同学们,不要去做代刷网课,因为自己刷网课还是个人行为,不要上升到社会层面。有些课对于区分度大的专业来讲营养就是不够的,我们更希望把时间投入在更有价值的地方,所以才会有这么多人有刷网课的需求。

一、立项和思路

        好,讲了这么多,就谈谈项目本身吧!

        前两天我在B站上看到一则视频,来自于up主:不高兴就喝水

        视频链接:5分钟,教你做个自动化软件拿来办公、刷副本、回微信 | 源码公开,开箱即用

        这条视频讲述了他利用Python的两个库pyautogui和xlrd,做了几个自动化的办公软件。比如说:自动填写excel表格,自动打地鼠,自动微信回复......通过识别图像的方法让程序对对应位置进行点击操作,从而实现自动化。

        我一看到就觉得这个方法很不错,能做超级多的拓展。我对于游戏的敏感就让我想到了他可以用来做游戏脚本,当然这是另外一个项目组同学的任务——我呢,最近被刷网课所困扰,所以我打算试试看用这个方法来做一个可以帮我自动刷网课的脚本。

        这个脚本的核心不在于逆向的读取,而在于对人行为的模拟,相当于完全不清楚我们要处理的程序内部是什么样子,我们只是让一个机器人去按下另一个机器的按钮,从而达成自动化。

        看到这几张图片,显然各位也能够理解我工具的原理啦,接下来我们就讲讲流程吧。

二、框架与实现

        在与ai的交流过程中,我发现它并不能有效理解整个框架和套入其中的循环,因此我选择一步步地线性制作流程——先一步步完善单次课程的自动化功能,再建立整个大循环,让他从“听好一节课”开始,到能“连续听课”。

        

        先从识别开始按钮,点击播放开始一次课程的循环。

        先要清楚什么时候算开始播放,什么时候结束了播放,这就是一次循环。

        线性推进功能,在每个功能的衔接上,逻辑必须非常严谨,所有情况全要考虑。

        课程中还会有答题的情况出现,我们必须想办法通过答题,否则播放就会停止。

        好在课中的题目不计分数,我们可以枚举解决。在2.0版本之前,我只能处理两个选项的题目,因为我目前只遇到了这样的情况。还有ABC,ABCD单选;ABC,ABCD多选我还没有做相应的处理。其实也很好实现,只要你有那么一点点的耐心去理解这个过程——单选选项多了无非就是多一轮枚举,而多选我们就需要一些排列组合上的枚举,比如三个选项需要我们进行最多C32+C33次的枚举,而四个选项的多选至少需要C42+C43+C44=11次枚举,有耐心还是可以做到的。视同学们的反馈和必要性我再继续更新这一点。不过我现在已经能用的很爽了。

        我在过程中把做题单独归类为一个函数,在播放过程中持续运行。

        完成了播放后,我们需要一些滚轮和点击的操作,让他能够重新显示开始播放的那个场景,完成一个循环的闭环,这才能方便我们以后的自动化连续的实现。

这里已经完成了播放,我们跳转后必须要找到下一节的按钮。

甚至还有特殊情况,就是当你没有做完“测试”的时候:

        我这里立项的时候就没有做“自动完成课后测试”这个功能,从实现上讲,这样做一定会涉及到逆向,这是我所不敢接触的,到时候给我发函了...难度和繁琐程度也会提高。再说了,我只是模拟一些“不太聪明”的“类人”的行为,又不是说要代替啥的,完全可以不用动鼠标一直听课,做几道测试题我真觉得没什么,已经够懒了(不是):(

        但是捏,不论你是稍后做还是已经做掉了,我们都考虑到了这两种情况,脚本都能运行下去

还有的特殊情况莫过于每一章节的开头多出来的界面了:

不能直接点击播放,这时候我们也得明白啥时候是到换章节的时候咯,要不然又得卡住了。

        假设我们刚刚的逻辑没问题且都可以实现,那我们就可以把整个过程封装成一个大循环,

最后就大功告成啦!

        浅浅的看一下ai给我做的过程梳理:

          1.用户启动程序,给予5秒切换窗口的时间。
        2.识别并点击 stact1.jpg,课程开始播放。
        3.每隔3秒检测:检测是否进入答题(识别 A.jpg 和 B.jpg)。检测课程播放是否结束(识别 finish.jpg)。如果进入答题:选择 A,提交。如果识别到 Wrong.jpg,则选择 B,提交。如果提交后没有识别到 submit.jpg,说明题目做对了,可以继续观看课程。
        4.如果检测到 finish.jpg:向下滚动,查找 nextCH.jpg 并点击。向下滚动,查找第二个 nextCH.jpg 并点击。如果弹出窗口并识别到 nextCH.jpg,需要再次点击。如果没有弹出窗口,直接跳转并显示 stact1.jpg 进入下一个循环。重复以上步骤,直到所有课程完成。

        然后这是答题函数的逻辑,清晰,但是很简单:
检测答题界面:当同时检测到A.jpg和B.jpg时,意味着进入了答题界面。

选择答案:
先点击A.jpg,表示选择选项A。
随后点击submit.jpg提交答案。
        检查是否错误:
        通过检测Wrong.jpg来判断答案是否错误。
        如果检测到Wrong.jpg,则表示选择错误,接着点击B.jpg(选项B)并再次提交。
                判断正确:
                如果没有检测到Wrong.jpg,则说明选择正确,可以继续播放课程。
检查课程结束:
使用if mouseclick(img_paths["finish"]):来检测是否课程结束。如果识别到finish.jpg,则退出循环,表示课程已完成。

        我想,大概就是这些过程了,当然还有许多意想不到的bug和特殊情况,不过在我和GPT的羁绊下,他们都将被扫清!

三、优化和反制检测

        学习通作为一个网课平台,本身就有第三方软件检测和人机检测的逻辑,后台数据都看得到。我们又该怎么处理呢?挂一天课就直接打回白挂了?首先我们得了解人机检测的机制,这样才能有效反制。

        总结一下,无非就是:

        1.倍速观看,导致学习时长不一致。

        2.答题速度过快,任务点瞬间多个完成

        3.行为识别不正常

        首先,我们使用一倍速观看(本来就没做加速功能),第一是老师就设置了一倍速观看,第二,这就是为什么有的人代刷网课这么快的原因。使用油猴对网页倍速,相当于在破拆源码了,这样的行为就有些不厚道了,时长对不上,我只能说活该被检测:)

        至于说答题速度过快的问题,那就time.sleep()嘛,装作在思考还不会?而且滚轮之类的所有操作我都加了sleep,这样可以加强系统的稳定性,现在我的软件成功率应该在90%以上,难免有时候加载速度慢了一点,导致有些按钮被误触了,循环失败。这段时间还能用来等待加载,美哉。

        像人的行为?说实话没啥行为,本来就不用动鼠标,专心记笔记就好了。但是话都说到这个份上了,所以我给鼠标加上了每隔一段时间就朝一个向量进行移动的指令,还按下esc。按下esc是为了解决有时候全屏出现的循环破坏bug。

四、源码

        本来我也可以帮别人代刷,也可以卖给别人这个软件,捞一波米。

        但是我真有良心,我不在乎靠这种手段得到微薄的不义之财,

        所以嘞,孩子们想要,我给便是。这次是全的哈,看得懂不。

        我不直接说明使用方法,说实话还用不来,真的可以自己去调试一下,很有意思的。

        (因为没有我图片里的按钮,或者识别内容,最好还是愿意自己去录入吧)

import pyautogui
import time
import os
import threading
import random
import sys

base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))

# 设置启动延迟时间
time.sleep(5)

# 获取按钮图片的路径
img_paths = {
    "next_ch": os.path.join(base_path, 'act', 'nextCH.jpg'),
    "stact": os.path.join(base_path, 'act', 'stact1.jpg'),
    "character": os.path.join(base_path, 'act', 'character.jpg'),
    "a": os.path.join(base_path, 'act', 'A.jpg'),
    "b": os.path.join(base_path, 'act', 'B.jpg'),
    "submit": os.path.join(base_path, 'act', 'submit.jpg'),
    "wrong": os.path.join(base_path, 'act', 'Wrong.jpg'),
    "finish": os.path.join(base_path, 'act', 'finish.jpg'),
    "select": os.path.join(base_path, 'act', 'select.jpg')
}

def mouseclick(img, maxRetries=20, retryDelay=0.1):
    missed_count = 0
    for attempt in range(maxRetries):
        try:
            location = pyautogui.locateCenterOnScreen(img, confidence=0.95)
            if location:
                print(f"找到 {img} 的位置: {location}")
                pyautogui.click(location)
                return True
            else:
                missed_count += 1
                if missed_count % 100 == 0:
                    print(f"未找到 {img},已尝试 {missed_count} 次,继续查找...")
        except pyautogui.ImageNotFoundException:
            missed_count += 1
            if missed_count % 100 == 0:
                print(f"在尝试 {attempt + 1} 中未找到 {img},已尝试 {missed_count} 次,继续重试...")

        time.sleep(retryDelay)

    print(f"未能在多次尝试中找到 {img}。")
    return False

def move_mouse():
    while True:
        time.sleep(5)
        # 随机上下移动鼠标
        direction = random.choice([-1, 1])  # 随机选择方向,上下移动
        y_offset = direction * random.randint(100, 300)  # 设置移动距离
        current_x, current_y = pyautogui.position()
        pyautogui.moveTo(current_x, current_y + y_offset, duration=0.5)
        # 按下Esc键
        pyautogui.press('esc')

def handle_questions():
    while True:
        time.sleep(2)
        if mouseclick(img_paths["a"]) and mouseclick(img_paths["b"]):
            print("选择 A")
            mouseclick(img_paths["a"])
            time.sleep(2)
            mouseclick(img_paths["submit"])
            time.sleep(2)

            if mouseclick(img_paths["wrong"]):
                print("回答错误,选择 B")
                mouseclick(img_paths["b"])
                time.sleep(2)
                mouseclick(img_paths["submit"])
                time.sleep(2)
            else:
                print("答对了,继续播放课程")

        if mouseclick(img_paths["finish"]):
            print("识别到 finish.jpg")
            time.sleep(2)
            pyautogui.scroll(-500)
            time.sleep(2)
            if mouseclick(img_paths["next_ch"]):
                print("找到下一个课程")
            else:
                print("未找到下一个课程,退出循环")
                break

def detect_character():
    while True:
        time.sleep(5)
        if mouseclick(img_paths["character"]):
            print("识别到 character.jpg,准备进入下一节")
            time.sleep(2)
            if mouseclick(img_paths["next_ch"]):
                print("点击 nextCH.jpg 进入下一节课")
                time.sleep(2)

                while True:
                    time.sleep(2)
                    if mouseclick(img_paths["select"]):
                        print("识别到 select.jpg,点击以继续")
                        mouseclick(img_paths["select"])
                        time.sleep(2)
                    elif mouseclick(img_paths["stact"]):
                        print("进入下一节课")
                        break
                    else:
                        print("未能找到 stact1.jpg,继续查找 nextCH.jpg")
                        if mouseclick(img_paths["next_ch"]):
                            print("找到并点击 nextCH.jpg")
                        else:
                            pyautogui.scroll(-500)

def play_course():
    threading.Thread(target=move_mouse, daemon=True).start()  # 启动鼠标移动线程

    while True:
        if mouseclick(img_paths["stact"]):
            threading.Thread(target=handle_questions, daemon=True).start()
            threading.Thread(target=detect_character, daemon=True).start()

        while True:
            time.sleep(3)
            if mouseclick(img_paths["finish"]):
                print("识别到 finish.jpg")
                time.sleep(2)
                while True:
                    pyautogui.scroll(-500)
                    time.sleep(2)
                    if mouseclick(img_paths["next_ch"]):
                        print("找到并点击 nextCH.jpg")
                        time.sleep(3)
                        break

                while True:
                    time.sleep(2)
                    pyautogui.scroll(-500)
                    time.sleep(2)
                    if mouseclick(img_paths["next_ch"]):
                        print("在下一个界面找到并点击 nextCH.jpg")
                        if mouseclick(img_paths["select"]):
                            print("识别到 select.jpg,等待2秒后重新查找 nextCH.jpg")
                            time.sleep(2)
                            continue
                        break

                if mouseclick(img_paths["stact"]):
                    print("进入下一节课")
                else:
                    print("未能找到下一节课,结束课程循环")
                    break

play_course()

五、结语

        还是那句话,这就是不正当手段,没啥好洗的。

        非常非常不建议这么干,话都说到这里了,dddd。

        被检测了,有问题了就去解决,挂了就老老实实重修补考。

        不要什么都想兼得,哪有这么好占的便宜,孩子。

        ok,这就是这次的项目分享了,欢迎大家评论指正,有任何问题和需求也可以发给我们呀,

        让我们一起进步!

                                                                                                          本次撰稿人:Oldmeat

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值