相信大家已经看过标题了,“我不是课神”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