前言
最近使用日历应用程序,但发现在日历上诸多节日没有显示,先不说假日调休一事,这个是国内一大特色,它也不好预料,可是比较常见的节日都没有自带显示,写这篇文章时恰巧临近母亲节,打开日历一看,找不到,百度搜索看看。
非常紧跟时事,在推荐下拉框就有关于节日不显示的问题,在之前我也不知道如何导入订阅日历,先点一个订阅地址的链接进去,发现有教程教如何设置日历,我也顺利导入成功了,但是只有节假日放假与调休的,不满足我想要的,其他链接下均没有找到相关教程。
准备工作
思考良久,解决灵感就想到了既然别人能设置成订阅日历,为啥我自己不做一个呢。开始回找那个链接地址处于何处,没想到制作者把他开源在github上,在国内搭建了服务器,这样国内用户使用ics(苹果日历订阅文件)链接就不会提示无法验证的消息导致不能完成日历订阅。
方案来源:如下
可以看到起作用的是这个ics文件,即弄明白这个文件格式就可以复写出一模一样的ics文件出来。文件结构如下:
先声明日历名称及日历对应的时区,之后的事件构建基本都是重复同一个模板填充,不同点为开始始日,结束日,及其名称描述。
弄明白这个也就很好往下进行了,日历制作,单靠自己翻找各个节日也不方便,既然已经想程序化那就多设置几个节日,经过一段时间的网上冲浪,发现日历网站收录的节日信息较多,且能满足当前需求,点击日历网,再点击节日大全。
节日信息抓取
下面编写程序抓取日历信息,并将其转换为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系统的日历应用程序订阅日历,可根据此篇内容动手制作一个日历订阅文件,并将其上传至可使用网站上。
日有所思夜有所梦,前方的道路无论是否顺畅都应当坚持往前走。
于二零二二年五月七日作