├── .gitignore ├── README.md └── login.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.jpg 2 | *.html 3 | *.txt 4 | *.cfg 5 | *.cookie 6 | *.vscode 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 南京大学教务网抢课系统 2 | 3 | #### 工具依赖 4 | 脚本中使用了一些python库,主要有 5 | - requests 6 | - beautifulsoup 7 | 8 | 可以使用pip进行安装 9 | ``` 10 | pip install requests 11 | pip install bs4 12 | ``` 13 | 如果发现有其他依赖缺失,按提示自行安装即可 14 | 15 | #### 使用方法 16 | 默认检查是否存在用户信息配置文件,如果不存在,则需要手动输入 17 | 之后需要输入验证码进行验证,验证码会自动弹出,同时默认保存在脚本所在目录中,输入后图片自动删除 18 | 登陆成功后,输入对应年级,可以获取该年级的专业课程列表 19 | 之后输入对应课程的序号(非课程号),即可开始抢课 20 | 21 | #### 支持Cookie登录 22 | 首次登陆后一段时间内可免验证登录,一段时间不进行操作后登录会失效,需要重新登陆 23 | 24 | #### 配置文件 25 | 使用该软件登陆系统可以使用配置文件存储用户信息避免重复输入 26 | 在根目录下创建user.cfg,格式如下 27 | ``` 28 | userName:181220000 29 | password:abcDEF 30 | ``` 31 | 32 | #### 注意事项 33 | 脚本中GrabCourse函数接受一个参数控制抢课时间间隔,为避免对教务网造成过大压力,建议间隔在5分钟以上,频率过高有概率导致封号,后果自负 34 | -------------------------------------------------------------------------------- /login.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from urllib import request,error 3 | from http import cookiejar 4 | import time 5 | from PIL import Image 6 | from io import BytesIO 7 | import os 8 | import re 9 | from bs4 import BeautifulSoup 10 | 11 | host = "http://elite.nju.edu.cn/jiaowu/" 12 | user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" 13 | def login(session): 14 | #获取cookie,如果本地有cookie,尝试使用cookie登录 15 | c = GetCookie(session) 16 | session.cookies.update(c) 17 | if c: 18 | rs = requests.models.Response() 19 | try: 20 | rs = session.get("http://elite.nju.edu.cn/jiaowu/student/index.do") 21 | except requests.exceptions.ConnectionError: 22 | print("连接失败,请检查您的网络连接") 23 | exit() 24 | if rs.content.__len__() > 5000: 25 | print("登陆成功!") 26 | return True 27 | else: 28 | print("登录已过期,请重新登录") 29 | 30 | #构造登陆请求体 31 | login_data={} 32 | files = os.listdir() 33 | if "user.cfg" in files: 34 | with open("user.cfg",'r') as f: 35 | for line in f: 36 | items = line.split(":") 37 | items[1]=items[1].replace('\n','').replace('\r','') 38 | login_data[items[0]]=items[1] 39 | else: 40 | print("请输入用户名") 41 | login_data['userName']=input() 42 | print("请输入密码") 43 | login_data['password']=input() 44 | login_data['retrunURL']="null" 45 | 46 | # 取得验证码图片 47 | now_time = str(int(time.time())) 48 | pic_url = host + 'ValidateCode.jsp' 49 | pic = session.get(pic_url).content 50 | im = Image.open(BytesIO(pic)) 51 | im.show() 52 | filename = '' + now_time + '.jpg' 53 | with open(filename, 'wb') as f: 54 | f.write(pic) 55 | print("请输入验证码(Please enter the ValidateCode)") 56 | vcode=input() 57 | os.remove(filename) #输入完验证码后自动删除本地图片 58 | login_data['ValidateCode']=vcode 59 | 60 | #尝试使用OCR自动识别验证码,但是由于验证码干扰较多,不能正确识别,因此采用手动输入方式 61 | # img = Image.open(filename) 62 | # img=img.convert('L') 63 | # vcode = pytesseract.image_to_string(img) # 使用ocr技术将图片中的验证码读取出来 64 | # time.sleep(0.3) 65 | # print(vcode) 66 | 67 | #发送登录请求 68 | response = session.post(host+"login.do",login_data) 69 | if response.content.__len__() > 6000: 70 | print("登陆成功!") 71 | SaveCookie(session) #保存此次登录的cookie 72 | return True 73 | else: 74 | print("登录失败,请检查账号密码及验证码") 75 | return False 76 | 77 | def SaveCookie(session): 78 | with open(".cookie",'w') as f: 79 | for key,val in session.cookies.get_dict().items(): 80 | f.write(key+":"+val+'\n') 81 | 82 | def GetCookie(session): 83 | cookie = {} 84 | if ".cookie" not in os.listdir(): 85 | return cookie 86 | with open(".cookie",'r') as f: 87 | for line in f: 88 | line = line.replace('\n','').replace('\r','') 89 | item = line.split(':') 90 | cookie[item[0]] = item[1] 91 | return cookie 92 | 93 | #拉取专业课课表 94 | def GetSpecialCourseList(session): 95 | #获取专业选课页面,用以获得院系编号 96 | selectPage = session.post(host+"student/elective/courseList.do",{'method':'specialityCourseList'}) 97 | selectPageSoup = BeautifulSoup(selectPage.content,"html.parser",from_encoding='utf-8') 98 | majorID = selectPageSoup.find('select',{'id':'specialityList'}).find('option')['value'] 99 | 100 | #返回的课程列表,存储课程编号对应的courseID 101 | courseIdList=[] 102 | #构造拉取课程信息的请求体 103 | courseList_reqdata={} 104 | courseList_reqdata['method']="specialityCourseList" 105 | courseList_reqdata['specialityCode']=majorID #专业代号,计科为221 106 | print("请输入对应年级(如2019)") 107 | grade = input() 108 | courseList_reqdata['courseGrade']=grade 109 | courseList = requests.models.Response() 110 | while True: 111 | try: 112 | courseList = session.post(host+"student/elective/courseList.do",courseList_reqdata) 113 | except requests.exceptions.ConnectionError: 114 | print("连接超时,正在尝试重新连接") 115 | time.sleep(1) 116 | else: 117 | break 118 | soup = BeautifulSoup(courseList.content,"html.parser",from_encoding='utf-8') 119 | trs = soup.find_all('tr',{'class':'TABLE_TR_01'}) 120 | print("序号\t课程号\t\t课程名\t\t\t学分\t学时\t类型\t开课院系") 121 | for tr in trs: 122 | tds = tr.find_all('td') 123 | courseNo = tds[0].find('a').find('u').string 124 | if(tds[1].string.__len__()<=7): 125 | print(str(trs.index(tr)+1)+'\t'+courseNo+'\t'+tds[1].string+'\t\t'+tds[2].string+'\t'+tds[3].string+'\t'+tds[4].string+'\t'+tds[6].string) 126 | else: 127 | print(str(trs.index(tr)+1)+'\t'+courseNo+'\t'+tds[1].string+'\t'+tds[2].string+'\t'+tds[3].string+'\t'+tds[4].string+'\t'+tds[6].string) 128 | click_td = tr.find('td',{'onclick':True}) 129 | if click_td==None: 130 | courseIdList.append("") 131 | pass 132 | else: 133 | js = click_td['onclick'] 134 | args = js.split(',') 135 | courseID = args[4][0:5] 136 | courseIdList.append(courseID) 137 | return courseIdList 138 | 139 | #拉取通识课课表 140 | def GetDiscussRenewCourseList(session): 141 | selectPage = session.get(host+"student/elective/courseList.do?method=discussRenewCourseList&campus=%E4%BB%99%E6%9E%97%E6%A0%A1%E5%8C%BA") 142 | soup = BeautifulSoup(selectPage.content,"html.parser",from_encoding='utf-8') 143 | 144 | #返回的课程列表,存储课程编号对应的courseID 145 | courseIdList = [] 146 | trs = soup.find_all('tr',re.compile('TABLE_TR_0[12]')) 147 | print("序号\t课程号\t\t课程名\t\t\t\t\t\t学分\t限额\t已选") 148 | for tr in trs: 149 | tds = tr.find_all('td') 150 | courseNo = tds[0].find('a').find('u').string 151 | if(tds[2].string.__len__()<=7): 152 | print(str(trs.index(tr)+1)+'\t'+courseNo+'\t'+tds[2].string+'\t\t\t\t\t'+tds[3].string+'\t'+tds[6].string+'\t'+tds[7].string) 153 | elif(tds[2].string.__len__()<=11): 154 | print(str(trs.index(tr)+1)+'\t'+courseNo+'\t'+tds[2].string+'\t\t\t\t'+tds[3].string+'\t'+tds[6].string+'\t'+tds[7].string) 155 | elif(tds[2].string.__len__()<=15): 156 | print(str(trs.index(tr)+1)+'\t'+courseNo+'\t'+tds[2].string+'\t\t\t'+tds[3].string+'\t'+tds[6].string+'\t'+tds[7].string) 157 | else: 158 | print(str(trs.index(tr)+1)+'\t'+courseNo+'\t'+tds[2].string+'\t\t'+tds[3].string+'\t'+tds[6].string+'\t'+tds[7].string) 159 | arg1 = tds[0].find('a')['href'].split('(') 160 | arg2 = arg1[1].split(',') 161 | courseIdList.append(arg2[0]) 162 | return courseIdList 163 | 164 | 165 | #接受两个参数,第一个为课程ID,第二个为每次抢课时间间隔 166 | def GrabSpecialCourse(courseID,interval=0): 167 | connectionFailedFlag = False 168 | while(True): 169 | selectCourse_reqdata={} 170 | selectCourse_reqdata['method']="addSpecialitySelect" 171 | selectCourse_reqdata['classId']=str(courseID) 172 | selectResult = requests.models.Response() 173 | while True: 174 | try: 175 | selectResult = s.post(host+'student/elective/selectCourse.do',selectCourse_reqdata) 176 | except requests.exceptions.ConnectionError: 177 | connectionFailedFlag=True 178 | print(GetTime()+"- 连接超时,正在尝试重新连接") 179 | time.sleep(1) 180 | else: 181 | if connectionFailedFlag: 182 | connectionFailedFlag=False 183 | print(GetTime()+"- 重连成功,继续为您抢课") 184 | break 185 | soup = BeautifulSoup(selectResult.content,"html.parser",from_encoding='utf-8') 186 | for tag in soup.find_all('div'): 187 | if tag.get('id')=="successMsg": 188 | print(GetTime()+"- 抢课成功!") 189 | return 190 | elif tag.get('id')=="errMsg": 191 | if tag.string.find("已经")!=-1: 192 | print(GetTime()+"- 您已经抢到该课程啦~") 193 | exit() 194 | elif tag.string.find("错误")!=-1: 195 | print(GetTime()+"- 出现错误,添加失败") 196 | exit() 197 | else: 198 | print(GetTime()+"- 当前班级已满,仍在为您持续抢课") 199 | else: 200 | pass 201 | if interval!=0: 202 | time.sleep(interval) 203 | 204 | #接受两个参数,第一个为课程ID,第二个为每次抢课时间间隔 205 | def GrabDiscussRenewCourse(courseID,interval=0): 206 | connectionFailedFlag = False 207 | while(True): 208 | selectResult = requests.models.Response() 209 | while True: 210 | try: 211 | selectResult = s.get(host+'student/elective/courseList.do?method=submitDiscussRenew&classId='+str(courseID)+'&campus=%E4%BB%99%E6%9E%97%E6%A0%A1%E5%8C%BA') 212 | except requests.exceptions.ConnectionError: 213 | connectionFailedFlag=True 214 | print(GetTime()+"- 连接超时,正在尝试重新连接") 215 | time.sleep(1) 216 | else: 217 | if connectionFailedFlag: 218 | connectionFailedFlag=False 219 | print(GetTime()+"- 重连成功,继续为您抢课") 220 | break 221 | soup = BeautifulSoup(selectResult.content,"html.parser",from_encoding='utf-8') 222 | script = soup.find_all('script')[1] 223 | # print(str(script)) 224 | if re.search("班级已满",str(script)) is not None: 225 | print(GetTime()+"- 当前班级已满,仍在为您持续抢课") 226 | elif re.search("-你已经选过这个班级了",str(script)) is not None: 227 | print(GetTime()+"- 添加失败,你已经选过这个班级了") 228 | exit() 229 | elif re.search("操作成功",str(script)) is not None: 230 | print(GetTime()+"- 抢课成功!课程存在时间冲突,已自动生成免修不免考申请") 231 | exit() 232 | elif re.search("课程选择成功",str(script)) is not None: 233 | print(GetTime()+"- 抢课成功!") 234 | exit() 235 | if interval!=0: 236 | time.sleep(interval) 237 | 238 | def GetTime(): 239 | return time.asctime(time.localtime(time.time()))[11:20] 240 | 241 | if __name__ == '__main__': 242 | s = requests.session() 243 | if not login(s): 244 | exit() 245 | print("请选择课程类型:") 246 | print("1.通识课 2.公选课 3.专业课") 247 | courseType = input() 248 | courseIdList = [] 249 | if int(courseType) == 1: 250 | courseIdList = GetDiscussRenewCourseList(s) 251 | elif int(courseType) == 2: 252 | courseIdList = GetSpecialCourseList(s) 253 | elif int(courseType) == 3: 254 | courseIdList = GetSpecialCourseList(s) 255 | else: 256 | print("课程类型输入有误") 257 | exit() 258 | 259 | print("请输入需要抢课的课程序号(非课程号)") 260 | courseNo = input() 261 | if int(courseNo)<=0 or int(courseNo)>courseIdList.__len__(): 262 | print("输入序号有误,请重新输入") 263 | else: 264 | courseID = courseIdList[int(courseNo)-1] 265 | if courseID=="": 266 | print("你已经选过该门课啦~换个课程吧") 267 | else: 268 | if int(courseType) == 1: 269 | GrabDiscussRenewCourse(courseID,3) 270 | if int(courseType) == 2: 271 | GrabSpecialCourse(courseID,300) 272 | if int(courseType) == 3: 273 | GrabSpecialCourse(courseID,300) --------------------------------------------------------------------------------