[Web逆向] 智慧职教资源库-刷课分析
### 只适用于https://zyk.icve.com.cn/### 已更新至Githubhttps://github.com/wuye4/zyk.icve.com.cn
#### 1.目前暂时不支持讨论、作业、测验、考试。
#### 2.该网站的新课与旧课会有参数的值有多种形式需要一定时间适配。
### 一、找接口
#### 1.登录接口https://sso.icve.com.cn/prod-api/data/userLoginV2
##### 会响应一个token
#### 2.access_token接口https://zyk.icve.com.cn/prod-api/auth/passLogin?token={}
##### 传入1.登录接口响应的token
#### 3.myCourseList接口https://zyk.icve.com.cn/prod-api/teacher/courseList/myCourseList?pageNum=1&pageSize=6&flag=1
##### 获取所选课程courseId、courseInfoId、id
#### 4.studyMoudleList接口 https://zyk.icve.com.cn/prod-api/teacher/courseContent/studyMoudleList?courseInfoId={}
##### 传入3.myCourseList接口获取的courseId 拿到一级标题与courseId、courseInfoId、id、level
#### 5.studyList接口https://zyk.icve.com.cn/prod-api/teacher/courseContent/studyList?level={}&parentId={}&courseInfoId={}
##### 传入4.studyMoudleList接口获取的courseInfoId、id拿到二级标题与courseId、courseInfoId、id、level
#### 继续往下点后发现还是请求studyList接口拿到最终课程的参数
#### 6.studyRecord接口 https://zyk.icve.com.cn/prod-api/teacher/studyRecord
##### 随便点击一个课程过一会就会请求studyRecord接口,也就是完成该课程的接口,以下以视频举例totalNum代表视频总时长,actualNum代表实际时长,studyTime代表学习时长,每个视频的时长是不固定的我们如何动态获取呢。
#### 7.status接口 https://upload.icve.com.cn/doc/{fileUrl}/status
##### 5.studyList接口中有一个fileUrl参数将其传入就可以获取视频时长参数duration,最后将其转化为秒数再请求6.studyRecord接口就可以完成刷视频
### 二、效果图
### 三、python代码实现
```python
import requests
from datetime import datetime
requests = requests.session()
def getcookie():
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Referer': 'https://zyk.icve.com.cn/',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-site',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
response = requests.get('https://sso.icve.com.cn/sso/auth?mode=simple&source=14&redirect=https://zyk.icve.com.cn/',
headers=headers)
# 登录
def userLoginV2(userName, password):
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Content-Type': 'application/json;charset=UTF-8',
'Language': 'cn',
'Origin': 'https://sso.icve.com.cn',
'Referer': 'https://sso.icve.com.cn/sso/auth?mode=simple&source=14&redirect=https%3A%2F%2Fzyk.icve.com.cn%2F',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
json_data = {
'type': 1,
'userName': userName,
'password': password,
'webPageSource': 1,
}
response = requests.post('https://sso.icve.com.cn/prod-api/data/userLoginV2', headers=headers,
json=json_data)
if len(response.json()["data"]["displayName"]) > 0:
print(response.json()["data"]["displayName"] + "登录成功")
return response.cookies.get("token")
# 获取access_token
def access_token(token):
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Pragma': 'no-cache',
'Referer': 'https://zyk.icve.com.cn/',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'isToken': 'false',
'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
params = {
'token': token,
}
response = requests.get('https://zyk.icve.com.cn/prod-api/auth/passLogin', params=params,
headers=headers)
return response.json()["data"]["access_token"]
# 课程
def courseList():
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Authorization': f'Bearer {accesstoken}',
'Connection': 'keep-alive',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
params = {
'pageNum': '1',
'pageSize': '6',
'flag': '1',
}
response = requests.get('https://zyk.icve.com.cn/prod-api/teacher/courseList/myCourseList', params=params,
headers=headers)
info = []
for i in response.json()["rows"]:
info.append((i["id"], i["courseId"], i["courseInfoId"], i["studentId"], i["courseName"]))
return info
# 一级标题
def studyMoudleList(courseInfoId):
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Authorization': f'Bearer {accesstoken}',
'Connection': 'keep-alive',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
params = {
'courseInfoId': courseInfoId,
}
response = requests.get('https://zyk.icve.com.cn/prod-api/teacher/courseContent/studyMoudleList', params=params,
headers=headers)
studymoudlelist = []
for i in response.json():
studymoudlelist.append((i["id"], i["courseId"], i["courseInfoId"], i["name"]))
return studymoudlelist
# 二级标题
def studyList1(level, parentId, courseInfoId):
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Authorization': f'Bearer {accesstoken}',
'Connection': 'keep-alive',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
params = {
'level': level,
'parentId': parentId,
'courseInfoId': courseInfoId,
}
response = requests.get('https://zyk.icve.com.cn/prod-api/teacher/courseContent/studyList', params=params,
headers=headers)
for i in response.json():
if i["level"] is not None:
studyList1(i["level"], i["id"], i["courseInfoId"])
else:
geturl(i)
# 获取最总课程网址
def geturl(i):
if i["fileType"] == 'docx' or i["fileType"] == 'doc':
if i["studentStudyRecord"] is None:
docx.append((i["id"], i["courseId"], i["courseInfoId"], i["name"], i["parentId"], i["fileUrl"]))
elif i["fileType"] == 'pdf' or i["fileType"] == 'pptx':
if i["studentStudyRecord"] is None:
pdf.append((i["id"], i["courseId"], i["courseInfoId"], i["name"], i["parentId"], i["fileUrl"]))
elif i["fileType"] == 'mp4':
if i["studentStudyRecord"] is None:
mp4.append((i["id"], i["courseId"], i["courseInfoId"], i["name"], i["parentId"], i["fileUrl"]))
elif i["fileType"] == 'jpg':
if i["studentStudyRecord"] is None:
jpg.append((i["id"], i["courseId"], i["courseInfoId"], i["name"], i["parentId"]))
def getdocorpdfxnum(courseInfoId, parentId, sourceId, studentId, fileUrl):
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Authorization': f'Bearer {accesstoken}',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Pragma': 'no-cache',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'sec-ch-ua': '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
params = {
'fileUrl': fileUrl,
}
response = requests.get(
'https://zyk.icve.com.cn/prod-api/teacher/oss/getUrlPngs',
params=params,
headers=headers,
)
totalNum = len(response.json()["data"])
tapjpganddocxandpdf(courseInfoId, parentId, sourceId, studentId, totalNum)
# 刷课
def tapjpganddocxandpdf(courseInfoId, parentId, sourceId, studentId, totalNum):
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Authorization': f'Bearer {accesstoken}',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Content-Type': 'application/json;charset=UTF-8',
'Origin': 'https://zyk.icve.com.cn',
'Pragma': 'no-cache',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'sec-ch-ua': '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
json_data = {
'courseInfoId': courseInfoId,
'id': '',
'parentId': parentId,
'studyTime': 60,
'sourceId': sourceId,
'studentId': studentId,
'actualNum': totalNum,
'lastNum': totalNum,
'totalNum': totalNum,
}
response = requests.put('https://zyk.icve.com.cn/prod-api/teacher/studyRecord', headers=headers,
json=json_data)
# 视频状态
def videostatus(courseInfoId, parentId, sourceId, studentId, fileUrl):
headers = {
'accept': 'application/json, text/plain, */*',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'no-cache',
# 'content-length': '0',
'origin': 'https://zyk.icve.com.cn',
'pragma': 'no-cache',
'sec-ch-ua': '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
}
response = requests.post(f'https://upload.icve.com.cn/{fileUrl}/status',
headers=headers)
time = str(response.json()["args"]["duration"])
duration_str_no_micro = time.split('.')
duration_obj = datetime.strptime(duration_str_no_micro, "%H:%M:%S")
total_seconds = duration_obj.hour * 3600 + duration_obj.minute * 60 + duration_obj.second
tapjpganddocxandpdf(courseInfoId, parentId, sourceId, studentId, total_seconds)
if __name__ == '__main__':
username = input("账号")
pwd = input("密码")
getcookie()
token = userLoginV2(username, pwd)
accesstoken = access_token(token)
# 所有课程信息
info = courseList()
# print(info)
for i in info:
print(i + "开始刷课")
studymoudlelist = studyMoudleList(i)
# print(studymoudlelist)
docx = []
pdf = []
mp4 = []
jpg = []
# id courseInfoId
for k in studymoudlelist:
studyList1(1, k, k)
# print(len(docx))
# print(len(pdf))
# print(len(mp4))
# print(len(jpg))
for i in docx:
print(i + "观看完毕")
getdocorpdfxnum(i, i, i, info, i)
for i in jpg:
print(i + "观看完毕")
tapjpganddocxandpdf(i, i, i, info, 1)
for i in pdf:
print(i + "观看完毕")
getdocorpdfxnum(i, i, i, info, i)
for i in mp4:
print(i + "观看完毕")
videostatus(i, i, i, info, i)
``` 这个代码过程以及分析过程简直就是范例啊 受教了 能不能写个学习通的,学习一下{:1_893:}{:1_893:}{:1_893:} 感谢分享 haohaovision 发表于 2024-9-18 06:28
大佬,截止24年9月18日,发现studyRecord这个接口的payload似乎是加密的(如图)
今天晚上睡不着没事干 ...
你好 可以参考这段代码 大佬,截止24年9月18日,发现studyRecord这个接口的payload似乎是加密的(如图)
今天晚上睡不着没事干起来写,从Github一路搜过来的,发现似乎接口有改动.直接按原样不加密提交上去不会报错,但是也没有更新进度。 无奈个人能力有限,js代码有混淆,无法确定是不是加密,是怎么加密的,望大佬更新! 代码分析过程简直就是范例啊 受教了 wuye4 发表于 2024-5-7 16:15
根据文件的网址,写就行了
希望大佬有时间出个教程~ eacpjls 发表于 2024-5-6 08:52
是的 下载到本地
根据文件的网址,写就行了 wuye4 发表于 2024-5-1 14:14
你是说下载到本地吗还是啥
是的 下载到本地 eacpjls 发表于 2024-4-29 16:41
ppt咋下载啊大佬
你是说下载到本地吗还是啥 icyou66 发表于 2024-4-29 15:11
有没有mooc的思路分析大佬。
有的,稍后再写 icyou66 发表于 2024-4-30 22:53
totalNum可以在章节接口的studentStudyRecord中获得
没看过的章节studentStudyRecord没有值