pengzhengchang 发表于 2022-11-10 20:59

智慧职教Mooc(icve-mooc)API分析

## 适用https://icve-mooc.icve.com.cn/cms/

### 主要刷课API

**URL:**https://course.icve.com.cn/learnspace/course/study/learningTime_saveVideoLearnDetailRecord.action
**请求方式:**Post
**参数:**

**limitId:** 包含在网页的javascript中,每次刷新页面limitId也会刷新,可重复用
**studyRecord:** 通过crypto-js AES加密的一串数据,包含课程ID,视频ID,以及学习时长,学习起始秒数和学习结束的秒数。

**返回结果:**”保存状态成功“或者“参数不合法,超出时长”,学习时长越长,需要等待一定时间才能第再次保存学习状态。学习时长短不需要等待。

!(https://raw.githubusercontent.com/kkkbbb/tuchuang/master/uPic/image-20221110204143266.png)



### 获取limitId

**URL:**https://course.icve.com.cn/learnspace/learn/learn/templateeight/index.action?params.courseId=26ae32dc2dcd4c9cbace10894d9a172b___&params.templateType=8&params.templateStyleType=0&params.template=templateeight&params.classId=&params.tplRoot=learn

**请求方式:** Get

**参数:**url里面可以看到,主要包含一个课程id,其他的似乎默认就行,可以去浏览器里找到对应的url

**返回结果:**内容是html网页,直接通过正则搜索找到limitId

!(https://raw.githubusercontent.com/kkkbbb/tuchuang/master/uPic/image-20221110204249701.png)



### studyRecord AES加密的学习状态参数

!(https://raw.githubusercontent.com/kkkbbb/tuchuang/master/uPic/image-20221110175244805.png)

官方加密功能函数和格式化函数的js文件URL: https://course.icve.com.cn/learnspace/resource/common/js/CommonUtil.js?v=2022042401。

studyRecord参数就是将数据格式化后序列化再进行AES加密得到的字符串

主要的参数就只有courseId,itemId,stratTime,endTime:

**courseId:** 代表当前学习课程的16进制id

**itemId:** 对应课程中的每个视频或者文档也有一个16进制id

**startTime:** 对应视频时长进度

**endTime:** 对应视频的时长,表示当前视频从startTime秒学习到了endTime的秒数



### 文档内容完成学习API

**url:**https://course.icve.com.cn/learnspace/course/study/learningTime_saveCourseItemLearnRecord.action

**参数:**课程id,视频id,其他的参数固定即可

!(https://raw.githubusercontent.com/kkkbbb/tuchuang/master/uPic/image-20221110205007663.png)

### 获取itemId

**url:**https://course.icve.com.cn/learnspace/learn/learn/templateeight/courseware_index.action?params.courseId=26ae32dc2dcd4c9cbace10894d9a172b___

返回的html中包含itmeId,可以通过beautifulsoup搜索id=s_point_.* 获得对对应的标签

!(https://raw.githubusercontent.com/kkkbbb/tuchuang/master/uPic/image-20221110204525033.png)

### 判断内容是否已经完成

**url:**https://course.icve.com.cn/learnspace/learn/learnCourseware/getSingleItemCompleteCase.json

返回json,completed等于1表示学习完成,2表示部分学习,0表示内容没有学习过。

!(https://raw.githubusercontent.com/kkkbbb/tuchuang/master/uPic/image-20221110204811323.png)



### 效果图

!(https://raw.githubusercontent.com/kkkbbb/tuchuang/master/uPic/image-20221110205152355.png)

!(https://raw.githubusercontent.com/kkkbbb/tuchuang/master/uPic/image-20221110205228083.png)

### 完整py+nodejs代码

py需要安装库: requests bs4

nodejs需要安装库: crypto-js

自行替换python代码中的Cookie,test.js主要是做参数加密,运行python文件即可

```
import requests
from bs4 import BeautifulSoup
import re
import os
import json
import sys


header = {
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36',
'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.9",
'Accept-Encodign':'gzip, deflate, br',
'Cookie':''
}
header['Cookie'] = ''

#获得limitId
res = requests.get(url='https://course.icve.com.cn/learnspace/learn/learn/templateeight/index.action?params.courseId=26ae32dc2dcd4c9cbace10894d9a172b___&params.templateType=8&params.templateStyleType=0&params.template=templateeight&params.classId=&params.tplRoot=learn',headers=header)
patter=re.compile('limitId.*;')
try:
    limitId=patter.search(res.content.decode()).group().split('"')
except:
    print('\033[31m获取limitId失败,检查Cookie\033[0m')
    exit()

#获得itemId
res = requests.get('https://course.icve.com.cn/learnspace/learn/learn/templateeight/courseware_index.action?params.courseId=26ae32dc2dcd4c9cbace10894d9a172b___',headers=header)
soup = BeautifulSoup(res.content,'lxml')
divs = soup.find_all(id=re.compile("s_point_.*"),itemtype="video")
itemids = {}
for i in divs:
    itemids=i['id'].strip("s_point_")

#开始刷课
for key in itemids.keys():
    itemid=itemids
    data2={
      'itemId':itemid,
      'videoTotalTime':'00:10:00'
    }
    total = requests.post(url='https://course.icve.com.cn/learnspace/course/plugins/cloud_updateVideoTotalTime.action',headers=header,data=data2)

    #判断视频是否学习完成
    data2={
      'params.courseId':'26ae32dc2dcd4c9cbace10894d9a172b___',
      'params.itemId':itemid
    }
    complete = requests.post(url='https://course.icve.com.cn/learnspace/learn/learnCourseware/getSingleItemCompleteCase.json',headers=header,data=data2)
    if json.loads(complete.content.decode())['result']['completed'] == '1':
      print(key,'视频状态已完成,跳过')
      continue

    start=0
    end=0
    #轮询片段
    while True:
      start=end
      end=start+10
      #单个视频片段状态保存循环
      while True:
            cmd = os.popen('node ./test.js %s %s %s' % (itemid,start,end))
            studyrecord=cmd.read().strip('\n')
            cmd.close()
            data={
                'limitId':limitId,
                'studyRecord':studyrecord
            }
            res2 = requests.post(url='https://course.icve.com.cn/learnspace/course/study/learningTime_saveVideoLearnDetailRecord.action',headers=header,data=data)
            if '保存成功' in res2.content.decode() or '总时长' in res2.content.decode():
                print("\r", end="")
                print(key,"\033[32m学习时长: {}秒 \033[0m".format(end), end="")
                sys.stdout.flush()
                break
            else:
                pass

      if '总时长' in res2.content.decode():
            break
    print(key,'\033[31m学习完成\033[0m')

#刷文档内容

#取出itemid
divs = soup.find_all(id=re.compile("s_point_.*"),itemtype="doc")
itemids = {}
for i in divs:
    itemids=i['id'].strip("s_point_")

#轮询item
for key in itemids.keys():
    itemid=itemids

    #判断文档是否学习完成
    data2={
      'params.courseId':'26ae32dc2dcd4c9cbace10894d9a172b___',
      'params.itemId':itemid
    }
    complete = requests.post(url='https://course.icve.com.cn/learnspace/learn/learnCourseware/getSingleItemCompleteCase.json',headers=header,data=data2)
    if json.loads(complete.content.decode())['result']['completed'] == '1':
      print(key,'状态已完成,跳过')
      continue

    #保存文档
    doc_data={
      'courseId':'26ae32dc2dcd4c9cbace10894d9a172b',
      'itemId':itemid,
      'recordType':0,
      'studyTime':300
    }
    response = requests.post(url='https://course.icve.com.cn/learnspace/course/study/learningTime_saveCourseItemLearnRecord.action',headers=header,data=doc_data)
    if '成功' in response.content.decode():
      print(key,'完成')
    else:
      print(key,'保存失败')
      set_trace()

```

test.js:

```
const CryptoJS = require("crypto-js");

const encrypt = function (e) {
    var f = CryptoJS.enc.Utf8.parse("learnspaceaes123");
    var d = CryptoJS.AES.encrypt(e, f, {
      mode: CryptoJS.mode.ECB,
      padding: CryptoJS.pad.Pkcs7
    });
    return d.toString()
};

const decrypt = function (e) {
    var cipherParams = CryptoJS.lib.CipherParams.create({
      ciphertext: CryptoJS.enc.Base64.parse(e)
   });
    var f = CryptoJS.enc.Utf8.parse("learnspaceaes123");
    var res = CryptoJS.AES.decrypt(cipherParams,f,{
      mode: CryptoJS.mode.ECB,
      padding: CryptoJS.pad.Pkcs7
    });
    return res.toString(CryptoJS.enc.Utf8);

}

const timeToSeconds = function (f) {
    var b = f.split(":");
    var d = parseInt(b);
    var a = parseInt(b);
    var c = parseInt(b);
    var e = d * 3600 + a * 60 + c;
    return e
};

const formatStr = function (c, a) {
    var l = "";
    var k = (c + "").length;
    if (k > 0) {
      if (k + 2 > a) {
            return c + ""
      } else {
            var g = a - k - 2;
            var h = 1;
            for (var e = 0; e < g; e++) {
                h = h * 10
            }
            var b = parseInt(Math.random() * h);
            var f = (b + "").length;
            if (f < g) {
                for (var d = f; d < g; d++) {
                  b = b * 10
                }
            }
            if (k >= 10) {
                l += k
            } else {
                l += "0" + k
            } l += c + (b + "")
      }
    } else {
      return c + ""
    }
    return l
};

const getParams=function (p) {
    var q = {
      courseId: p.courseId,
      itemId: p.itemId,
      time1: formatStr(
            (new Date()).getTime(),
            20
      ),
      time2: formatStr(parseInt(p.startTime), 20),
      time3: formatStr(timeToSeconds(p.videoTotalTime), 20),
      time4: formatStr(parseInt(p.endTime), 20),
      videoIndex: p.videoIndex || 0,
      time5: formatStr(p.studyTimeLong, 20),
      terminalType: p.terminalType || 0
    };
    return q
}

var itemids = process.argv;
var start = process.argv
var end = process.argv
var p = {
    "interval": true,
    "playComplete": true,
    "courseId": "26ae32dc2dcd4c9cbace10894d9a172b___",
    "itemId": itemids,
    "position": 4,
    "videoTotalTime": "00:10:35",
    "startTime": parseInt(start),
    "endTime": parseInt(end),
    "studyTimeLong": end-start
}
//console.log(p)
console.log(encrypt(JSON.stringify(getParams(p))))

```

pengzhengchang 发表于 2022-11-11 14:37

PLA81 发表于 2022-11-11 13:54
用cookie登录或者账号密码,之后在哪里更改课程?ID

你可以直接在python代码里面搜索courseid,然后替换成你的课程的id。课程id打开学习页面直接在源码中搜索courseid能找到

lfm333 发表于 2022-11-11 09:06

感谢楼主分享

cyxnzb 发表于 2022-11-11 00:30

图挂了都,试试图床?

judgecx 发表于 2022-11-11 02:29

fiddle?mac你的是试用版吗?还是买了,还是破解的

123-木头人 发表于 2022-11-11 08:35

感谢分享

zhengxinjun 发表于 2022-11-11 08:40

收藏下来慢慢学习

petal 发表于 2022-11-11 08:53

参观学习下   

FcSp 发表于 2022-11-11 09:14

感谢楼主分享

a155865 发表于 2022-11-11 09:26

参观学习下

最新的 发表于 2022-11-11 09:40

python最后一行报错了,而且我的cookie正确显示 获取limitId失败,检查Cookie
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 智慧职教Mooc(icve-mooc)API分析