爬取URP教务系统课表并生成ics日历文件

warning: 这篇文章距离上次修改已过814天,其中的内容可能已经有所变动。

前言

最近用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

转载时须注明出处及本声明

添加新评论