从教务网站上获取课表并导入iCloud日历


前言

因为我家羽羽说她的课表一天一个样,每次都手动输入到日历中的话非常不方便,所以我打算帮她把课表从教务网站上爬出来,然后写在ics文件中,这样就可以一键导入了。

方案

打开他们学校的教务网站,我就麻了,因为必须要输入验证码。这对自动化脚本来说是一个非常阴间的事情,所以考虑使用selenium,打开网页后,人工登录,程序检测到网页跳转后开始运行。

登录完成后就可以看到课表,默认是月课表,所以只需要让脚本自己翻页然后读取网页全部内容即可。通过F12,我们可以看到那些写着有课程信息的元素大致长什么样子,使用正则表达式即可爬取所有课程了。

使用

在经历了漫长了写代码过程之后,终于到了运行的时候啦!看到一个程序能跑起来,真的是很开心的一件事!(大家也可以尝试复制下面的代码,自己运行试试看)

如果读者想要运行以下代码,需要进行下列步骤:

  1. 安装一个Python(这不是废话吗,建议是3.6以上,我自己用的3.10)
  2. 安装icalendarselenium(在命令行执行pip install xxx
  3. 如果你使用Chrome浏览器,请下载和你的Chrome匹配的chromedriver(见参考链接6),并为chromedriver添加环境变量,或放在脚本所在目录下。
  4. 如果你不使用Chrome浏览器,请自行搜索selenium如何使用你的浏览器(FireFoxEdge Safari等主流浏览器都是支持的,如果你使用其他浏览器,也可以尝试,因为它们大概率和Google Chrome使用同样的内核,只不过版本较老)另外,不要忘记更改chrome = Chrome()这条语句。
  5. 在脚本所在目录下打开命令行,输入python xxx即可运行(xxx是你的脚本名,通常你应该让后缀是.py)(通常安装python时会关联文件,所以双击运行也不是不可以)
  6. 日程会储存在cdutcm.ics中,许多日历App都支持.ics格式

配置更改

有如下内容可能需要更改:

  1. 如果你不是使用的Chrome,更改chrome = Chrome()(第68行)
  2. 默认只会获取课程,而不会获取考试,若要获取考试请更改正则表达式中的上课任务(第32行)
  3. 默认是秋季学期课表,若要获取春季学期课表,更改while month != '八月'while month != '二月',交换八月金和二月的位置大概就可以了(第76和83行)
  4. 更改begin_date = datetime(2022, 8, 29, tzinfo=UTC8)中的2022, 8, 29为本学期开学第一周星期一的时间(第97行)

代码

# coding=utf-8

import re
from time import sleep
from typing import Any
import icalendar as ics
from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By
from datetime import datetime, timezone, timedelta

UTC8 = timezone(timedelta(hours=8))

def create_event(name: Any, location: Any, dtstart: Any, dtend: Any, description: Any) -> ics.Event:
    '''
    create a single icalendar event
    
    :type of params: any Python native type or icalendar property type.
    '''

    event = ics.Event()
    event.add('summary', name)
    event.add('location', location)
    event.add('dtstart', dtstart)
    event.add('dtend', dtend)
    event.add('description', description)
    return event

def get_lesson_from_html(begin_date: datetime, html: str, begin_week: int = -1) -> tuple[list[dict[str,str|datetime]],int]:
    REGEX = (
        r'<a class="fc-day-grid-event fc-h-event fc-event fc-start fc-end" lay-tips="'
        r'<table class=&quot;kb-tips&quot; border=&quot;1&quot;>'
        r'<tr><th>事件类型:</th><td colspan=&quot;3&quot;>上课任务</td></tr><tr><th>'
        r'上课时间:</th><td colspan=&quot;3&quot;>(.*?)--(.*?)</td></tr><tr><th>'
        r'教学模式:</th><td colspan=&quot;3&quot;>.*?</td></tr><tr><th>'
        r'教学形式:</th><td colspan=&quot;3&quot;>.*?</td></tr><tr><th>'
        r'星期:</th><td>(\d*?)</td><th>节次:</th><td>\d*?</td></tr><tr><th>上课周次:</th><td >(\d*?)</td><th>'
        r'课序号:</th><td>.*?</td></tr><tr><th>'
        r'课程:</th><td  colspan=&quot;3&quot;>\[.*?\]\[.*?\](.*?)\[.*?\]</td></tr><tr><th>'
        r'授课教师:</th><td colspan=&quot;3&quot;>(.*?)</td></tr><tr><th>'
        r'教学场地:</th><td colspan=&quot;3&quot;>(.*?)\(?\)?</td></tr><tr><th>'
        r'上课班级:</th><td colspan=&quot;3&quot;>.*?</td></tr><tr><th>'
        r'排课/上课:</th><td colspan=&quot;3&quot;>.*?</td></tr><tr><th>'
        r'授课内容:</th><td colspan=&quot;3&quot;>((.|\n)*?)</td></tr></table>" style="background-color: rgb\(\d+, \d+, \d+\);">'
    )
    
    end_week = 0
    lessons : list[dict[str,str|datetime]] = []
    for match in re.findall(REGEX, html):
        day = int(match[2]) - 1
        week = int(match[3]) - 1
        if week <= begin_week: continue
        end_week = max(end_week, week)
        today = begin_date + timedelta(days=7 * week + day)
        start_t = [int(x) for x in match[0].split(':')]
        end_t = [int(x) for x in match[1].split(':')]
        dtstart = today + timedelta(hours=start_t[0], minutes=start_t[1], seconds=start_t[2])
        dtend = today + timedelta(hours=end_t[0], minutes=end_t[1], seconds=end_t[2])
        name = str(match[4])
        teacher = str(match[5])
        location = str(match[6])
        content = str(match[7])
        lessons.append({'课程':name, '教学场地':location, '开始时间':dtstart, '结束时间':dtend, '教师':teacher, '授课内容':content})
        
    return (lessons, end_week)

def get_lesson(begin_date: datetime) -> list[dict[str,str|datetime]]:
    # 第一步获取网页元素
    chrome = Chrome()
    chrome.get('http://jwweb.cdutcm.edu.cn')
    while chrome.current_url != 'https://jwweb.cdutcm.edu.cn/new/welcome.page':
        sleep(1) # 这里需要手动输入账号密码验证码!
    sleep(3) # 等待加载
    iframe = chrome.find_element(By.XPATH, '/html/body/div[3]/div/div/div[2]/div/div/iframe')
    chrome.switch_to.frame(iframe)
    month = chrome.find_element(By.XPATH, '/html/body/div[1]/div/div[1]/div/div/div/div[1]/div[3]/h2').get_attribute('textContent').split(' ')[1]
    while month != '八月':
        chrome.find_element(By.XPATH, '/html/body/div[1]/div/div[1]/div/div/div/div[1]/div[1]/div/button[1]').click()
        sleep(3) # 等待加载
        month = chrome.find_element(By.XPATH, '/html/body/div[1]/div/div[1]/div/div/div/div[1]/div[3]/h2').get_attribute('textContent').split(' ')[1]
    
    begin_week = -1
    lessons : list[dict[str,str|datetime]] = []
    while month != '二月':
        html = chrome.page_source
        lessons_gets, begin_week = get_lesson_from_html(begin_date, html, begin_week)
        lessons += lessons_gets
        chrome.find_element(By.XPATH, '/html/body/div[1]/div/div[1]/div/div/div/div[1]/div[1]/div/button[2]').click()
        sleep(3) # 等待加载
        month = chrome.find_element(By.XPATH, '/html/body/div[1]/div/div[1]/div/div/div/div[1]/div[3]/h2').get_attribute('textContent').split(' ')[1]
    
    return lessons

if __name__ == '__main__':
    calendar = ics.Calendar()
    calendar.add('version', '2.0')
    
    begin_date = datetime(2022, 8, 29, tzinfo=UTC8)
    lessons = get_lesson(begin_date)
    
    for lesson in lessons:
        name = lesson['课程']
        location = lesson['教学场地']
        dtstart = lesson['开始时间']
        dtend = lesson['结束时间']
        description = '教师:' + str(lesson['教师']) + '\n\n授课内容:\n' + str(lesson['授课内容'])
        event = create_event(name, location, dtstart, dtend, description)
        calendar.add_component(event)

    with open('cdutcm.ics', 'wb') as f:
        f.write(calendar.to_ical())

参考链接

  1. 「Selenium」- 在页面中,点击按钮(或元素)
  2. 使用Python在Selenium WebDriver中获取WebElement的HTML源代码
  3. ICS在线课表制作
  4. ICS在线课表制作 源码
  5. 如果意外地从 iCloud 中删除了日历、书签或通讯录
  6. chromedriver下载与安装方法,亲测可用

评论
  目录