隐藏域input里面放当前时间_两天时间挑战python实现广州大学抢课?(抢课篇)(第二次修改补充)...

795d791f69ca09bea923a3b0f717c9f0.png

最后一天,明天就抢课了。昨天弄完搜索后(没看前面两篇的同学可以进入我的主页查看),差最后一步提交,因为最后一步提交数据操作有点特殊,留到今天来写。

正当我们想愉快地完成最后一步提交的抓包时,看到 禁选 的红字,当时就懵B了。

7a46a7aad007d4c0b9ade76e96c79e4f.png
禁选

表单要发送的数据一般存储在后台无法直接获得,难道就这样结束吗?当然有方法,第一种是保存上一次的抢课抓包记录,记住这个请求,像正方这种系统一般来说表单发送的数据都不会有什么变化的。另一种就是绕过这个 禁选 ,谷歌浏览器F12网页进入并调试这个js文件:

85ca26c772ba365ad7b2b470d4e8d2d3.png
抢课按钮出现条件

看到οnclick=chooseCourseZzxk,搜索这个js函数,一般这个js都是一个post请求发送链接(因为还没开始选课,有可能还没有将选课按钮触发的js文件放出来,有可能抓包数据还是没有)。

因为上一学期我保存了抓包记录,所以上面的方法只是个思路没有尝试过,不一定可行。这里就直接分享上学期提交按钮发送的数据,其实提交按钮也只是发送了一个请求d。

提交按钮抓包请求:

url = 'http://jwxt.gzhu.edu.cn/jwglxt/xsxk/zzxkyzb_xkBcZyZzxkYzb.html?'
hearder['Referer'] = 'http://jwxt.gzhu.edu.cn/jwglxt/xsxk/zzxkyzb_cxZzxkYzbIndex.html?gnmkdm=N253512&layout=default&su=********'
param_data = {
    'gnmkdm': 'N253512',
    'su': '********'
}
form_data = {
    'jxb_ids': '',  # 很长很长的数据
    'kch_id': '131200949',
    'kcmc': '(131200949)世界电影大师 - 2.0 学分',
    'rwlx': '2',
    'rlkz': '0',
    'rlzlkz': '0',
    'sxbj': '0',
    'xxkbj': '0',
    'qz': '0',
    'cxbj': '0',
    'xkkz_id': 'A9E3BC0A172151E9E053206411ACB231',
    'njdm_id': '2018',
    'zyh_id': '6BC10423C2C23226E053206411AC7AEF',
    'kklxdm': '10',
    'xklc': '1',
    'xkxnm': '2020',
    'xkxqm': '3'
}
request = session.post(url, params=param_data, data=form_data, hearders=hearder)

kcmc:这个字段是按照这个格式获得:(课程编号)课程名称 - 课程学分 学分。中间注意一下空格。

sxbj:这个字段提示一下是在js生成的,并且是根据rlkz变化的,也就是我们的主修课程和选修课程选择(如下图):

67e83d354fdca1d52a74b82a8f97cad0.png
sxbj的值

让我疑惑的是qz这个参数怎么找都找不到,对比过不同的抓包记录它的值都是0,这里就直接写死0了,有大佬找到生成方法的可在下面评论区讲解一下。

其他的参数都可以直接通过搜索字段名或者值得出结果,具体的请求方法大同小异,按照前两篇的经验发送请求就行,这里就不一一讲解了。

提醒一下大家,因为到时候抢课或涉及到服务器卡死状态,所以每个请求必须添加超时处理,这里每个请求设计了3秒超时,有需要可以更改。

最后在抢课的请求简单的使用了多线程加快抢课速度。

最后附上全部代码(多线程用得有点扣脚……代码还没仔细测试,可能还有很多漏洞,后续会不断更新这篇文章完善):

import re
import requests
import time
import yzm_deal
import json
import threading
username = input('帐号:')
password = input('密码:')
seting = 160  # 验证码识别的阀值
isAutoYzm = input('n是否自动识别验证码(N/n取消): ')
enter_choose_num = ''  # 进入选修课的代号
lesson_name = ''  # 课程名称
url = 'https://cas.gzhu.edu.cn/cas_server/login?service=https%3A%2F%2Fcas.gzhu.edu.cn%3A443%2Fshunt%2Findex.jsp'  # 官网登录地址
header = {  # 浏览器请求头
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/9.0.084.36 Safari/536.5',
}
session = requests.session()  # cookie自动保存管理器

flag = 0  # 线程暂停标志
gLock = threading.Lock()  # 线程锁


def time_out(e):  # 网页异常输出内容
    if str(e).find('timed out') > -1:
        print('网页繁忙,重新加载中。。。。')
    else:
        print(e)


def get_time():  # 北京时间
    this_url = 'http://time.tianqi.com/'
    while True:
        try:
            request = session.get(this_url, headers=header, timeout=8)
            result = re.search(r'times">(.*?)<', request.text).groups()[0]
            print(result)
            return request
        except Exception as e:
            time_out(e)


def Page():  # 进入首页
    print('------开始进入首页-------')
    while True:
        try:
            request = session.get(url, headers=header, timeout=8)
            if request.text.find('name="lt"') > -1:
                print('*****进入首页成功******')
                return request
        except Exception as e:
            time_out(e)


def captcha():  # 验证码处理
    this_url = 'https://cas.gzhu.edu.cn/cas_server/captcha.jsp'
    print('------开始加载验证码-------')
    while True:
        try:
            request = session.get(this_url, headers=header, timeout=8)
            print('*****验证码加载成功******')
            with open('code.png', 'wb')as f:
                f.write(request.content)
            yzm_deal.yzm_print(seting)
            if isAutoYzm != 'N' and isAutoYzm != 'n':
                code = yzm_deal.yzm_deal(seting)
                print('code OCR result: ' + code)
                return code
            else:
                code = input('Please input the code: ')
                return code
        except Exception as e:
            time_out(e)


def login(req):  # 登录
    global username
    global password
    this_url = 'https://cas.gzhu.edu.cn' + re.search(r'fm1" action="(.*?)"', req.text).groups()[0]  # 登录按钮链接
    form_data = {
        'username': username,
        'password': password,
        'captcha': '',  # 验证码
        'warn': 'true',  # 固定值
        'lt': '',
        'execution': '',
        '_eventId': 'submit',  # 固定值
        'submit': '登录'  # 固定值
    }
    header['Referer'] = url
    print('------开始登录-------')
    while True:
        try:
            form_data['lt'] = re.search(r'lt" value="(.*?)"', req.text).groups()[0]  # 登录form['lt']
            form_data['execution'] = re.search(r'execution" value="(.*?)"', req.text).groups()[0]  # 登录form['execution']
            form_data['captcha'] = captcha()
            req = session.post(this_url, data=form_data, headers=header, timeout=8)
            if req.text.find('账号或密码错误') > -1:
                print('账号或密码错误!!n')
                username = input('重新输入帐号:')
                password = input('重新输入密码:')
                form_data['username'] = username
                form_data['password'] = password
            elif req.text.find('验证码不正确') > -1:
                print('验证码不正确')
            elif req.text.find('当前选课期间') > -1:
                print('*****登录成功******')
                break
            else:
                print('网页繁忙,重新加载中。。。。')
                time.sleep(2)
        except Exception as e:
            time_out(e)

    print('------开始进入系统-------')
    while True:
        try:
            request = session.get('http://jwxt.gzhu.edu.cn/sso/lyiotlogin', headers=header, timeout=8)
            if request.text.find('自主选课') > -1:
                print('*****进入系统成功******')
                time.sleep(0.5)
                return request
            else:
                print('网页繁忙,重新加载中。。。。')
                time.sleep(2)
        except Exception as e:
            time_out(e)


def get_name(req):  # 获取名字
    this_url = 'http://jwxt.gzhu.edu.cn/jwglxt/xtgl/index_cxYhxxIndex.html'
    param_data = {
        'xt': 'jw',
        'localeKey': 'zh_CN',
        '_': '',
        'gnmkdm': 'index',
        'su': username
    }
    header['Referer'] = req.url
    print('------开始获取姓名-------')
    while True:
        try:
            param_data['_'] = str(int(time.time() * 1000))
            request = session.get(this_url, params=param_data, headers=header, timeout=8)
            name = re.search(r'media-heading">(.*?)</', request.text).groups()[0]
            print('*****获取姓名成功******')
            break
        except Exception as e:
            time_out(e)
    print('n欢迎   ' + name + '   同学')


def enter_choose(req):  # 进入自主选课
    global enter_choose_num
    global ver
    gnmkdm = re.search(r"'(.{1,8})','(.{10,50})','自主选课", req.text).groups()
    enter_choose_num = gnmkdm[0]
    this_url = 'http://jwxt.gzhu.edu.cn/jwglxt' + gnmkdm[1]
    param_data = {
        'gnmkdm': gnmkdm[0],
        'layout': 'default',
        'su': username
    }
    header['Referer'] = req.url
    print('------开始进入自主选课界面------')
    while True:
        try:
            request = session.get(this_url, params=param_data, headers=header, timeout=8)
            if request.text.find('主修课程') > -1:
                print('******进入自主选课界面成功******')
                return request
            elif request.text.find('当前不属于选课阶段') > -1:
                print('对不起,当前不属于选课阶段,如有需要,请与管理员联系!')
                time.sleep(3)
            else:
                print('网页繁忙,重新加载中。。。。')
                time.sleep(3)
        except Exception as e:
            time_out(e)


def get_input_value(id, req):  # 输入返回表单隐藏域的值
    result = re.search(str(id) + '"' + '.*?value="(.*?)"', req.text).groups()[0]
    return result


def get_hidden_data_form(req):  # 获取jspage数据
    this_url = 'http://jwxt.gzhu.edu.cn/jwglxt/js/comp/jwglxt/xkgl/xsxk/zzxkYzb.js'
    ver = re.search(r'ver=(.*?)"', req.text).groups()[0]
    param_data = {
        'ver': ver
    }
    header['Referer'] = req.url
    print('-----开始jspage配置-----')
    while True:
        try:
            request = session.get(this_url, params=param_data, headers=header, timeout=8)
            result = json.loads('{"' + re.search(r'kspageD*d*D*d*', request.text).group() + '}')
            print('*****jspage配置完成******')
            return result
        except Exception as e:
            time_out(e)


def search_form_kklxdm(req, num):  # 返回主修/选修发送的信息
    result = re.findall(r'queryCourse.*?,(.*?))', req.text)
    if num == 1:
        return eval('[' + result[0] + ']')
    else:
        return eval('[' + result[1] + ']')


def get_hidden_data(req, num):  # 获取表单隐藏域信息
    this_url = 'http://jwxt.gzhu.edu.cn/jwglxt/xsxk/zzxkyzb_cxZzxkYzbDisplay.html'
    page_data = get_hidden_data_form(req)
    xkkz_id = search_form_kklxdm(req, num)[1]
    form_data = {
        'xkkz_id': xkkz_id,
        'xszxzt': get_input_value('xszxzt', req),
        'kspage': page_data['kspage'],
        'jspage': page_data['jspage']
    }
    param_data = {
        'gnmkdm': enter_choose_num,
        'su': username
    }
    header['Referer'] = req.url
    print('------开始搜索隐藏域配置------')
    while True:
        try:
            request = session.post(this_url, params=param_data, data=form_data, headers=header, timeout=8)
            if request.text.find('请使用上方的查询工具') > -1:
                print('*****搜索隐藏域配置成功******')
                return page_data, request
            else:
                print('网页繁忙,重新加载中。。。。')
                time.sleep(3)
        except Exception as e:
            time_out(e)


def search(req):  # 搜索课程
    global lesson_name
    this_url = 'http://jwxt.gzhu.edu.cn/jwglxt/xsxk/zzxkyzb_cxZzxkYzbPartDisplay.html'
    lesson_name = input('n请输入课程名称:')  # ok------------
    lesson_choose = input('n主修课程请按"1",选修课程请按"2":')
    param_data = {
        'gnmkdm': enter_choose_num,
        'su': username
    }
    if lesson_choose == '1':
        page_data, req2 = get_hidden_data(req, 1)  # 1就是主修课程
        kklxdm = search_form_kklxdm(req, 1)
    else:
        page_data, req2 = get_hidden_data(req, 2)  # 2就是选修课程
        kklxdm = search_form_kklxdm(req, 2)
    form_data = {
        'filter_list[0]': lesson_name,  # ok
        'xqh_id': get_input_value('xqh_id', req),  # ok
        'jg_id': get_input_value('jg_id_1', req),  # ok-------------
        'zyh_id': get_input_value('zyh_id', req),  # ok
        'zyfx_id': get_input_value('zyfx_id', req),  # ok
        'njdm_id': get_input_value('njdm_id', req),  # ok
        'bh_id': get_input_value('bh_id', req),  # ok
        'xbm': get_input_value('xbm', req),  # ok
        'xslbdm': get_input_value('xslbdm', req),  # ok
        'ccdm': get_input_value('ccdm', req),  # ok
        'xsbj': get_input_value('xsbj', req),  # ok
        'xkxnm': get_input_value('xkxnm', req),  # ok
        'xkxqm': get_input_value('xkxqm', req),  # ok
        'jxbzb': get_input_value('jxbzb', req),  # ok

        'sfkknj': get_input_value('sfkknj', req2),  # ok
        'sfkkzy': get_input_value('sfkkzy', req2),  # ok
        'sfznkx': get_input_value('sfznkx', req2),  # ok
        'zdkxms': get_input_value('zdkxms', req2),  # ok
        'sfkxq': get_input_value('sfkxq', req2),  # ok
        'sfkcfx': get_input_value('sfkcfx', req2),  # ok
        'kkbk': get_input_value('kkbk', req2),  # ok
        'kkbkdj': get_input_value('kkbkdj', req2),  # ok
        'sfkgbcx': get_input_value('sfkgbcx', req2),  # ok
        'sfrxtgkcxd': get_input_value('sfrxtgkcxd', req2),  # ok
        'tykczgxdcs': get_input_value('tykczgxdcs', req2),  # ok
        'rlkz': get_input_value('rlkz', req2),  # ok
        'rwlx': get_input_value('rwlx', req2),  # ok
        'xkly': get_input_value('xkly', req2),  # ok
        'bklx_id': get_input_value('bklx_id', req2),  # ok

        'kklxdm': kklxdm[0],  # ok-------------
        'kspage': str(int(page_data['jspage']) + 1),  # -------
        'jspage': str(int(page_data['jspage']) + 10),  # -------
    }
    header['Referer'] = req.url
    request1 = None
    print('------开始设置搜索数据1------')
    while True:
        try:
            request1 = session.post(this_url, params=param_data, data=form_data, headers=header, timeout=8)
            if request1.text.find('tmpList') > -1:
                print('*****设置搜索数据1成功*****')
                break
            else:
                print('网页繁忙,重新加载中。。。。')
                time.sleep(2)
        except Exception as e:
            time_out(e)
    print(request1.text + 'n')

    if len(request1.text) > 30:
        this_url2 = 'http://jwxt.gzhu.edu.cn/jwglxt/xsxk/zzxkyzb_cxJxbWithKchZzxkYzb.html'
        lesson_name = json.loads(request1.text)['tmpList'][0]['kcmc']
        form_data2 = {
            'filter_list[0]': lesson_name,  # ok
            'xqh_id': get_input_value('xqh_id', req),  # ok
            'jg_id': get_input_value('jg_id_1', req),  # ok
            'zyh_id': get_input_value('zyh_id', req),  # ok
            'zyfx_id': get_input_value('zyfx_id', req),  # ok
            'njdm_id': get_input_value('njdm_id', req),  # ok
            'bh_id': get_input_value('bh_id', req),  # ok
            'xbm': get_input_value('xbm', req),  # ok
            'xslbdm': get_input_value('xslbdm', req),  # ok
            'ccdm': get_input_value('ccdm', req),  # ok
            'xsbj': get_input_value('xsbj', req),  # ok
            'xkxnm': get_input_value('xkxnm', req),  # ok
            'xkxqm': get_input_value('xkxqm', req),  # ok

            'sfkknj': get_input_value('sfkknj', req2),  # ok
            'sfkkzy': get_input_value('sfkkzy', req2),  # ok
            'sfznkx': get_input_value('sfznkx', req2),  # ok
            'zdkxms': get_input_value('zdkxms', req2),  # ok
            'sfkxq': get_input_value('sfkxq', req2),  # ok
            'sfkcfx': get_input_value('sfkcfx', req2),  # ok
            'kkbk': get_input_value('kkbk', req2),  # ok
            'kkbkdj': get_input_value('kkbkdj', req2),  # ok
            'rwlx': get_input_value('rwlx', req2),  # ok
            'xkly': get_input_value('xkly', req2),  # ok
            'bklx_id': get_input_value('bklx_id', req2),  # ok
            'rlkz': get_input_value('rlkz', req2),  # ok

            'kklxdm': kklxdm[0],  # ok--------------
            'kch_id': '',
            'xkkz_id': kklxdm[1],
            'cxbj': '',
            'fxbj': ''
        }
        form_data2['kch_id'] = re.search(r'kch_id":"(d*)', request1.text).groups()[0]
        form_data2['cxbj'] = re.search(r'cxbj":"(d*)', request1.text).groups()[0]
        form_data2['fxbj'] = re.search(r'fxbj":"(d*)', request1.text).groups()[0]
        header['Referer'] = req.url
        print('------开始设置搜索数据2------')
        while True:
            try:
                request2 = session.post(this_url2, params=param_data, data=form_data2, headers=header, timeout=8)
                if request2.text.find('do_jxb_id') > -1:
                    print('*****设置搜索数据2成功*****')
                    print(request2.text)
                    return post_lesson_data(req, kklxdm, req2, request1, request2)
                else:
                    print('网页繁忙,重新加载中。。。。')
                    time.sleep(2)
            except Exception as e:
                time_out(e)

    else:
        print('没有查询到课程!!!')
        isContinue = input('是否继续抢课?(Y/N)')
        if isContinue == 'Y' or isContinue == 'y' or isContinue == '':
            return search(req)
        else:
            exit(0)


def post_lesson_data(req, kklxdm, req2, requests1, requests2):  # 获取提交抢课按钮的发送数据
    this_url = 'http://jwxt.gzhu.edu.cn/jwglxt/xsxk/zzxkyzb_xkBcZyZzxkYzb.html'
    search_data1 = json.loads(requests1.text)
    search_data2 = json.loads(requests2.text)
    param_data = {
        'gnmkdm': enter_choose_num,
        'su': username
    }
    form_data = {
        'jxb_ids': '',
        'kch_id': '',
        'kcmc': '',
        'njdm_id': get_input_value('njdm_id', req),  # ok
        'zyh_id': get_input_value('zyh_id', req),  # ok
        'xkxnm': get_input_value('xkxnm', req),  # ok
        'xkxqm': get_input_value('xkxqm', req),  # ok

        'rwlx': get_input_value('rwlx', req2),  # ok
        'rlkz': get_input_value('rlkz', req2),  # ok
        'rlzlkz': get_input_value('rlzlkz', req2),  # ok
        'xklc': get_input_value('xklc', req2),  # ok

        'sxbj': '',  # 0为选修课 1为必修课
        'xxkbj': '',
        'qz': '0',  # 写死,没有变化
        'cxbj': '',

        'xkkz_id': kklxdm[1],
        'kklxdm': kklxdm[0]
    }
    form_data['jxb_ids'] = search_data2[0]['do_jxb_id']
    form_data['kch_id'] = search_data1['tmpList'][0]['kch_id']
    form_data['kcmc'] = '(' + form_data['kch_id'] + ')' + lesson_name + ' - ' + search_data1['tmpList'][0]['xf'] + ' 学分'

    form_data['xxkbj'] = search_data1['tmpList'][0]['xxkbj']
    form_data['cxbj'] = search_data1['tmpList'][0]['cxbj']
    if form_data['rwlx'] == '1':
        form_data['sxbj'] = '1'
    else:
        form_data['sxbj'] = '0'
    header['Referer'] = req.url
    return this_url, param_data, form_data, header


def post_action(num, this_url, param_data, form_data, header):  # 抢课
    global flag
    while True:
        try:
            if flag == 1:
                break
            request = session.post(this_url, params=param_data, data=form_data, headers=header, timeout=3)
            print('***********' + '当前线程' + str(num) + '************')
            print(request.text)
            if request.text.find('flag') > -1:
                if json.loads(request.text)['flag'] == '1':
                    print('抢课成功')
                elif request.text.find('学分') > -1:
                    print('超过通识选修本学期最高选课学分限制,不可选!')
                else:
                    print('未到选课时间')
                    continue
                gLock.acquire()
                flag = 1
                gLock.release()
                break
        except Exception as e:
            time_out(e)


def task(this_url, param_data, form_data, header):  # 多线程抢课
    print('nn---------开始抢课--------------n')
    input('回车键开始:n')
    t_obj = []
    for i in range(4):
        t = threading.Thread(target=post_action, args=(i + 1, this_url, param_data, form_data, header))
        t_obj.append(t)
    for i in t_obj:
        i.start()
    for i in t_obj:
        i.join()


def main():
    global flag
    req = get_time()  # 获取北京时间
    req = Page()  # 进入首页
    req = login(req)  # 登录
    get_name(req)  # 获取名称
    req = enter_choose(req)  # 进入自主选课界面
    while True:
        flag = 0  # 线程暂停标志置0
        this_url, param_data, form_data, header = search(req)  # 搜索课程界面
        task(this_url, param_data, form_data, header)  # 多线程抢课
        isContinue = input('是否继续抢课?(Y/N)-------------')
        if isContinue == 'Y' or isContinue == 'y' or isContinue == '':
            continue
        else:
            exit(0)  # 退出


if __name__ == '__main__':
    main()

yzm_deal.py文件:

from PIL import Image
import tesserocr  # 没有安装的请注释掉


def binarizing(img, seting):
    # 二值化,seting为控制阀值
    img = img.convert("L")  # 转灰度
    pixdata = img.load()
    w, h = img.size
    for x in range(w):
        for y in range(h):
            if pixdata[x, y] < seting:
                pixdata[x, y] = 0
            else:
                pixdata[x, y] = 255
    return img


def yzm_deal(seting):
    img = binarizing(Image.open('code.png'), seting)
    img.save('code.png', quality=100)
    result = tesserocr.image_to_text(img, lang='eng')
    exclude_char_list = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'
    text = ''.join([x for x in result if x in exclude_char_list])
    return text


def yzm_print(seting):
    # 手动输入打印验证码
    img = binarizing(Image.open('code.png'), seting)
    pixdata = img.load()
    w, h = img.size
    for y in range(h - 7):
        for x in range(w):
            if pixdata[x, y] == 0:
                print('8', end='')
            else:
                print(' ', end='')
        print()

两天的任务可以说是基本完成了。第一次写知乎文章发现还蛮辛苦的,连续2天写到凌晨2点。不过想想感觉成果也是值得~~路过的大佬们觉得哪里说的不对的可以指点一下。

7月10号下午补充:

先附上抢课截图:

fcb4c3ca7e836d4fcbbd591ca5879f21.png
第一门课

多线程用得有点抠。学校官网11:30关闭了自主选课入口(没有课程),12:00又重新开放,需要在11:00就登入进去准备好提交数据,12点准时post提交。

还有很重要的一点因为代码抢课前面的所有请求都没有用到多线程,所以程序都只不过是一个简单自动登陆到抢课页面的脚本,遇到网络堵塞还是跟你手动抢课一样会die掉,所以程序必须在开始抢课提前半小时进入到等待发起抢课请求的那一步,不然前面的登录操作会费很多时间甚至登不上。

中午登录官网一直崩溃状态,下午登录看到的今天抢课信息:

80e1139b041d530d34ac305a2bfb20fb.png

继续选第二门课时:

b56dbe1ff3702d309098cfc3585583d1.png
第二门课

异常请求处理没有弄好。之后我会不断更新和修改这篇文章的代码。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值