SCU抢课脚本

分析过程

image-20211229145405750.png

image-20211229145254043.png

image-20211229145230672.png

初步计算前两个结果相符

Screenshot_20211229_145200

找tokenvalue,最后在教务处主页找到,初步估计是每次选完刷新一次tokenvalue的值

image-20211229164921722.png

这个貌似是教务处防脚本选课的机制(可能,因为这个功能在我看来没多大用处,具体对web机制很不了解),

它是模拟你查找一个老师,你正常选课时肯定不可避免地鼠标移到选课老师上面,它会利用类似hover的功能对这个老师进行一个查找,就是下图的queryTeacherJL,返回的数据也很让我迷惑,所以结合前人代码,我感觉代码加上这个应该没啥问题了(希望不会被请喝茶)

image-20211229193239678.png

代码部分

设置各个接口

image-20220116093724749.png

设置伪装请求头

image-20220116093826475.png

要注意去除cookie,因为每回值不一样

登陆函数

image-20220116094010542.png

采用循环登录方式,错误的话不断重新来,直到登陆成功

此处采用了下载验证码图片的功能,能够模拟出我们教务系统登录的方式

对登陆时后端接受的信息分析,其中密码采取了md5加密,在函数中导入此模块,对密码加密进行传输

获取响应数据中存在“欢迎您”即 登陆成功!

获取课表

# 获取选课结果课表
def getClassTable(s):
    classTable_res = s.get(url=classTable_url, headers=headers)
    class_list = classTable_res.json()['xkxx']
    print('您已选的所有课程如下:')
    cnt = 0
    print("--- {0:25} {1:8} {2}_{3}".format('课程名', '老师名', '课程号', '课序号'))    # 使用format时一定不要在:间空格,否则报错,找了老长时间问题了
    for i in class_list:
        for j in i.values():
            cnt += 1
            print("[{0}] {1:25} {2:8} {3}_{4}".format(
                str(cnt),
                j['courseName'],
                re.split(r"[*]", j['attendClassTeacher'])[0],
                list(j['id'].values())[0],
                list(j['id'].values())[1]
            ))
            class_dict = {}
            if j['courseName'] not in selected_class:
                class_dict['kch'] = list(j['id'].values())[0]
                class_dict['kxh'] = list(j['id'].values())[1]
                class_dict['kcm'] = j['courseName']
                selected_class.append(class_dict)

这里采用列表的数据形式接收课程信息,循环打印,并添加到字典中,为后面 检查是否已选课做准备

下面是json数据格式化后的样式,据此找到各个部分的信息

[
    {
        "105396020_21": {
            "attendClassTeacher": "李喜雪* ", 
            "courseCategoryCode": "", 
            "courseCategoryName": "", 
            "courseName": "大学英语(创意阅读)-4", 
            "coursePropertiesCode": "001", 
            "coursePropertiesName": "必修", 
            "courseSelectionTime": "20211224012618", 
            "dgFlag": "81086440,李喜雪(无)", 
            "examTypeCode": "01", 
            "examTypeName": "考试", 
            "flag": "", 
            "fsktms": "", 
            "id": {
                "coureNumber": "105396020", 
                "coureSequenceNumber": "21", 
                "executiveEducationPlanNumber": "2021-2022-2-1", 
                "studentNumber": "xxxxxxx"    // 此处省略
            }, 
            "programPlanName": "网络空间安全培养方案", 
            "programPlanNumber": "8160", 
            "restrictedCondition": "", 
            "rlFlag": "81086440,李喜雪(无)", 
            "selectCourseStatusCode": "016", 
            "selectCourseStatusName": "置入", 
            "sfczfskt": "", 
            "studyModeCode": "01", 
            "studyModeName": "正常", 
            "timeAndPlaceList": [], 
            "unit": 2, 
            "xkzy": "", 
            "ywdgFlag": "", 
            "zkxh": ""
        }, 
        "106777020_01": {}, 
        "107061050_44": {}, 
        "107118000_12": {}, 
        "314003030_03": {}, 
        "314037040_03": {}, 
        "314038030_03": {}, 
        "314039040_01": {}, 
        "314042020_01": {}, 
        "314049020_01": {}, 
        "314056030_03": {}, 
        "999005030_05": {}
    }
]

搜索函数

# 搜索相关课程信息
def search(s):
    search = input('请输入你要搜索的关键字(课程名或者老师名):')
    data = {
        'searchtj': search,
        'xq': '0',
        'jc': '0',
        'kclbdm': ''
    }
    search_json = s.post(url=search_url, data=data, headers=headers).json()
    result_list = search_json['rwRxkZlList']  # 此时成为字符串,所以后续错误,因为按照列表来读取数据的
    result_list = json.loads(result_list)
    if not result_list: # 判断列表为空
        print('无结果')
        return
    for item in result_list:
        print('{0}----{1}----{2}_{3}----课余量:{4}'.format(item['kcm'], item['skjs'], item['kch'], item['kxh'], item['bkskyl']))

获取选课信息

# 获取要选课的各种信息
def getSelectInfo(s, kch, kxh, kcm):
    data = {
        'searchtj': kch,
        'xq': '0',
        'jc': '0',
        'kclbdm': ''
    }
    search_json = s.post(url=search_url, data=data, headers=headers).json()
    result_list = search_json['rwRxkZlList']  # 此时成为字符串,所以后续错误,因为按照列表来读取数据的
    result_list = json.loads(result_list)
    if not result_list:  # 判断列表为空
        return -2   # 已选同课程号的课
    for item in result_list:
        if item['kcm'] == kcm and item['kch'] == kch and item['kxh'] == kxh:    # 判断输入信息无误
            if int(item['bkskyl']) > 0:
                return 1    # 有课余量
            else:
                return -1   # 无课余量
        else:
            return -3   # 输入信息有误

根据不同情况返回值不同

防喝茶机制

# 防教务处请喝茶机制(应该是这样),它是模拟你查找一个老师,你正常选课时肯定不可避免地鼠标移到选课老师上面,它会利用类似hover的功能对这个老师进行一个查找
def queryTeacherJL(s, kch, kxh):
    query_data = {
        'id': '2021-2022-1@' + kch + '@' + kxh
    }
    return s.post(url=query_url, data=query_data, headers=headers)

选课函数

# 选课
def select(s):
    kch = input('请输入课程号(9位数字):')
    kxh = input('请输入课序号(01、02等):')
    kcm = input('请输入课程名(完整的):')
    new_kcms = kcm + kch + '@' + kxh
    kcms = ''
    for i in new_kcms:
        kcms = kcms + str(ord(i)) + ','
    cnt = 0
    while True:
        if not queryTeacherJL(s, kch, kxh):
            return  # 及时退出
        cnt += 1
        print('第{0}轮选课:'.format(cnt))
        select_result = getSelectInfo(s, kch, kxh, kcm)
        if select_result == 1:
            token_res = s.get(url=token_url, headers=headers).text
            tokenvalue = re.compile("([a-fA-F0-9]{32})").findall(token_res)[0]
            select_data = {
                'dealType': '5',
                'kcIds': kch + '@' + kxh + '@2021-2022-2-1',
                # 分析:大的数应该是中文的unicode编码,小的是课程号和课序号编码
                # 'kcms': '20154, 31867, 30340, 35821, 35328, 45, 45, 35821, 35328, 23398, 20837, 38376, 40, 49, 48, 53, 50, 57, 57, 48, 50, 48, 64, 48, 49, 41',
                       # 人类的语言--语言学入门(105299020_01)
                       # 数码摄影科学与艺术(205300020_01)
                       # 25968, 30721, 25668, 24433, 31185, 23398, 19982, 33402, 26415, 40, 50, 48, 53, 51, 48, 48, 48, 50, 48, 64, 48, 49, 41,
                # 经验证确实如此,这些数字是Unicode的10进制编码
                'kcms': kcms,
                'fajhh': '8160',
                'sj': '0_0',
                'searchtj': '',
                'kclbdm': '',
                'inputCode': '',
                'tokenValue': tokenvalue
            }
            select_res = s.post(url=select_url, data=select_data, headers=headers).json()
            print(select_res)
            getClassTable(s)
            break
        elif select_result == -1:
            print('无课余量')
        elif select_result == -2:
            print('已选同课程号的课或者课程号错误')
            return
        elif select_result == -3:
            print('输入信息有误')
            return
        time.sleep(random.uniform(1.5, 3))

主函数

def run(s):
    login(s)
    print(Fore.LIGHTRED_EX + "*****欢迎来到Xherlock的选课系统*****")
    print(Fore.LIGHTRED_EX + "          (1) 查看课表")
    print(Fore.LIGHTRED_EX + "          (2) 查询课程信息")
    print(Fore.LIGHTRED_EX + "          (3) 选课")
    print(Fore.LIGHTRED_EX + "          (4) 退出")
    print(Style.RESET_ALL)
    while True:
        choice = input('请输入选项:')
        if choice == '1':
            getClassTable(s)
        elif choice == '2':
            search(s)
        elif choice == '3':
            select(s)
        elif choice == '4':
            quit()
        
        
def quit():
    print(Fore.LIGHTBLUE_EX + "感谢您使用Xherlock的文件管理系统!")
    sys.exit(0)


if __name__ == '__main__':
    # ASCII字符签名,好看而已
    print(Fore.LIGHTMAGENTA_EX + "___   ___  __    __   _______ .______       __        ______     ______  __  ___")
    print("{0}\\  \\ /  / |  |  |  | |   ____||   _  \\     |  |      /  __  \\   /      ||  |/  /".format(Fore.LIGHTMAGENTA_EX))
    print("{0} \\  V  /  |  |__|  | |  |__   |  |_)  |    |  |     |  |  |  | |  ,----\'|  \'  / ".format(Fore.LIGHTMAGENTA_EX))
    print("{0}  >   <   |   __   | |   __|  |      /     |  |     |  |  |  | |  |     |    <  ".format(Fore.LIGHTMAGENTA_EX))
    print("{0} /  .  \\  |  |  |  | |  |____ |  |\\  \\----.|  `----.|  `--\'  | |  `----.|  .  \\ ".format(Fore.LIGHTMAGENTA_EX))
    print("{0}/__/ \\__\\ |__|  |__| |_______|| _| `._____||_______| \\______/   \\______||__|\\__\\".format(Fore.LIGHTMAGENTA_EX))
    print(Style.RESET_ALL)
    session = requests.session()  # 保存cookie
    run(session)
最后修改:2022 年 01 月 16 日
如果觉得我的文章对你有用,请随意赞赏