爬取URP教务系统课表并生成ics日历文件
前言最近用Python写了一个工具，可以把课程表导出到iCalendar格式，之后可以把这个文件导入到系统的软件当中，比如iPhone自带的日历, Outlook, Google Calendar 等等你常用的日历工具，使得提醒更加明朗。先上图，这是将课表导入Google日历的样子，省去繁琐的教务处课表查询/专属app广告，甚至可以在上课前30分钟提醒（再也不用担心迟到了）。适用范围本人测试环境为河北工业大学，理论上新版URP教务系统均可使用。使用方法先上源码，最后面有下载链接。import requests import re import datetime import configparser import hashlib import json import os def md5(str): m = hashlib.md5() m.update(str.encode("utf8")) return m.hexdigest() config = configparser.ConfigParser() config.read('config.ini') # 教务系统地址 url = config['URP'].get('url') username = input('id:') password = input('password:') session = requests.session() im_url = url + 'img/captcha.jpg' im_data = session.get(im_url) # 验证码处理及模拟登陆 with open('captcha.jpg', 'wb') as file: file.write(im_data.content) captcha = input("Captcha(saved in the code directory):") login_url = url + 'j_spring_security_check' post_data = { 'j_username': username, 'j_password': md5(password), 'j_captcha': captcha } # 课表数据获取 login_res = session.post(login_url, data=post_data) table_url = url + 'student/courseSelect/thisSemesterCurriculum/ajaxStudentSchedule/curr/callback' tablePage = session.get(table_url).text table_byte = bytes(tablePage, 'utf-8') # 数据预处理 with open('class.json', 'wb') as file: file.write(table_byte) file.close() with open('class.json', "a+", encoding='utf-8') as f: old = f.read() f.seek(0) f.write(']') f.close() with open('class.json', "r+", encoding='utf-8') as f: old = f.read() f.seek(0) f.write('[') f.write(old) # 写入日历 # 第一周周一日期 startYear = config['startDate'].getint('year') startMonth = config['startDate'].getint('month') startDay = config['startDate'].getint('day') beginDate = datetime.date(startYear, startMonth, startDay) startTime = [None] startTime.extend(config['time'].get('startTime').replace(' ', '').split(',')) endTime = [None] endTime.extend(config['time'].get('endTime').replace(' ', '').split(',')) weekName = [None] weekName.extend(config['time'].get('weekName').replace(' ', '').split(',')) VCALENDAR = '''BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:%(username)s 课程表 X-WR-TIMEZONE:Asia/Shanghai X-WR-CALDESC:%(username)s 课程表 BEGIN:VTIMEZONE TZID:Asia/Shanghai X-LIC-LOCATION:Asia/Shanghai BEGIN:STANDARD TZOFFSETFROM:+0800 TZOFFSETTO:+0800 TZNAME:CST DTSTART:19700101T000000 END:STANDARD END:VTIMEZONE ''' % {'username': username} file = open('课程表.ics', 'w', encoding='utf-8') file.write(VCALENDAR) with open("class.json", 'r', encoding='utf-8') as f: temp = json.loads(f.read()) for index in range(len(temp[0]['dateList'][0]['selectCourseList'])): # 剔除未安排教室的课程 if temp[0]['dateList'][0]['selectCourseList'][index]['timeAndPlaceList'] is None: continue for index1 in range(len(temp[0]['dateList'][0]['selectCourseList'][index]['timeAndPlaceList'])): className = temp[0]['dateList'][0]['selectCourseList'][index]['courseName'] classBuilding = temp[0]['dateList'][0]['selectCourseList'][index]['timeAndPlaceList'][index1][ 'teachingBuildingName'] classRoom = temp[0]['dateList'][0]['selectCourseList'][index]['timeAndPlaceList'][index1]['classroomName'] classSession = temp[0]['dateList'][0]['selectCourseList'][index]['timeAndPlaceList'][index1][ 'classSessions'] classAmount = temp[0]['dateList'][0]['selectCourseList'][index]['timeAndPlaceList'][index1][ 'continuingSession'] classWeek = temp[0]['dateList'][0]['selectCourseList'][index]['timeAndPlaceList'][index1]['classDay'] classWeekTimes = temp[0]['dateList'][0]['selectCourseList'][index]['timeAndPlaceList'][index1][ 'weekDescription'].split(',') for index3 in range(len(classWeekTimes)): VEVENT = '' VEVENT += 'BEGIN:VEVENT\n' # 周次 WeekTimes = re.findall(r"\d+\.?\d*", classWeekTimes[index3]) # 开始周 delta = datetime.timedelta(weeks=int(WeekTimes[0]) - 1) # 开始星期 delta += datetime.timedelta(days=int(classWeek) - 1) classStartTime = beginDate + delta # 开始日期 classStartDate = beginDate + delta # 开始时间 classStartTime = datetime.datetime.strptime( startTime[int(classSession)], '%H:%M').time() # 结束时间 classEndTime = datetime.datetime.strptime( endTime[int(classSession) + int(classAmount) - 1], '%H:%M').time() # 最终开始时间 classStartDateTime = datetime.datetime.combine( classStartDate, classStartTime) # 最终结束时间 classEndDateTime = datetime.datetime.combine( classStartDate, classEndTime) # 写入开始时间 VEVENT += 'DTSTART;TZID=Asia/Shanghai:{classStartDateTime}\n'.format( classStartDateTime=classStartDateTime.strftime( '%Y%m%dT%H%M%S')) # 写入结束时间 VEVENT += 'DTEND;TZID=Asia/Shanghai:{classEndDateTime}\n'.format( classEndDateTime=classEndDateTime.strftime( '%Y%m%dT%H%M%S')) # 设置循环 if '-' in classWeekTimes[index3]: VEVENT += 'RRULE:FREQ=WEEKLY;WKST=MO;COUNT={count};BYDAY={byday}\n'.format(count=str( int(WeekTimes[1]) - int(WeekTimes[0]) + 1), byday=weekName[int(classWeek)]) else: interval = int(WeekTimes[0]) VEVENT += 'RRULE:FREQ=WEEKLY;WKST=MO;COUNT={count};INTERVAL={interval};BYDAY={byday}\n'.format( count=1, interval=str(interval), byday=weekName[int(classWeek)]) # 地点 VEVENT += ('LOCATION:' + classBuilding + classRoom + '\n') # 名称 VEVENT += ('SUMMARY:' + className + '\n') VEVENT += 'END:VEVENT\n' file.write(VEVENT) file.write('END:VCALENDAR') file.close() f.close() print('Finished, check code directory!') os.system('pause')之后在同一目录下新建一个config.ini，内容如下[startDate] year = month = day = [URP] url = [time] startTime = 08:20, 09:10, 10:25, 11:15, 14:00, 14:50, 16:05, 16:55, 18:40, 19:30, 20:20 endTime = 09:05, 09:55, 11:10, 12:00, 14:45, 15:35, 16:50, 17:40, 19:25, 20:15, 21:05 weekName = MO, TU, WE, TH, FR, SA, SU[startDate] 对应学期开始的时间[URP] 里面填写教务系统地址[time] 根据上课时间自行调整程序执行过程中会要求输入学号密码和验证码（保存在程序目录中）示例可以参考我的GitHub