wuye4 发表于 2024-4-26 22:04

[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)

```

SVIP9大会员 发表于 2024-4-26 23:05

这个代码过程以及分析过程简直就是范例啊 受教了

shiyun01 发表于 2024-4-28 11:22

能不能写个学习通的,学习一下{:1_893:}{:1_893:}{:1_893:}

wantwill 发表于 2024-11-23 14:42

感谢分享

李玉风我爱你 发表于 2024-11-5 21:39

haohaovision 发表于 2024-9-18 06:28
大佬,截止24年9月18日,发现studyRecord这个接口的payload似乎是加密的(如图)

今天晚上睡不着没事干 ...

你好 可以参考这段代码

haohaovision 发表于 2024-9-18 06:28

大佬,截止24年9月18日,发现studyRecord这个接口的payload似乎是加密的(如图)

今天晚上睡不着没事干起来写,从Github一路搜过来的,发现似乎接口有改动.直接按原样不加密提交上去不会报错,但是也没有更新进度。 无奈个人能力有限,js代码有混淆,无法确定是不是加密,是怎么加密的,望大佬更新!

THqqqqp 发表于 2024-5-16 16:53

代码分析过程简直就是范例啊 受教了

eacpjls 发表于 2024-5-8 14:38

wuye4 发表于 2024-5-7 16:15
根据文件的网址,写就行了

希望大佬有时间出个教程~

wuye4 发表于 2024-5-7 16:15

eacpjls 发表于 2024-5-6 08:52
是的 下载到本地

根据文件的网址,写就行了

eacpjls 发表于 2024-5-6 08:52

wuye4 发表于 2024-5-1 14:14
你是说下载到本地吗还是啥

是的 下载到本地

wuye4 发表于 2024-5-1 14:14

eacpjls 发表于 2024-4-29 16:41
ppt咋下载啊大佬

你是说下载到本地吗还是啥

wuye4 发表于 2024-5-1 14:14

icyou66 发表于 2024-4-29 15:11
有没有mooc的思路分析大佬。

有的,稍后再写

wuye4 发表于 2024-5-1 14:13

icyou66 发表于 2024-4-30 22:53
totalNum可以在章节接口的studentStudyRecord中获得

没看过的章节studentStudyRecord没有值
页: [1] 2 3 4 5
查看完整版本: [Web逆向] 智慧职教资源库-刷课分析