使用Python开发选课脚本

参考链接

编号 分类 文章及链接 介绍 作者 来源分类 撰写日期 收录日期
L1 参考 python自动预约公众号 hzp666 CSDN博客 2022-03-07 2023-09-11
L2 参考 微信小程序抢票脚本 躺平de咸鱼 CSDN博客 2022-07-06 2023-09-13

前言: 孩子所在的小学每到开学季就会有周末课外非文化课的辅导班的抢课大战。某校通也是个小开发商,他们做的微信公众号上面的选课缴费程序因为有2-3千家长同时抢课,每次都会搞得网站接近瘫痪20分钟以上。运气好能抢到心仪的课,运气不好的时候一个课也选不到。而且,这个开发商还没有锁位机制,抢到课了,交钱慢了也不行。没有预留交钱的时间。几次想来搞一个抢课程序,但是都是觉得有点麻烦或者事情多给放弃了。趁着这次开学季,研究了一下,实验算是成功了。

1. 工具准备

2. 使用抓包工具fiddler获取微信公众号课程信息

为了能够从脚本发出选课请求,需要获知如下内容:

  1. 选课接口URL

  2. Header参数,包括识别用户正常登录的cookies

  3. post数据格式

  4. post数据内容

    用电脑版微信登录某校通的微信公众号,注意提前在微信上绑定学生信息。
    进入选课界面,用fiddler跟踪公众号的信息。
    在这里插入图片描述
    通过访问选课界面,并通过其中的js脚本,可以定位到接口以及相应的header数据(最好保持完全一样)以及cookies数据。 post数据格式这一块,没有跑正式流程之前以为是json格式,后来才知道是web表单格式。至于数据内容,有四项GUID数据构成。分别是学生ID列表、课程组ID、课程ID、学校ID,除了课程ID外,其它几项的值从头到尾都是不变的,课程ID根据需要报名的课程去获取设置。
    正式抢课之前,由于接口没有开放,只是推测。所以正确的学生id没有拿到,格式也有错误。
    还好,抢课当天还有些课可以选,最后测试通过,算是正确完成。下个开学季可以随便选课了。

在这里插入图片描述

3. 编写python程序

使用第三方的requests组件来发送表单数据。
记得使用前安装 pip install requests

核心代码及控制逻辑已经实现。同时选多个学生或者多个课程建议多线程进行,需自行实现。

import sys
import requests
import time
import json
from pathlib import Path
from marklog import info_logout, init_log


class SelectCourse:
    over_flag = False
    # 学校id,不变
    course_school_id = 'COURSE_SCHOOL_ID'
    # 项目id,不变
    project_id = 'PROJECT_ID'
    # 学生码 ["GUID"]
    student_code = 'STUDENT_CODE'
    base_url = 'http://xxx.xxx'

    def select_course(self, course_id):
        referer_url = f'{self.base_url}/CourseSelection/CourseShow?CourseSchoolId={self.course_school_id}&' \
                    f'ProjectId={self.project_id}&id={course_id}'
        header = {
            "Accept": "application/json, text/javascript, */*; q=0.01",
            "Accept-Encoding": "gzip, deflate",
            "Accept-Language": "zh-CN,zh",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
              "Chrome/107.0.0.0 Safari/537.36 NetType/WIFI MicroMessenger/7.0.20.1781(0x6700143B)"
              " WindowsWechat(0x63090621) XWEB/8391 Flue",
            "Referer": referer_url,  # 课程明细url
            # 采用表单模式发送请求内容
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
            "Connection": "keep-alive",
            "X-Requested-With": "XMLHttpRequest",
            "Host": "xxx.xxx",
            "Origin": "xxx.xxx",
            # Cookie信息放在Header里面
            "Cookie": "ASP.NET_SessionId=xxxxxxxxxxx; NetCXT=xxxxxxxxxxxx"
        }

        post_data = {"ProjectAndCourseLibraryId": course_id,
                     "StudentInfoLibraryIdList": f'["{self.student_code}"]',
                     "CourseSchoolId": self.course_school_id,
                     "ProjectId": self.project_id}

        # 执行选课操作
        post_url = f'{self.base_url}/CourseSelection/yyyyy'
        # requests库可以自动处理编码、
        r = requests.post(post_url, data=post_data, headers=header, verify=False)

        if r.status_code == 200:
            res = json.loads(r.text)
            if res["ErrorCode"] == 0:
                info_logout(f'选课:{res["ErrorMsg"]},课程id: {course_id}')
                self.set_over_flag(True)
            else:
                info_logout(f'选课结果:{res["ErrorMsg"]},课程id: {course_id}')
            return True
        else:
            info_logout(r.text, True)
            info_logout(f'选课异常,错误代码{r.status_code}')
            return False

if __name__ == '__main__':
    log_filename = Path(sys.argv[0]).stem + '.log'
    init_log(log_filename)
    info_logout(f'版本:0.1 2023/09/13 可以使用。注意实际使用前设置确认各个课程id、cookies等各个参数。', True)
    select = SelectCourse()
    course_ids = {'羽毛球A': 'zzz1',
                  '口风琴演奏A': 'zzz2',
                  '篮球D2': 'zzz3',
                  '语言表演C': 'zzz4'}

    interval = 100
    while not select.is_over():
        course_name = '羽毛球A'
        if select.select_course(course_ids[course_name]):
            info_logout(f'【{course_name}】 选课成功, 请马上进行支付', True)
            # 因为电脑微信不支持支付功能,只能在手机模拟器上来操作。不做自动支付。
        if not select.is_over():
            time.sleep(interval/1000)
            info_logout(f'休息{interval}ms')
    info_logout('选课结束', True)

4. 实操结果截屏:

在这里插入图片描述