前言
发现大学群里好多代刷网课的,确实觉得好多大学网课好费时间,而且没啥用,刚好用来实战一下学的Python,就当练手了。省下来的时间不是又能多敲几行代码😃
一边省时间,还能学技能,这还不学起来

一、长江雨课堂
1.分析视频请求
F12抓包

播放过程中只有hearbest一个请求,可以确定这个应该就是我们要找的
请求是POST格式,data数据是一个json,通过多个heatbest可以看出是多个观看记录
截取一个分析如下:
c: 1561016
cards_id: 2027472
cc: ""
classroomid: 9763037
cp:424.6
d: 485.4
et: "playing"
fp: 304.5
i: 5
lob: "ykt"
n:"ali-cdn.xuetangx.com"
p:"web"
pg:"2027472_16s0j"
skuid: ""
slide: 44
sp:2
sq: 54
t:"ykt_cards"
tp: 304.5
ts: "1667910647346"
u: 35359786
uip: ""
v: 2027472
v_url: "rain://xtvideo/id/1901324024"
没发现什么加密的字段,那就一个一个处理
复制第一个1561016,在请求里搜索

也只有一个请求里搜索到扫一眼可以知道这是课程的列表数据
地址
https://changjiang.yuketang.cn/v2/api/web/courses/list?identity=2
请求里也莫有加密,后面有用到的我们提取保存一下
c的意思应该是class(班级)
get_classroom_id = "https://changjiang.yuketang.cn/v2/api/web/courses/list?identity=2"
classroom_id_response = requests.get(url=get_classroom_id, headers=headers)for ins in json.loads(classroom_id_response.text)["data"]["list"]:your_courses.append({"course_name": ins["course"]["name"],"classroom_id": ins["classroom_id"],"course_sign": ins["university_course_series_id"],"course_id": ins["course"]["id"]})
card_id同上搜索也只有一个请求
地址: https://changjiang.yuketang.cn/v2/api/web/logs/learn/9763037?actype=-1&page=0&offset=20&sort=-1
9763037搜索就可以发现是classroom_id,已经保存过了


就是一个课程列表数据
和上面一样有用的数据保存一下
def get_videos_ids(course_name,classroom_id,course_sign):get_homework_ids = "https://changjiang.yuketang.cn/v2/api/web/logs/learn/"+str(classroom_id)+"?actype=-1&page=0&offset=20&sort=-1"homework_ids_response = requests.get(url=get_homework_ids, headers=headers)homework_json = json.loads(homework_ids_response.text)homework_dic = {}try:for i in homework_json["data"]["activities"]:if i['type'] == 14:# 课堂passelif i['type'] == 9:# 公告passelif i['type'] == 2:# 课件homework_dic[i['title']] = {'courseware_id' : i['courseware_id'],'id' : i['id']}print(course_name+"共有"+str(len(homework_dic))+"个作业喔!")return homework_dic
| KEY | Value | 说明 |
|---|---|---|
| cp | 424.6 | 观看时长(破解点) |
| d: | 485.4 | 视频长度 |
| et | “playing” | 操作 |
| fp | 304.5 | 常量 |
| i | 5 | 常量 |
| lob | “ykt” | 常量 |
| n | “ali-cdn.xuetangx.com” | 常量 |
| p | “web” | (观看方式)常量 |
| pg | “2027472_16s0j” | 算个小加密可能 |
| skuid | “” | 常量 |
| slide | 44 | 视频所在ppt页数 |
| sp | 2 | 常量 |
| sq | 54 | 常量 |
| t | “ykt_cards” | 常量 |
| tp | 304.5 | 常量 |
| ts | “1667910647346” | 13位时间戳 |
| u | 35359786 | user_id |
| uip | “” | 常量 |
| v | 2027472 | cards_id |
| v_url | “rain://xtvideo/id/1901324024” | 地址 |
差不多都是请求里搜索然后找位置保存,没有什么变化,只有pg后面的一小串要在js里面找,但是是个随机产生的数,可以写死。
可以下一个xhr断点

段下来之后一个一个看调用堆栈
到resetTimer函数的时候,可以看到参数的构建位置
也可以在这里下断点看一下都是怎么构建的
因为我要找pg, 看到
pg: e.options.id + "_" + u
所以就向上找u

15760行有定义
var t, i, r, o, n, d, u = Math.floor(1048576 * (1 + Math.random())).toString(36);
2.代码实现
ok所用参数都找到获取方式了接下来就是写代码的时间
# -*- coding: utf-8 -*-
# version 4
# developed by zk chen
import time
import requests
import re
import json
import execjsclass jsFun:def __init__(self):passdef math(self):fun = '''function a(){let u = Math.floor(1048576 * (1 + Math.random())).toString(36);return u;};'''ctx = execjs.compile(fun)return ctx.call("a")
# 以下的cookie复制下面图片中的内容!!!!而且脚本需在登录雨课堂状态下使用
Cookie = "这里复制粘贴Cookie"
# 以下字段不用改,下面的代码也不用改动
user_id = ""
f = jsFun()
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.42','Content-Type': 'application/json','Cookie': Cookie'authority':'changjiang.yuketang.cn','method':'GET','path':'/v2/api/web/courses/list?identity=2','referer':'https://changjiang.yuketang.cn/v2/web/personal/info','sec-fetch-dest':'empty','sec-fetch-mode':'cors','sec-fetch-site':'same-origin',# 'university-id':'2727','x-csrftoken':'LE6xYWZ4rWN3UFHHDFkiykMBvffI4sCf','dnt' : '1'
}leaf_type = {"video": 0,"homework": 6,"exam": 5,"recommend": 3,"discussion": 4
}def get_video_id_list(cards_id,classroomid):url = f'https://changjiang.yuketang.cn/v2/api/web/cards/detlist/{cards_id}?classroom_id={classroomid}'js = json.loads(requests.get(url=url, headers=headers).text)List = []num =1for ins in js['data']['Slides']:for i in ins['Shapes']:if 'playurl' in i:if i['file_type'] == 1:List.append((i['URL'],str(num)))num += 1return List , numdef one_video_watcher(video_name,cid,user_id,classroomid,cards_id):classroomid = str(classroomid)id_list,num = get_video_id_list(cards_id,classroomid)for url_id,n in id_list:url = "https://changjiang.yuketang.cn/video-log/heartbeat/"get_url = f"https://changjiang.yuketang.cn/video-log/get_video_watch_progress/?user_id={user_id}&cid={cid}&video_type=ykt_cards&classroom_id={classroomid}&cards_id={cards_id}&v_url={url_id}&slide={n}"headers['xtbz'] = 'ykt'progress = requests.get(url=get_url, headers=headers)if_completed = '0'# while 1:try:js = json.loads(progress.text)[str(n) + ":"+re.search(r'[0-9]+',url_id).group()]videolength = js['video_length']except:print("视频" + str(n) + " " + video_name + "学习失败!")continue# time.sleep(10)# continuetry:if_completed = re.search(r'"completed":(.+?),', progress.text).group(1)except:passif if_completed == '1':print(video_name+"已经学习完毕,跳过")continueelse:print(video_name+",尚未学习,现在开始自动学习")video_frame = 0val = 0learning_rate = 20t = time.time()timestap = int(round(t * 1000))while val != "1.0" and val != '1':heart_data = []for i in range(50):heart_data.append({"i": 5,"et": "loadeddata","p": "web","n": "ali-cdn.xuetangx.com","lob": "ykt","cp": video_frame,"fp": 0,"tp": 0,"sp": 1,"ts": str(timestap),"u": int(user_id),"uip": "","c": cid,"v": cards_id,"skuid": '',"classroomid": classroomid,"cc": '',"d": videolength,"pg": f"{cards_id}_"+f.math(),"sq": 2,"t": "ykt_cards","cards_id": cards_id,"slide": n,"v_url": url_id})video_frame += learning_ratemax_time = int((time.time() + 3600) * 1000)timestap = min(max_time, timestap+1000*15)data = {"heart_data": heart_data}r = requests.post(url=url,headers=headers,json=data)try:error_msg = json.loads(r.text)["message"]if max_time< timestap+1000*15:video_frame = 0except:passtry:delay_time = re.search(r'Expected available in(.+?)second.', r.text).group(1).strip()print("由于网络阻塞,万恶的雨课堂,要阻塞" + str(delay_time) + "秒")time.sleep(float(delay_time) + 0.5)video_frame = 0print("恢复工作啦~~")r = requests.post(url=submit_url, headers=headers, data=data)except:passprogress = requests.get(url=get_url,headers=headers)tmp_rate = re.search(r'"rate":(.+?)[,}]',progress.text)if tmp_rate is None:return 0val = tmp_rate.group(1)print("学习进度为:" + str(float(val)*100) + "%/100%" + " last_point: " + str(video_frame))time.sleep(0.7)print("视频"+str(n)+" "+video_name+"学习完成!")return 1def get_videos_ids(course_name,classroom_id,course_sign):get_homework_ids = "https://changjiang.yuketang.cn/v2/api/web/logs/learn/"+str(classroom_id)+"?actype=-1&page=0&offset=20&sort=-1"homework_ids_response = requests.get(url=get_homework_ids, headers=headers)homework_json = json.loads(homework_ids_response.text)homework_dic = {}try:for i in homework_json["data"]["activities"]:if i['type'] == 14:# 课堂passelif i['type'] == 9:# 公告passelif i['type'] == 2:# 课件homework_dic[i['title']] = {'courseware_id' : i['courseware_id'],'id' : i['id']}print(course_name+"共有"+str(len(homework_dic))+"个作业喔!")return homework_dicexcept:print("fail while getting homework_ids!!! please re-run this program!")raise Exception("fail while getting homework_ids!!! please re-run this program!")if __name__ == "__main__":your_courses = []# 首先要获取用户的个人ID,即user_id,该值在查询用户的视频进度时需要使用user_id_url = "https://changjiang.yuketang.cn/v/course_meta/user_info"id_response = requests.get(url=user_id_url, headers=headers)id_response.encoding="utf-8"try:user_id = re.search(r'"user_id": ([0-9]+),', id_response.text).group(1)except:print("也许是网路问题,获取不了user_id,请试着重新运行")raise Exception("也许是网路问题,获取不了user_id,请试着重新运行!!! please re-run this program!")# 然后要获取教室idget_classroom_id = "https://changjiang.yuketang.cn/v2/api/web/courses/list?identity=2"submit_url = "https://gruestc.yuketang.cn/mooc-api/v1/lms/exercise/problem_apply/?term=latest&uv_id=3194"headers["referer"] = 'https://changjiang.yuketang.cn/v2/web/index?date=1666686775760&newWeb=1'classroom_id_response = requests.get(url=get_classroom_id, headers=headers)try:for ins in json.loads(classroom_id_response.text)["data"]["list"]:your_courses.append({"course_name": ins["course"]["name"],"classroom_id": ins["classroom_id"],"course_sign": ins["university_course_series_id"],"course_id": ins["course"]["id"]})except Exception as e:print("fail while getting classroom_id!!! please re-run this program!")raise Exception("fail while getting classroom_id!!! please re-run this program!")# 显示用户提示for index, value in enumerate(your_courses):print("编号:"+str(index+1)+" 课名:"+str(value["course_name"]))number = input("你想刷哪门课呢?请输入编号。输入0表示全部课程都刷一遍\n")if int(number)==0:#0 表示全部刷一遍for ins in your_courses:homework_dic = get_videos_ids(ins["course_name"],ins["classroom_id"], ins["course_sign"])for one_video in homework_dic.items():one_video_watcher(one_video[0],ins["course_id"],user_id,ins["classroom_id"],cards_id=homework_dic[one_video])else:#指定序号的课程刷一遍number = int(number)-1homework_dic = get_videos_ids(your_courses[number]["course_name"],your_courses[number]["classroom_id"],your_courses[number]["course_sign"])for one_video in homework_dic.items():one_video_watcher(one_video[0], your_courses[number]["course_id"], user_id, your_courses[number]["classroom_id"],cards_id = one_video[1]['courseware_id'])
cookie替换一下

总结
大体就是播放视频,抓包,找参数,在请求里搜素,加密了就下断点看实现。
!!!主要是思路,还有一些雨课堂,学习通,mooc,什么的都差不多的。


















