python第一记,长江雨课堂抓包刷课实战
前言
发现大学群里好多代刷网课的,确实觉得好多大学网课好费时间,而且没啥用,刚好用来实战
一下学的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:
# 课堂
pass
elif i['type'] == 9:
# 公告
pass
elif 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 execjs
class jsFun:
def __init__(self):
pass
def 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 =1
for 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 += 1
return List , num
def 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)
# continue
try:
if_completed = re.search(r'"completed":(.+?),', progress.text).group(1)
except:
pass
if if_completed == '1':
print(video_name+"已经学习完毕,跳过")
continue
else:
print(video_name+",尚未学习,现在开始自动学习")
video_frame = 0
val = 0
learning_rate = 20
t = 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_rate
max_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 = 0
except:
pass
try:
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 = 0
print("恢复工作啦~~")
r = requests.post(url=submit_url, headers=headers, data=data)
except:
pass
progress = requests.get(url=get_url,headers=headers)
tmp_rate = re.search(r'"rate":(.+?)[,}]',progress.text)
if tmp_rate is None:
return 0
val = 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 1
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:
# 课堂
pass
elif i['type'] == 9:
# 公告
pass
elif 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
except:
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!")
# 然后要获取教室id
get_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)-1
homework_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,什么的都差不多的。