爬取URP教务系统课表并生成ics日历文件
warning:
这篇文章距离上次修改已过1026天,其中的内容可能已经有所变动。
前言
最近用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
版权属于:NoColor
转载时须注明出处及本声明