# 智慧树 (zhihuishu.com) API 逆向分析 > 日期: 2026-06-23 | 全部接口已验证通过 --- ## 一、概述 智慧树有两套 API 体系: | 体系 | 域名 | 加密 | 课程类型 | |------|------|------|---------| | Zhidao | `studyservice-api` / `onlineservice-api` | AES-CBC (secretStr) | 共享学分课 | | Hike | `hikeservice` / `studyresources` / `hike-teaching` | 无加密 | 校内学分课 | --- ## 二、登录流程 ### 4.1 密码登录(需要滑块验证码) ``` Step 1: GET passport.zhihuishu.com/login → 获取 Session Cookie (JSESSIONID等) Step 2: POST passport.zhihuishu.com/user/validateAccountAndPassword Body: secretStr=Base64(encodeURIComponent(JSON.stringify({ account: "手机号", password: "密码", validate: "滑块验证码token" }))) → {status: 1, uuid: "...", pwd: "一次性密码"} Step 3: POST appcomm-user.zhihuishu.com/.../checkNeedAuth Body: uuid=xxx → {rt: {needAuth: 0}} Step 4: GET passport.zhihuishu.com/login?pwd={pwd}&service={service} → 302重定向链 → 设置 CASLOGC Cookie 注意: 必须用 requests 库跟随重定向,urllib 不会自动跨域传 Cookie 加密方式: btoa(encodeURIComponent(JSON)) — 就是 Base64 + URL编码 (不是 AES!这是登录专用加密) ``` ### 4.2 滑块验证码 网易易盾,captchaId: `75f9f716460a422f89a628f50fd8cc2b` ```html ``` --- ## 三、Zhidao API(共享学分课) ### 5.1 AES 加密 ```python KEY_MAP = { "home": b"7q9oko0vqb3la20r", # 课程列表 "video": b"azp53h0kft7qi78q", # 视频/学习/进度 "qa": b"kcGOlISPkYKRksSK", # 弹题 } IV = b"1g3qqdh4jvbskb9x" def aes_encrypt(data_str, key=KEY_MAP["video"]): pad_len = 16 - len(data_str) % 16 padded = data_str + chr(pad_len) * pad_len cipher = AES.new(key, AES.MODE_CBC, IV) return base64.b64encode(cipher.encrypt(padded.encode())).decode() ``` **请求格式:** ``` POST /api/endpoint Content-Type: application/x-www-form-urlencoded Body: secretStr={AES密文}&dateFormate={毫秒时间戳} ``` ⚠️ `dateFormate` 必须同时在**加密 JSON 内**和**表单字段外**,且值相同。 ### 5.2 课程列表 ``` POST onlineservice-api.zhihuishu.com/gateway/t/v1/student/course/share/queryShareCourseInfo 加密密钥: HOME_KEY 加密前: {"status":0,"pageNo":1,"pageSize":10,"dateFormate":时间戳} 返回: { "code": 0, "result": { "totalCount": 1, "courseOpenDtos": [{ "secret": "RAC_id", // 后续 API 的课程标识 "courseName": "课程名", "recruitId": 389213, // 招生ID "courseId": 1000076607, // 课程ID "schoolName": "学校", "teacherName": "教师" }] } } ``` ### 5.3 章节/视频列表 ``` 先调 gologin: GET studyservice-api.zhihuishu.com/login/gologin?fromurl=... 再调: POST studyservice-api.zhihuishu.com/gateway/t/v1/learning/videolist 加密密钥: VIDEO_KEY 加密前: {"recruitAndCourseId":"RAC_id","dateFormate":时间戳} 返回: { "code": 0, "data": { "courseId": 1000076607, "videoChapterDtos": [{ "id": 1001076678, // chapterId "name": "章节名", "videoLessons": [{ "id": 1001297571, // lessonId "name": "小节名", "videoSmallLessons": [{ "id": 4001, // smallLessonId (单视频时=0) "videoId": 63921147,// 视频ID "videoSec": 600, // 总时长(秒) "chapterId": 1001076678 }] }] }] } } ``` ### 5.4 视频弹题信息 ``` POST studyservice-api.zhihuishu.com/gateway/t/v1/popupAnswer/loadVideoPointerInfo 加密密钥: VIDEO_KEY 加密前: {"lessonId":...,"lessonVideoId":...,"recruitId":...,"courseId":...,"dateFormate":时间戳} 返回: questionPoint 数组 (弹题时间点 + 题目ID) ``` ### 5.5 视频播放 URL ``` GET newbase.zhihuishu.com/video/initVideo?jsonpCallBack=result&videoID={videoId} 返回: JSONP,包含 lines[0].lineUrl ``` ### 5.6 学习状态 ``` POST studyservice-api.zhihuishu.com/gateway/t/v1/learning/queryStuyInfo 加密密钥: VIDEO_KEY 加密前: { "lessonIds": [1001297571], // 课时ID列表 "lessonVideoIds": [], // 子视频ID列表,单视频课时(smallLessonId=0)填空数组[] "recruitId": 389213, // 招生ID "dateFormate": 1782200000000 } ⚠️ lessonVideoIds 为 0 时不要传 [0],传空数组 [] 返回: { "code": 0, "data": { "lv": { "4001": { "watchState": 1, "studyTotalTime": 600 } }, "lesson": { "1001297571": { "watchState": 1, "studyTotalTime": 600 } } } } watchState: 0=未看完, 1=已看完 studyTotalTime: 已学习秒数 ``` ### 5.7 提交学习进度 ``` Step 1 (prelearningNote): POST studyservice-api.zhihuishu.com/gateway/t/v1/learning/prelearningNote 加密密钥: VIDEO_KEY 加密前: {"ccCourseId":...,"chapterId":...,"isApply":1,"lessonId":..., "lessonVideoId":...,"recruitId":...,"videoId":...,"dateFormate":时间戳} 返回: data.studiedLessonDto.id → Base64编码得到 learningTokenId Step 2 (saveDatabaseIntervalTimeV2): POST studyservice-api.zhihuishu.com/gateway/t/v1/learning/saveDatabaseIntervalTimeV2 加密密钥: VIDEO_KEY 加密前: { "ewssw": "0,1,2", // watchPoint "sdsew": getEv([...]), // EV混淆的参数 "zwsds": learningTokenId, // Base64编码的token "courseId": ..., "dateFormate": 时间戳 } ``` ### 5.8 EV 混淆算法 ```python def getEv(data_list, key="zzpttjd"): """XOR 混淆""" data = ';'.join(map(str, data_list)) key_cycle = (ord(c) for _ in iter(int,1) for c in key) ev = '' for c in data: tmp = hex(ord(c) ^ next(key_cycle)).replace('0x', '') if len(tmp) < 2: tmp = '0' + tmp ev += tmp[-4:] return ev # saveDatabaseIntervalTimeV2 的 raw_ev 参数: raw_ev = [ recruitId, lessonId, smallLessonId, videoId, chapterId, '0', # studyStatus played_time - last_submit, # 本次播放时长 played_time, # 累计播放时长 HMS(played_time), # HH:MM:SS 格式 uuid + "zhs" # UUID后缀 ] ``` --- ## 四、Hike API(校内学分课) 无需 AES 加密,直接 GET 请求,带 Cookie 即可。 ### 6.1 课程列表 ``` GET hikeservice.zhihuishu.com/student/course/aided/getMyCourseList?uuid={uuid}&data={UTC时间} 返回: result.startInngcourseList (注意拼写) ``` ### 6.2 章节/资源树 ``` GET studyresources.zhihuishu.com/studyResources/stuResouce/queryResourceMenuTree?courseId={id} 返回: rt 数组,childList 非空=目录,childList=null=文件,dataType=3=视频 ``` ### 6.3 视频信息 ``` GET studyresources.zhihuishu.com/studyResources/stuResouce/stuViewFile?courseId={id}&fileId={id} 返回: dataId (视频流ID), totalTime, studyTime ``` ### 6.4 提交学习记录 ``` GET hike-teaching.zhihuishu.com/stuStudy/saveStuStudyRecord?uuid=...&courseId=...&fileId=... &studyTotalTime=...&startWatchTime=...&endWatchTime=...&startDate=...&endDate=... &signature=MD5(SALT + uuid + courseId + fileId + studyTotalTime + startDate + endDate + endWatchTime + startWatchTime + uuid) SALT = "o6xpt3b#Qy$Z" ``` --- ## 五、Cookie 体系 登录后获取的关键 Cookie: | Cookie | 域名 | 说明 | |--------|------|------| | CASLOGC | passport.zhihuishu.com | URL编码JSON,含uuid/realName/userId | | JSESSIONID | passport.zhihuishu.com | Session ID | | SERVERID | 各子域名 | 服务器路由 | | SESSION | onlineservice-api / studyservice-api | API会话 (通过CAS重定向获取) | ⚠️ 登录时必须跟随完整 CAS 重定向链,否则缺少 `onlineservice-api` 的 SESSION Cookie 导致 API 返回 401。 --- ## 六、关键注意事项 1. **AES JSON 格式**: `json.dumps(data, separators=(',', ':'))` — 紧凑格式,无空格 2. **dateFormate 双写**: 加密 JSON 内和表单字段都要有,且值相同 3. **lessonVideoIds 不能含 0**: 单视频课时 smallLessonId=0,此时传空数组 `[]` 4. **CAS 重定向**: 必须用 requests.Session() 自动跟随,urllib 不会跨域传 Cookie 5. **latin-1 编码**: Cookie 值保持 URL 编码状态(纯 ASCII),不要解码成中文 6. **Encrypt 已变更**: 登录接口用 `btoa(encodeURIComponent(JSON))`,不是 AES