336 lines
17 KiB
Python
336 lines
17 KiB
Python
import requests
|
||
from PIL import Image
|
||
from io import BytesIO
|
||
import base64
|
||
import ddddocr
|
||
import time
|
||
|
||
# 全局Session对象
|
||
session = requests.session()
|
||
#全局变量用户名密码和观看速度
|
||
#读取用户信息文件内容
|
||
def read_config_file():
|
||
global username, password, speed
|
||
with open('config.txt', 'r', encoding='utf-8') as file:
|
||
lines = file.readlines()
|
||
for line in lines:
|
||
if line.startswith('username:'):
|
||
username = line.split(':')[1].strip().replace(" ", "").replace("\n", "")
|
||
elif line.startswith('password:'):
|
||
password = line.split(':')[1].strip().replace(" ", "").replace("\n", "")
|
||
elif line.startswith('speed:'):
|
||
speed = line.split(':')[1].strip().replace(" ", "").replace("\n", "")
|
||
|
||
|
||
# 模拟登录
|
||
def login():
|
||
#获取验证码图片地址
|
||
url_code = 'https://www.casmooc.cn/server/modules/imgCode/detail'
|
||
data_code = {
|
||
"source": "codeDetail"
|
||
}
|
||
# 请求登录接口需要下列请求头
|
||
headers = {
|
||
'x-access-device-type': 'WEB',
|
||
'x-access-origin': 'aHR0cHM6Ly93d3cuY2FzbW9vYy5jbg==',
|
||
#解码后为X-Access-Origin: https://www.casmooc.cn,应该是固定字段,请求头中必须包含这个值
|
||
}
|
||
|
||
response_code = requests.post(url_code, headers=headers, json=data_code, timeout=10)
|
||
if response_code.status_code == 200:
|
||
# 解析JSON数据
|
||
data = response_code.json()
|
||
#print(data)
|
||
else:
|
||
data = {}
|
||
print(f"请求失败,状态码:{response_code.status_code}")
|
||
uuid = data['data']['uuid']
|
||
captcha_url = data['data']['codeBase64']
|
||
#print(uuid)
|
||
|
||
# 去掉前缀 'data:image/jpeg;base64,'
|
||
header, encoded = captcha_url.split(',', 1)
|
||
|
||
# 解码Base64数据
|
||
image_data = base64.b64decode(encoded)
|
||
|
||
# 使用BytesIO将字节数据转换为PIL图像
|
||
image = Image.open(BytesIO(image_data))
|
||
|
||
# 显示图像
|
||
#image.show()
|
||
|
||
# 如果需要保存图像,可以使用image.save方法
|
||
image.save('Captcha.png')
|
||
|
||
ocr = ddddocr.DdddOcr()
|
||
with open('Captcha.png', 'rb') as f:
|
||
img_bytes = f.read()
|
||
Captcha_res = ocr.classification(img_bytes)
|
||
print("验证码为:", Captcha_res)
|
||
# 定义登录URL和验证码URL
|
||
login_url = 'https://www.casmooc.cn/server/modules/eosoperator/login'
|
||
#captcha_url = 'data:image/jpeg;base64,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'
|
||
|
||
# 定义登录表单数据
|
||
login_data = {
|
||
"userID": username, #"dingxr@microsate.com"
|
||
"password": password, #"WXWXsstc_2018"
|
||
"code": Captcha_res,
|
||
"codeUuid": uuid
|
||
}
|
||
# 发送登录请求
|
||
response_login = session.post(login_url, json=login_data, headers=headers, timeout=10)
|
||
time.sleep(4)
|
||
# 检查是否登录成功
|
||
if response_login.status_code == 200 and response_login.json()['code'] == '0':
|
||
print("----------登录成功----------")
|
||
print("等待系统初始化")
|
||
time.sleep(4)
|
||
data_login = response_login.json()
|
||
# 获取token
|
||
token = data_login['data']['token']
|
||
else:
|
||
print("!!!!!!!!登录失败!!!!!!!!")
|
||
token = ''
|
||
return token
|
||
|
||
# 获取需要学习的网课列表
|
||
def get_class_list(token):
|
||
url = 'https://www.casmooc.cn/server/api/course/myList'
|
||
headers = {
|
||
'x-access-origin': 'aHR0cHM6Ly93d3cuY2FzbW9vYy5jbg==',
|
||
'x-access-token': token
|
||
}
|
||
#获取当前年份
|
||
year = str(time.localtime().tm_year)
|
||
myList_data = {
|
||
"formData": {
|
||
"courseName": "",
|
||
"isCompleted": 0, # 获取未学完课程
|
||
"year": year
|
||
},
|
||
"pageData": {
|
||
"currentPage": 1, # 获取第一页的课程
|
||
"pageSize": 20, # 一页有多少课程,默认为5
|
||
"sortName": "",
|
||
"sortOrder": ""
|
||
}
|
||
}
|
||
response_class = session.post(url, json=myList_data, headers=headers, timeout=10)
|
||
time.sleep(4)
|
||
# 检查是否获取成功
|
||
if response_class.status_code == 200 and response_class.json()['code'] == '0':
|
||
print("获取未学习课程列表成功")
|
||
myList = response_class.json()
|
||
course_list = myList['data']['list']
|
||
#print(course_list)
|
||
else:
|
||
print("获取未学习课程列表失败")
|
||
course_list = []
|
||
return course_list
|
||
|
||
#刷课时
|
||
def swipe_class(token, course_list):
|
||
# 判断课程是否分节,比如上中下
|
||
url_chapter = 'https://www.casmooc.cn/server/api/course/chapter'
|
||
# 获取观看锁
|
||
url_submitLock = 'https://www.casmooc.cn/server/api/study/submitLock'
|
||
# 提交观看记录
|
||
url_submit = "https://www.casmooc.cn/server/api/study/submit"
|
||
# 确认课程id
|
||
url_detail = "https://www.casmooc.cn/server/api/course/detail"
|
||
headers = {
|
||
'x-access-origin': 'aHR0cHM6Ly93d3cuY2FzbW9vYy5jbg==',
|
||
'x-access-token': token
|
||
}
|
||
|
||
def submit_and_detail(belongCourseId, courseId, studyLockUUID):
|
||
#此处设置观看速度 30为正常速度,数字越小速度越快,必须为正整数
|
||
if speed in ['1', '1.5', '2', '3']:
|
||
submit_time = 30 / float(speed)
|
||
else:
|
||
submit_time = 30
|
||
time.sleep(submit_time)
|
||
data_submit = {
|
||
"belongCourseId": belongCourseId,
|
||
"courseId": courseId,
|
||
"isRecordAudio": 0,
|
||
"lastLearnTime": 0,
|
||
"recordDuration": '30',
|
||
"studyLockUUID": studyLockUUID
|
||
}
|
||
data_detail = {
|
||
"id": courseId
|
||
}
|
||
#提交观看记录
|
||
response_submit = session.post(url_submit, headers=headers, json=data_submit, timeout=10)
|
||
if response_submit.status_code == 200:
|
||
# 解析JSON数据
|
||
pass
|
||
#data = response_submit.json()
|
||
#print("提交数据结果\n", data)
|
||
else:
|
||
print(f"请求失败,状态码:{response_submit.status_code}")
|
||
time.sleep(2)
|
||
|
||
#当前进度信息
|
||
response_detail = session.post(url_detail, headers=headers, json=data_detail, timeout=10)
|
||
if response_detail.status_code == 200:
|
||
# 解析JSON数据
|
||
data = response_detail.json()
|
||
print("该课程已学习时长(秒)")
|
||
print(data['data']['trueStudyTime'])
|
||
print("该课程学习进度(百分比)")
|
||
trueStudyProgress = data['data']['trueStudyProgress']
|
||
print(trueStudyProgress, '%')
|
||
if trueStudyProgress > 95.0:
|
||
print('该课程学习完毕')
|
||
return False
|
||
else:
|
||
return True
|
||
else:
|
||
print(f"请求失败,状态码:{response_detail.status_code}")
|
||
return True
|
||
|
||
for course in course_list:
|
||
# 刷完一个网课休息一分钟
|
||
print('一分钟后开始学习')
|
||
time.sleep(60)
|
||
courseId = course['courseId']
|
||
courseName = course['courseName']
|
||
data_chapter = {
|
||
"chapterName": "",
|
||
"firstCourseId": courseId
|
||
}
|
||
response_chapter = session.post(url_chapter, headers=headers, json=data_chapter, timeout=10)
|
||
#判断视频是否分节, 访问url_chapter,若不为空则分节
|
||
if response_chapter.json()['data'] != []:
|
||
print('该课程需分节学习')
|
||
childList_flag = 0 # 用于标记是第几个分节视频
|
||
for course_data in response_chapter.json()['data']:
|
||
print("正在学习:", course_data['childList'][0]['secondCourseName'])
|
||
secondCourseId = course_data['childList'][0]['secondCourseId'] # 分节后的课程ID
|
||
lockIndex = int(time.time() * 1000)
|
||
data_submitLock = {
|
||
"belongCourseId": courseId, # 此处为视频ID, 若不分节,此处为空
|
||
"courseId": secondCourseId, # 此处ID为具体播放视频ID
|
||
"source": "courseVideoComponent", #文档类学习为courseView
|
||
"lockIndex": lockIndex, #由播放时的毫秒级时间戳组成
|
||
"is1Lock": 1 #文档类学习没有该属性
|
||
}
|
||
response_submitLock = session.post(url_submitLock, headers=headers, json=data_submitLock, timeout=10)
|
||
studyLockUUID = response_submitLock.json()['data']
|
||
#开始刷课, Flag判断是否学习完,学习完为False
|
||
Flag = True
|
||
while Flag:
|
||
# 此处设置观看速度 30为正常速度,数字越小速度越快,必须为正整数
|
||
if speed in ['1', '1.5', '2', '3']:
|
||
submit_time = 30/float(speed)
|
||
else:
|
||
submit_time = 30
|
||
time.sleep(submit_time)
|
||
data_submit = {
|
||
"belongCourseId": courseId,
|
||
"courseId": secondCourseId,
|
||
"isRecordAudio": 0,
|
||
"lastLearnTime": 0,
|
||
"recordDuration": '30',
|
||
"studyLockUUID": studyLockUUID
|
||
}
|
||
# 提交观看记录
|
||
response_submit = session.post(url_submit, headers=headers, json=data_submit, timeout=10)
|
||
if response_submit.status_code == 200:
|
||
# 解析JSON数据
|
||
pass
|
||
# data = response_submit.json()
|
||
# print("提交数据结果\n", data)
|
||
else:
|
||
print(f"请求失败,状态码:{response_submit.status_code}")
|
||
time.sleep(2)
|
||
# 当前进度信息
|
||
response_chapter = session.post(url_chapter, headers=headers, json=data_chapter, timeout=10)
|
||
if response_chapter.status_code == 200:
|
||
# 解析JSON数据
|
||
data = response_chapter.json()
|
||
# 获取当前时间戳
|
||
current_time = time.localtime()
|
||
# 格式化时间
|
||
formatted_time = time.strftime("%Y-%m-%d %H:%M:%S", current_time)
|
||
# 打印时间
|
||
print("该课程分节已学习时长(秒) 当前时间:", formatted_time)
|
||
print(data['data'][childList_flag]['childList'][0]['trueStudyTime'])
|
||
print("该课程学习进度(百分比)")
|
||
trueStudyProgress = data['data'][childList_flag]['childList'][0]['trueStudyProgress']
|
||
print(trueStudyProgress, '%')
|
||
if trueStudyProgress > 95.0:
|
||
print('该课程学习完毕')
|
||
childList_flag += 1 #data列表索引后移
|
||
break
|
||
else:
|
||
continue
|
||
else:
|
||
print(f"请求失败,状态码:{response_chapter.status_code}")
|
||
else:
|
||
print('正在学习:', courseName)
|
||
data_detail = {
|
||
"id": courseId
|
||
}
|
||
response_detail = session.post(url_detail, headers=headers, json=data_detail, timeout=10)
|
||
#判断课程是否属于电子阅读类
|
||
if response_detail.status_code == 200:
|
||
# 解析JSON数据
|
||
enterUrl = response_detail.json()['data']['enterUrl']
|
||
#如果是文档
|
||
if enterUrl.endswith('.pdf'):
|
||
lockIndex = int(time.time() * 1000)
|
||
data_submitLock = {
|
||
"belongCourseId": '', # 此处为视频ID, 若不分节,此处为空
|
||
"courseId": courseId, # 此处ID为具体播放视频ID
|
||
"lockIndex": lockIndex, # 由播放时的毫秒级时间戳组成
|
||
"source": "courseView", # 文档类学习为courseView
|
||
}
|
||
response_submitLock = session.post(url_submitLock, headers=headers, json=data_submitLock, timeout=10)
|
||
studyLockUUID = response_submitLock.json()['data']
|
||
# 开始刷课
|
||
Flag = True
|
||
while Flag:
|
||
Flag = submit_and_detail('', courseId, studyLockUUID)
|
||
|
||
#如果是视频
|
||
else:
|
||
lockIndex = int(time.time() * 1000)
|
||
data_submitLock = {
|
||
"belongCourseId": '', # 此处为视频ID, 若不分节,此处为空
|
||
"courseId": courseId, # 此处ID为具体播放视频ID
|
||
"source": "courseVideoComponent", # 文档类学习为courseView
|
||
"lockIndex": lockIndex, # 由播放时的毫秒级时间戳组成
|
||
"is1Lock": 1 # 文档类学习没有该属性
|
||
}
|
||
response_submitLock = session.post(url_submitLock, headers=headers, json=data_submitLock, timeout=10)
|
||
studyLockUUID = response_submitLock.json()['data']
|
||
# 开始刷课
|
||
Flag = True
|
||
while Flag:
|
||
Flag = submit_and_detail('', courseId, studyLockUUID)
|
||
else:
|
||
print(f"请求失败,状态码:{response_detail.status_code}")
|
||
print('无法判断,学习下一个课程')
|
||
continue
|
||
|
||
|
||
|
||
if __name__ == '__main__':
|
||
# 读取文件并设置全局变量
|
||
read_config_file()
|
||
print(f"用户名: {username}")
|
||
print(f"密码: {password}")
|
||
print(f"播放速度: {speed}倍速")
|
||
#模拟登录获取token
|
||
token = login()
|
||
if token != '':
|
||
#获取课程列表
|
||
course_list = get_class_list(token)
|
||
swipe_class(token, course_list)
|
||
else:
|
||
print('模拟登录失败,请检查用户名密码是否正确') |