使用python获取日历信息并制作订阅文件

使用python获取日历信息并制作订阅文件

前言

最近使用日历应用程序,但发现在日历上诸多节日没有显示,先不说假日调休一事,这个是国内一大特色,它也不好预料,可是比较常见的节日都没有自带显示,写这篇文章时恰巧临近母亲节,打开日历一看,找不到,百度搜索看看。

非常紧跟时事,在推荐下拉框就有关于节日不显示的问题,在之前我也不知道如何导入订阅日历,先点一个订阅地址的链接进去,发现有教程教如何设置日历,我也顺利导入成功了,但是只有节假日放假与调休的,不满足我想要的,其他链接下均没有找到相关教程。

准备工作

思考良久,解决灵感就想到了既然别人能设置成订阅日历,为啥我自己不做一个呢。开始回找那个链接地址处于何处,没想到制作者把他开源在github上,在国内搭建了服务器,这样国内用户使用ics(苹果日历订阅文件)链接就不会提示无法验证的消息导致不能完成日历订阅。

方案来源:如下

可以看到起作用的是这个ics文件,即弄明白这个文件格式就可以复写出一模一样的ics文件出来。文件结构如下:

先声明日历名称及日历对应的时区,之后的事件构建基本都是重复同一个模板填充,不同点为开始始日,结束日,及其名称描述。

扫描二维码关注公众号,回复: 14615810 查看本文章

弄明白这个也就很好往下进行了,日历制作,单靠自己翻找各个节日也不方便,既然已经想程序化那就多设置几个节日,经过一段时间的网上冲浪,发现日历网站收录的节日信息较多,且能满足当前需求,点击日历网,再点击节日大全。

节日信息抓取

下面编写程序抓取日历信息,并将其转换为ics文件。

首先试探下网页能不能比较友好的获取,获取还算是简单,只需要正常传入请求头即可。

再分析网页结构,由于是静态网页,直接抓取element信息就行,其关键信息在li节点下,这里使用xpath进行解析,//li[@class="jr1"]//text(),即可完成节点文本数据获取。

结果形如:元旦[1月1日],其中在ics文件中日期应当为年月日的结构,再引入datetime模块对日期进行解析后转换为年月日结构:20220101。

抓取网页信息代码如下:

from datetime import datetime
import re
import httpx  # httpx与requests的api相似,可以仅更改httpx为requests,代码运行无误
from faker import Faker  # 设置伪造请求头user-agent
from lxml import etree

def get_url():
    headers = {
    
    
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
        'Accept-Encoding': 'gzip, deflate, br',
        'Accept-Language': 'zh-CN,zh;q=0.9',
        'Connection': 'keep-alive',
        'User-Agent': Faker().chrome(version_from=98, version_to=100, build_from=4800, build_to=5000),
        'Host': '日历网站',
        'Referer': '日历网站',
    }

    url = '日历网站'
    try:
        response = httpx.get(url, headers=headers)
        if response.status_code == 200:
            return response.text
        else:
            return None
    except Exception as e:
        print(e)
        return None


def parse_jr_date(text, y):
    jr, dt = text.split('[')
    dt = f'{
      
      y}年' + re.search(r'\d{1,2}月\d{1,2}日', dt).group()
    dt = datetime.strptime(dt, '%Y年%m月%d日').strftime('%Y%m%d')
    return jr, dt


def parse_html(html):
    html = etree.HTML(html)
    date = html.xpath('//meta[@name="LastUpdate"]/@content')[0]
    y = datetime.strptime(date, '%Y/%m/%d %H:%M:%S').year
    jr_rili = ''.join(html.xpath('//li[@class="jr1"]//text()')).split(']')[:-1]
    return y, jr_rili

日历订阅文件设置

根据前文分析ics文件的设置框架,可以简单将ics文件拆分成ics头部设置和ics事件设置:

from datetime import datetime

now = datetime.now().strftime('%Y%m%dT%H:%M:%S')


def set_ics_header(year):  # year为当前年份
    return "BEGIN:VCALENDAR\n" \
           + "PRODID:NULL\n" \
           + "VERSION:2.0\n" \
           + "CALSCALE:GREGORIAN\n" \
           + "METHOD:PUBLISH\n" \
           + f"X-WR-CALNAME:{
      
      year}年节假日\n" \
           + "X-WR-TIMEZONE:Asia/Shanghai\n" \
           + f"X-WR-CALDESC:{
      
      year}年节假日\n" \
           + "BEGIN:VTIMEZONE\n" \
           + "TZID:Asia/Shanghai\n" \
           + "X-LIC-LOCATION:Asia/Shanghai\n" \
           + "BEGIN:STANDARD\n" \
           + "TZOFFSETFROM:+0800\n" \
           + "TZOFFSETTO:+0800\n" \
           + "TZNAME:CST\n" \
           + "DTSTART:19700101T000000\n" \
           + "END:STANDARD\n" \
           + "END:VTIMEZONE\n"


def set_jr_ics(jr, date, uid):  # jr: 节日,date:日期,uid:编序
    return "BEGIN:VEVENT\n" \
           + f"DTSTART;VALUE=DATE:{
      
      date}\n" \
           + f"DTEND;VALUE=DATE:{
      
      date}\n" \
           + f"DTSTAMP:{
      
      date}T000001\n" \
           + f"UID:{
      
      date}T{
      
      uid:0>6}_jr\n" \
           + f"CREATED:{
      
      date}T000001\n" \
           + f"DESCRIPTION:{
      
      jr}\n" \
           + f"LAST-MODIFIED:{
      
      now}\n" \
           + "SEQUENCE:0\n" \
           + "STATUS:CONFIRMED\n" \
           + f"SUMMARY:{
      
      jr}\n" \
           + "TRANSP:TRANSPARENT\n" \
           + "END:VEVENT\n"

将获取到的日期信息应用到上面代码中:

def concat_ics(y, jr):  # 返回一个完整的ics文件内容
    header = set_ics_header(y)
    jr_rq = list(map(parse_jr_date, jr, [y] * len(jr)))
    # 将同一天的节日进行编号
    jr_rq.sort(key=lambda x: x[1])
    pre_num = 0
    for num in range(len(jr_rq)):
        if jr_rq[num][1] != jr_rq[pre_num][1]:
            pre_num = num
        jr_rq[num] += (num - pre_num + 1,)
    jr_ics = ''.join(map(lambda x: set_jr_ics(*x), jr_rq))
    return header + jr_ics + 'END:VCALENDAR'

保存为ics文件:

def save_ics(fname, text):
    with open(fname, 'w', encoding='utf-8') as f:
        f.write(text)  
# fname: calendar_{year}_jr.ics

以上就是从日历网获取日历信息到制作ics文件的全过程,代码已开源:

github地址:【https: //github.com/lk-itween/Calendar 】

订阅日历信息

上述操作弄完后并不能在手机上看到设置好的日历信息,这时把制作的ics文件放到可以可访问的网上,再通过苹果的日历订阅就可以使用了,或许可以将文件保存到手机里面,通过文件路径的方式访问ics文件,接下来只讨论将其放到网上。

不用搭建服务器,使用代码托管平台,如github,但是这个国内访问较为吃力,那么可以试试gitee,同样也是代码托管平台,或许还有其他的,只要能通过公域网访问,且能保存ics文件的网站即可。

在github或者gitee里面新建一个仓库。

  • github:

  • gitee(如果是gitee托管代码仓库,需要在管理页面将仓库设置为公开):

设置好仓库名,将ics文件上传即可,以gitee为例,上传完后等待页面刷新,会出现你上传的几个文件,如calendar_2022_jr.ics

此时点击需要订阅的日历文件,如calendar_2022_jr.ics,再点击原始数据:

复制网址,或者复制上面页面对应的网址,将blob修改为raw

接下来,打开手机,进入日历,点击日历界面的下方日历

再点击添加日历 -> 添加订阅日历

在输入框输入刚才复制的网址,比如我的为:(https://gitee.com/kangliz/Calendar/raw/main/calendar_2022_jr.ics

再点击订阅,如果没有检测出网页异常,就会显示以下页面:

可以修改标记颜色,然后点击添加即日历订阅成功。

总结

本篇简单的介绍了如何从网站获取日历信息并将其制作成ics文件,供ios系统的日历应用程序订阅日历,可根据此篇内容动手制作一个日历订阅文件,并将其上传至可使用网站上。

日有所思夜有所梦,前方的道路无论是否顺畅都应当坚持往前走。


于二零二二年五月七日作

猜你喜欢

转载自blog.csdn.net/weixin_46281427/article/details/124641599
今日推荐