我为什么要虐自己

最近觉得课程格子广告越来越多,乱七八糟的东西越来越多,完全失去了一开始的存在价值,并且没有电脑端app,想查看课程必须拿出手机,而我使用电脑频率要比手机高,所以才有了折腾的动力。

于是我打开日历app,开始添加日程,添加一门课,填写课程名字,填写上课地点,选择日期,选择时间,选择重复方式,选择重复次数,加点备注……

看了一下课程数量,因为上课时间地点不同,每个课程还有可能要添加好几个日程,实在是有点多。

写脚本呗。

日历的格式

我随便导出了一个日程,导出的文件格式是ics,用文本编辑器打开,可以看到下面这样的数据结构。

BEGIN:VCALENDAR

VERSION:2.0

X-WR-CALNAME:课程表

X-APPLE-CALENDAR-COLOR:#F64F00FF

BEGIN:VTIMEZONE

TZID:Asia/Shanghai

X-LIC-LOCATION:Asia/Shanghai

BEGIN:STANDARD

DTSTART:19010101T000000

RDATE;VALUE=DATE-TIME:19010101T000000

TZNAME:CST

TZOFFSETFROM:+080543

TZOFFSETTO:+0800

END:STANDARD

END:VTIMEZONE

BEGIN:VEVENT

DTEND;TZID=Asia/Shanghai:20170420T093500

LAST-MODIFIED:20170110T165154Z

UID:088E2918-EEF6-4860-BFE6-5AA498939D98

DTSTAMP:20170111T050243Z

LOCATION:博B403

DESCRIPTION:测试

SEQUENCE:0

SUMMARY:软件质量保证与测试

DTSTART;TZID=Asia/Shanghai:20170420T080000

CREATED:20170110T165154Z

RRULE:FREQ=WEEKLY;COUNT=8

END:VEVENT

END:VCALENDAR

看着结构还是很清晰的,稍微分析一下可以知道整体包在VCALENDAR中,然后有个VTIMEZONE,然后下面就是日程VEVENT了。Event中又有几个字段,比如SUMMARY表示事件名称,DESCRIPTION表示备注,DTSTART表示事件开始时间,还有DTEND表示事件结束时间等等。于是想到应该可以用代码自动生成这些。

解析教务系统里的课程表

分析原数据

从官网查看本学期课表,检查元素,找到课程表的链接,打开源码,把源码保存到本地以便于分析。

稍微整理一下数据可以得到下面这样的结果。因为数据很多很乱,我用分割线将每一门课分割开来。

Raw

合并课程项

可以看出有的课程很长有的很短,分析发现是因为每门课可能每周有两个上课时间,第二个上课时间被分离到另一个数组了,所以要做一下组装。长度超过6的就把它上面的那条记录取出来,替换掉尾部,代码如下。

courses=[]

temp.each do |x|

if x[1].length > 6

courses << x

else

courses << (courses.last.slice(0..(-x.length-1)) + x)

end

end

打印结果如下,符合预期。

合并后的数据

提取有用的信息

courses.each do |c|

course = {

name: c[2].to_s,

credit: c[4].to_f,

prop: c[5].to_s,

exam: c[6].to_s,

teacher: c[7].to_s.delete(" ").split("*"),

week: c[11].to_s.delete("周上 ").split(","),

weekday: c[12].to_i,

order: real_order[c[13].to_i],

count: c[14].to_i,

place: c[16].to_s,

classroom: c[17].to_s

}

formatted << course

end

将每一个课程组成Hash对象,存到数组里。

其中week的内容是由"4-11,14-17周上"处理得到的,将它格式化成一个数组,每个元素形如"4-11",任课教师teacher的内容也差不多。

打印结果如下,这样看起来已经很不错了。

格式化后的数据

生成日历格式的数据

课程数据已经基本到手,要做成日历格式的数据,就单独写了个ICalendar类,根据参数来生成相应的日历元数据。网上可以找得到自动生成日历的gem,我看了一下,感觉要用它的API还得学一会文档,也用不了这么多功能,干脆就自己写了。

分析了一下日历的元数据,发现有很多字段是可以省略掉的,比如事件的创建时间,修改时间,这些没有必要存在,之后导入的时候会自动生成,我也不需要控制这个信息。还有UID,估计是用来表示事件的唯一,以免重复导入,也懒得做了,毕竟导入的时候会生成新的日历,不需要操心这个。

于是日历的模版就想好了。

BEGIN:VCALENDAR

VERSION:2.0

X-WR-CALNAME:课程表

X-APPLE-CALENDAR-COLOR:#{@color}

BEGIN:VTIMEZONE

TZID:#{@tzid}

X-LIC-LOCATION:#{@tzid}

END:VTIMEZONE

BEGIN:VEVENT

DTEND;TZID=#{@tzid}:#{e[:end_time]}

LOCATION:#{e[:location]}

DESCRIPTION:#{e[:description]}

SUMMARY:#{e[:summary]}

DTSTART;TZID=#{@tzid}:#{e[:start_time]}

RRULE:#{e[:rrule]}

END:VEVENT

END:VCALENDAR

所以只要用@event变量存放一个课程数组,到时候拼接一下就可以得到所有日程组成的字符串。

代码如下:

class ICalendar

def initialize(cal_name = '课程表' + Time.now.to_i.to_s)

@version = 2.0

@x_wr_calname = cal_name

@x_apple_calendar_color = '#F64F00FF'

@tzid = 'Asia/Shanghai'

@x_lic_location = 'Asia/Shanghai'

@events = []

end

def add_event(options={})

default_event = {

start_time: '20170420T080000',

end_time: '20170420T093500',

location: '地点',

description: '备注\n详细描述',

summary: "标题",

rrule: 'FREQ=WEEKLY;COUNT=8'

}

@events << default_event.merge(options)

end

def publish

gen_header + gen_events + gen_footer

end

private

def gen_header

header = <

BEGIN:VCALENDAR

VERSION:#{@version}

X-WR-CALNAME:#{@x_wr_calname}

X-APPLE-CALENDAR-COLOR:#{@x_apple_calendar_color}

BEGIN:VTIMEZONE

TZID:#{@tzid}

X-LIC-LOCATION:#{@tzid}

END:VTIMEZONE

BAR

end

def gen_events

events_cal = @events.map do |e|

event = <

BEGIN:VEVENT

DTEND;TZID=#{@tzid}:#{e[:end_time]}

LOCATION:#{e[:location]}

DESCRIPTION:#{e[:description]}

SUMMARY:#{e[:summary]}

DTSTART;TZID=#{@tzid}:#{e[:start_time]}

RRULE:#{e[:rrule]}

END:VEVENT

FOO

end

events_cal.join

end

def gen_footer

"END:VCALENDAR\n"

end

end

计算上课日期和时间

其实这个是我最懒的去思考的部分,整体思路很清晰,但这里很令人头疼,因为能自动获取的数据只有第几周开始上课,第几周结束,每天第几节课上课,上几节课。所以要求我自己提供一个开学日期,也就是要确定第一周是哪一周,以及每节课的上课下课时间,然后才能计算具体的课程时间。

计算开课日期

第一步先确定那门课程第一次课的日期。

设置默认开学日期20170220,用这个初始化一个Time实例,得到的具体时间是2017年2月20日0点整。存为变量start_day,然后调用wday方法获取weekday,也就是确定那天是周几,存为变量start_weekday。

根据开课的周次来确定周偏移量,因为开学那周是第一周,所以偏移量为class_start_week - 1。

根据开课的weekday,也就是周几开课,来确定“日偏移量”,即f[:weekday] - start_weekday表示。

所以开始上课的那天就是start_day加上周偏移量乘以7天乘以24小时乘以3600秒,再加上日偏移量乘以24小时乘以3600秒。

that_day = start_day + ((class_start_week - 1) * 7 + f[:weekday] - start_weekday) * 24 * 3600

计算课程开始和结束时间

接下来就是用that_day加上上课时间的“秒偏移量”就行了。这里其实很烦,因为每节课的时间是没有什么规律的,起初我认为是第一节课早上8点上课,然后每节课45分钟,每小节课中间休息5分钟,每大节课中间休息15分钟。在这个想法上抽象了一个算法来动态计算,后来发现一节大课有时候是2小节课有时候是3小节课,中午休息的时间和晚上休息的时间都不一样,修正模型太麻烦了,所以就手动录入了每一节课的上下课时间,用查表的形式获取时间。

START_TIME_OF_CLASS = [8 * 3600,

8 * 3600 + 50 * 60,

9 * 3600 + 50 * 60,

10 * 3600 + 40 * 60,

11 * 3600 + 30 * 60,

14 * 3600,

14 * 3600 + 50 * 60,

15 * 3600 + 50 * 60,

16 * 3600 + 40 * 60,

18 * 3600 + 30 * 60,

19 * 3600 + 20 * 60,

20 * 3600 + 10 * 60]

END_TIME_OF_CLASS = [8 * 3600 + 45 * 60,

9 * 3600 + 35 * 60,

10 * 3600 + 35 * 60,

11 * 3600 + 25 * 60,

12 * 3600 + 15 * 60,

14 * 3600 + 45 * 60,

15 * 3600 + 35 * 60,

16 * 3600 + 35 * 60,

17 * 3600 + 25 * 60,

19 * 3600 + 15 * 60,

20 * 3600 + 05 * 60,

20 * 3600 + 55 * 60]

于是课程的上课时间就是根据节次,即用节次减1作为下标从表里取出的时间,而下课时间则是由节次减1加上节数减1得到。

s_time = that_day + START_TIME_OF_CLASS[f[:order] - 1]

e_time = that_day + END_TIME_OF_CLASS[f[:order] + f[:count] - 2]

然后格式化成日历所需要的时间格式就行了

s_time.strftime("%Y%m%dT%H%M%S")

# => 20170222T095000

Done

剩下的就是把这些子模块拼接起来,整体的思路就是获取课程原数据,用nokogiri解析并处理得到可控的数据结构,然后遍历每节课程生成一坨events,添加到ICalendar实例的@events变量中。最后用publish方法拼接出完整的日历元数据,写入ics文件,再用邮件发送到指定订阅用户的邮箱中。

使用效果如下:

运行程序获取日历.png

从邮件的附件中下载ics文件,用日历app打开,即可导入日历中,最好是新建一个日历以免出现错误后不方便删除。

导入后结果如下,时间完美正确,还有一些有用的备注信息。

日历截图

完美,开心,告辞。

php 爬课程表信息,Ruby爬取教务系统生成课程表相关推荐

  1. python爬取景点信息_python 爬取马蜂窝景点翻页文字评论的实现

    使用Chrome.python3.7.requests库和VSCode进行爬取马蜂窝黄鹤楼的文字评论(http://www.mafengwo.cn/poi/5426285.html). 首先,我们复制 ...

  2. python爬房源信息_Python爬链家网租房信息

    爬去链家网的租房信息然后存储到数据库中. #-*- coding:utf-8 -*- import requests import re import random import MySQLdb fr ...

  3. Java爬取frame的课程表_从爬取湖北某高校hub教务系统课表浅谈Java信息抓取的实现 —— import java.*;...

    原创文章与源码,如果转载请注明来源. 一.概述 整个系统用Java开发.我们现在要做的是类似于超级课程表.课程格子之类的功能:输入一个学生的教务系统账号.密码,得到Ta的课程表信息.点击进入课表查询, ...

  4. Python爬取URP教务系统课程表并保存到excel

    Python爬取URP教务系统课程表并保存到excel 爬取URP教务系统课程表最终结果如图所示: 接下来开始操作: 首先打开教务系统->按F12->点击Network->刷新一下界 ...

  5. 我的第一个开源项目:Java爬虫爬取旧版正方教务系统课程表、成绩表

    Java爬虫爬取旧版正方教务系统课程表.成绩表 一.项目展示 1.正方教务系统 首页 2.爬虫系统 首页: 成绩查询: 课表查询: 二.项目实现 1.爬取思路描述 无论是成绩查询或课表查询亦或者其它的 ...

  6. python3爬取教务系统的个人学期课程表(无头谷歌浏览模拟登录)

    前言 今天带来的是与上次爬取教务系统获取成绩单的姐妹版--爬取教务个人的学期课程表. 工具 使用pycharm编辑器,安装selenium库,beautifulsoup库,csv库,当然需要下载对应的 ...

  7. 用selenium制作爬虫爬取教务课程信息

    https://apriljia.com/2018/09/26/%E7%94%A8selenium%E5%88%B6%E4%BD%9C%E7%88%AC%E8%99%AB%E7%88%AC%E5%8F ...

  8. android jsoup 课程表,使用jsoup爬取数据实现android课程表

    说明:只是爬虫的一个实现案例,所以没有多做功能,只做了登录跟课表功能,课表有修改周次,单击课程显示课程详细信息等功能. 开发平台:Android Studio 界面 使用TimetableView a ...

  9. python3爬虫模拟登录爬取教务系统成绩单(获取cookie操作)

    前言 今天来写写爬取教务系统的爬虫,此次的爬虫目的是爬取教务系统里面的成绩单,涉及到的库依旧是selenium,re,beautifulsoup,Options,今天多了个csv库用来处理爬取的数据, ...

最新文章

  1. MDK编译后生成bin文件占用FLASH大小说明
  2. You should rebuild using libgmp = 5 to avoid timing attack vulnerability
  3. JS控制HTML元素的显示和隐藏
  4. centos7标准版命令界面和图形界面相互切换
  5. python定时任务是异步的吗_定时任务、异步任务
  6. python rpc_对python调用RPC接口的实例详解
  7. 深入理解Yii2.0 (3)行为(Behavior)
  8. 3 ROC曲线和PR曲线和AUG
  9. css3边框交替动画_用纯CSS3制作的效果非常炫酷的元素边框线条动画特效
  10. javascript 学习
  11. DWC的1000M的MAC自环和PHY自环测试寄存器修改方式
  12. [Tensorflow]L2正则化和collection【tf.GraphKeys】
  13. java List的stream()方法解析
  14. 光流传感器 定位精度_基于多传感器的无人机定位和避障技术研究
  15. 计算机技术网络信息安全
  16. 一个非常简单的方法使用JavaScript打包一个网页成为安卓app(打包远景论坛)
  17. 红旗linux软件下载,红旗linux操作系统下载
  18. 阿里云视频直播推流和播流地址生成
  19. 思之以实,取之以略,行之以果
  20. postgre创建存储过程_postgre 存储过程

热门文章

  1. android tv的冷启动,YunOS for TV发布 10秒极速冷启动开机
  2. 关于Kaggle打开GPU加速需要手机验证的问题及解决方案
  3. Maya 收缩包裹用法
  4. Problem - 1042D - D. Petya and Array(树状数组)
  5. 想知道PDF转高清图片软件哪个好?
  6. C#+SQL SERVER 工厂耗材管控系统----------上篇
  7. e3mall商城的归纳总结3之后台商品节点、认识nginx
  8. 为何要劝小白不学python_新手小白如何学好Python?
  9. css标签不换行,CSS不换行与CSS换行
  10. SFTP客户端代码示例