CUMT公选课抢课脚本

  1 min to read  

List [CTL]

    2019/2/25 更新

    想到更好的思路实现选课开始时立即秒杀选课,而不会因为连不上教务系统只能后期监控选课:

    抢课开始前十分钟维护一个TCP连接池,用一个Socks5代理管理

    开始选课时将Http客户端的代理设置为维护的本地代理;本地代理随机选取一个Tcp连接,和客户端完成Socks5正常的握手过程(不需解析,只需应答,因为目标都是教务系统),然后开始双向转发数据即可

    因为HTTP/1.1默认是keep-alive connection,所以只需维护4-8个Tcp连接保证并发量即可


    Demo

    又到了一学期一度选课的日子,上午十点开放选课系统开始,花了三个小时,写好了抢课脚本,中间遇到了一点玄学…虽然程序写好了,但因为这些玄学,我陷入了思考人生中….

    由于几个月前我写好了教务系统模拟登录的代码,所以这次只需要导入上次的包,继承Loginer再用sessions成员发请求就好了。这里总结一下敲代码过程中出现的问题

    • 首先是父类的初始化,Python3只需要super()函数就可以了,而Python2中是需要传入子类的,要求argument>=1。当调用初始化函数__init__()时需super().__init__()来初始化父类中的成员变量,调用构造函数__new__()时需:super().__new__(cls),即使用父类来构建子类实例。 super()函数主要是用作菱形继承中的MRO父类初始化,它只能引用父类的初始成员变量,成员函数动态生成的属性它是无法访问的
    • 在这种I/O密集(网络I/O)的程序下,多线程是个好选择。因为Cpython的GIL是针对CPU运算密集的,当处于I/O等待时多线程会自动切换,且多进程程序进程切换花销高于线程,故选择多线程。其实最佳是事件循环的协程
    • 为了能在一个线程抢课成功后退出其他线程,引入了一个全局变量,在每个线程发起请求前会先判断这个全局变量的值,当一个线程抢课成功时会修改这个全局变量,以便达到同步退出。当我最初用多进程的时候,发现多进程不支持全局变量的共享,因为在每一个子进程中都会有一个全局变量的数据副本,如想共享内存,应使用multiprocessing.Value或在进程池中使用multiprocessing.manager(manager基于网络通信)。
    • 为了解决Cookie过期问题,在每个线程Target函数前判断请求后的页面长度,假如Cookie过期会返回302指向登录页面,requests默认会跟进302,故lenth有40000字节,如正常请求则返回的是几十字节的json。如Cookis过期,再次调用子对象的登录函数刷新成员Cookie变量
    • 再有就是玄学问题,两个headers,cookie,data都相同的post请求一个会返回400 Bad Request;当我用移动宽带或VPS访问的时候也会返回400。此处400为请求参数错误。玄学问题困扰了我很久。而且在我调试的时候,打印响应的状态码,连着手机热点时请求正常返回200,换到宽带瞬间变成400,CSDN上有人相同的情况,可能是ISP线路问题导致丢包,但浏览器请求完全正常。嗯这一定是玄学

    核心请求函数:

    def lessons(self, no):
            global THREAD_FLAG
            url = 'http://jwxt.cumt.edu.cn/jwglxt/xsxk/zzxkyzb_xkBcZyZzxkYzb.html?gnmkdm=N253512&su='+self.user
            print('[*]Thread-'+no+' Start')
            while True:
                if THREAD_FLAG:
                    try:
                        response = requests.post(url,data = self.rob_data,headers = self.header_2,timeout = 5)
                        if len(response.text) > 10000:
                            #self.reflush_time()
                            #self.get_public()
                            #self._get_csrftoken()
                            #self.post_data()
                            self.login_us()
                        print('[*]Thread-'+no+'  请求成功')
                        if response.json()['flag'] != '1':
                            print('[*]Thread-'+no+'  异常!')
                            print('[*]异常状态码: '+response.json()['msg'])
                            raise Exception
                        print('[*]Thread-'+no+'  Success!')
                        print('[*]'+self.kcmc+'  抢课成功!')
                        print('[*]程序即将退出...')
                        THREAD_FLAG = False
                    except KeyboardInterrupt:
                        sys.exit()
                    except:
                        print('[*]Thread-'+no+'  Fail')
                else:
                    print('[*]Thread-'+no+' Close')
                    return
    

    想要挂在VPS上抢课,但东京的IP访问不到教务系统,o(︶︿︶)o

    抢课程序的弊端,post到服务器的变量全是拼音拼写(不知道哪儿请的外包程序员),有很多暂时不清楚啥意思,实在猜不出来,故可能针对不同学院不同年级会出现普适性差的情况。再有就是这个程序需要课程代码,所以很难做到在系统开放前就启动程序的场景,大多数时候是挂着监控捡漏。


    CUMT公选课抢课脚本

    需要将模拟登录代码与抢课代码放于同一目录下,并修改config.json,具体使用说明见README


    2018/7/8 23:45

    洗个澡的功夫抢课成功了,终于修满公选课学分了