多设备同步订阅课程表,适用于iOS与Android
关键词:iCloud日历同步,RFC 2445,iCalendar,Outlook,GMail
摘要 🔑
知识准备
- RFC 2445标准:1998由微软公司发布的标准的互联网日历标准,让用户能够在各种计算机和各种程序之间创建和共享电子日历。
- iCloud同步:在多台支持iCloud的Apple设备中,只需在任意一个设备中进行某项设置或工作内容,其他设备均可同步得到此项设置或工作内容。
- ics文件:后缀为ics的文件,是RFC 2445标准下的日历文件,使用此文件可以直接向日历中添加日程时间表。
- 订阅:在不打开某项链接的前提下,自动获取此链接的最新内容。
前提 🚩
本文均以iOS为例说明,目前,所有流行日历工具比如:Lotus Notes、Outlook、GMail 和 Apple 的 iCal 都支持RFC 2445标准,故Android也适用。
在过去的两年,博主在每学期初一直坚持将自己的课程表手动输入到自己的iCloud日历中,通过iCloud提供的多设备同步,可以做到MacBook、iPad、iPhone、Apple Watch都有课程表信息,利用iOS提供的小组件以及Apple Watch的先天便捷性,可以非常方便地了解到自己当天及本周甚至本学期的所有课程名称,时间,上课地点及任课教师。
但是上述做法有如下已知缺点:
- 课程不会自动更新:每逢节假日调休,需手动调整课程表
- 课表错误率高:手动输入极大可能会导致人工错误,检查过程繁琐
- 每个学期均需手动录入一次课程信息:过程繁琐
于是,在上学期期末复习时突发奇想,有了如下想法。
想法💡
使用爬虫获取课程表信息,生成ics文件,产生此ics文件订阅,使用MacBook订阅日历地址。爬虫定期爬取课程表,更新ics文件。
可行性验证 ⚖️
- 爬虫可用性:此项因学校而异,需要自己去尝试。本人尝试成功,不再赘述。
- ics文件生成:博主用MacBook导出了自己已有的日历日程后用文本编辑打开了此ics文件,结合RFC 2445标准与网上现有博客得知,可行。
- 生成ics订阅:博主在网络上查了一下,未查到ics订阅生成方法。于是自己随便找了一个已有的订阅地址,发现地址路径末尾为ics文件名,猜测是ics文件下载地址,将此地址放入浏览器中,回车直接下载到了此ics日历文件,得知可行。
- 爬虫定期爬取:学校教务处不更新,就一直能行。
如果读者想要实现如上步骤,另需满足如下前置要求:
- 一台拥有固定IP的服务器
- Python的基本阅读调试技能
想法验证可行,开始实现想法。
想法实现 🔨
爬虫编写
注:此过程因学校而异,本人就不放出此爬虫的Python代码,只给出用于后续步骤的数据样本使步骤连贯。
ics文件生成
经过观察学习,ics文件的格式模版如下所示:
# -*- coding: UTF-8 -*-
BEGIN:VCALENDAR
# 下面是一个日程
BEGIN:VEVENT
CREATED:20191203T142408Z
UID:20200831T10250020200831T12000024231812
DTEND;TZID=Asia/Shanghai:20200831T120000 #日程结束时间
TRANSP:OPAQUE
X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC
SUMMARY:标题
LAST-MODIFIED:20191227T060829Z
DTSTAMP:20191203T142417Z
DTSTART;TZID=Asia/Shanghai:20200831T102500 #日程开始时间
LOCATION:地点
DESCRIPTION:备注
SEQUENCE:0
END:VEVENT
# 上面是一个日程
# 顺序排列多个日程
END:VCALENDAR
上述中文内容已经把一个日程在存在形式描述清楚,未描述的可以先不理会,不影响最终的使用效果。
编写Python代码:
import random
# 创建日历文件
def createCalender(filename, data):
file = open(filename + ".ics", "a")
file.write("# -*- coding: UTF-8 -*-
BEGIN:VCALENDAR
")
file.close()
for w in data:
data1 = w['classes']
for d in data1['classes']:
for c in d.values():
for course in c:
createCourse(course, filename + ".ics", data1['weekdays'])
file = open(filename + ".ics", "a")
file.write("END:VCALENDAR")
file.close()
# 为每个课程创建对应的日历信息
def createCourse(course, file_name, weekdays):
courseDay = weekdays[course['weekday'] - 1].replace('-', '') + 'T'
courseTime = course['course_time'].split('~')
startTime = courseDay + courseTime[0].replace(':', '') + '00'
endTime = courseDay + courseTime[1].replace(':', '') + '00'
uid = startTime + endTime + str(course['credit']) + str(random.randint(0, 9999999))
coursename = course['course_name']
location = course['location'] + ' ' + course['teacher']
description = '课程号:' + course['course_id'] + '\n' + '学分:' + str(course['credit'])
file = open(file_name, "a")
file.write("BEGIN:VEVENT
CREATED:20191203T142408Z
")
file.write('UID:{}
'.format(uid))
file.write("DTEND;TZID=Asia/Shanghai:{}
".format(endTime))
file.write("TRANSP:OPAQUE
X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC
")
file.write("SUMMARY:{}
".format(coursename))
file.write("LAST-MODIFIED:20191227T060829Z
DTSTAMP:20191203T142417Z
")
file.write("DTSTART;TZID=Asia/Shanghai:{}
".format(startTime))
file.write("LOCATION:{}
".format(location))
file.write("DESCRIPTION:{}
".format(description))
file.write("SEQUENCE:0
END:VEVENT
")
file.close()
if __name__ == '__main__':
createCalender("course.ics", data) #data使用上面的数据样本,使用eval()转换为dict
运行此代码,可以得到course.ics文件。
生成ics日历订阅
由上文分析,生成ics日历订阅也就等价于生成ics文件的下载链接,博主使用的是Nginx,所以方法如下。其他http服务器如tomcat自行百度相关方法。
- 找到nginx的
index.html
所在目录,在此目录下的一切文件及递归文件均可直接下载。
假设nginx的
index.html
路径为/home/nginx/html/index.html
在日常访问域名
https://xxx.com
时,浏览器下载得到index.html
并打开,就是我们看到的网页。所以如果访问
https://xxx.com/ics/xxx.ics
,浏览器就会下载/home/nginx/html/ics/xxx.ics
得到xxx.ics
,如果没有找到,那就是404 Not Found。
- 将ics文件放置在nginx的
index.html
所在目录下或其子文件夹下即可。 - 订阅地址就自然是
https://xxx.com/ics/xxx.ics
了。
爬虫定期爬取数据
在CentOS下,编辑/etc/crontab增加相应定时任务即可。
最终效果示例 📽
分析与结论 📝
- 解决了上述提出的缺点
- 若教务处网站不进行防爬虫更新,理论上可一直使用
- 简化了生活流程,使其更加条理化