这几天配置 RSS 花了我很多的精力,大作业都没心思做了,感觉可以说的地方还蛮多的,这么好的机会,还不拿来水一篇?

先直接看下最终的成果图:

还是很爽的

什么是 RSS

RSS 是一个基于 XML 的格式规范,它可以将来自各平台的信息用统一的格式表示,借助专用阅读器就可以达到一站式获取信息的效果

为什么要使用 RSS

不知道大家有没有过这样的经历:虽然关注了一些博主,但是动态中不会显示他们的消息,让你一度以为他似了,直到有一天点进他的主页,才发现他一直在活跃。现在我们可以直接通过 RSS 订阅这个博主的动态,这样就不会被平台的逆天机制恶心到了

除此之外,以往获取信息需要前往不同的平台,跳转到各种页面,这本身就是一个很繁琐的流程,而且很容易出现遗漏。RSS 通过将信息聚合起来,可以优雅地解决这个问题

那还说啥了,开搞!

选一款 RSS 阅读器

经过一番了解,我得知阅读器大体包括本地阅读器和在线阅读器。在线阅读器的优势在于可以实时更新,如果订阅源更新内容比较多,本地阅读器可能会出现遗漏信息的情况,不过基本上只要每天都上线刷新一下也没什么大问题

网上能找到的在线阅读器基本上都需要付费才能享受全部功能,我最终选择的是本地阅读器,所以不会过多介绍。除此之外还有自己部署阅读器的做法,不过花钱用 VPS 部署一个阅读器感觉多少有些没必要,如果只是本地部署的话,那就跟本地阅读器没什么区别了,所以把他排除掉了,如果是富哥当我没说

Feedly

推荐的唯一一个在线阅读器,界面非常的美观,是我阅读器里面用下来体验最舒适的一个。它还支持自动生成 RSS,如果一些网站没有提供 RSS 订阅,这个功能可以帮你爬取并处理信息,还提供了一个可视化的界面让你手动调整,十分高效,当然这个功能是付费计划专属的。缺点就是免费计划功能阉割,且只支持 100 个订阅源和 3 个文件夹,要想用的舒服还是得付费,如果你可以承受一个月 50 上下的花销,可以选择这一个阅读器

Irreader

本地阅读器,功能齐全,同样支持自动生成 RSS,最大亮点就是配备了十分完善的推送系统。可以选择弹窗提醒,也支持通过邮件、钉钉、微信、Telegram 等方式推送,不过除了弹窗以外的几乎所有功能都需要 VIP,价格不低,免费用户只能订阅 10 个 RSS,加上这款软件的颜值实在不能说好看,最后也是果断放弃了

Folo

与 RSSHub(开源的 RSS 源生成和分享平台,后面会讲)出自同一制作组,虽然没有 Irreader 那样强大的推送服务,但是他拥有用户间互相分享的数量庞大的 RSS 源,界面也比较漂亮,通过分享自己的 RSS 源还有机会拿到赏金,属于是非常全面的一款产品

至于为什么没有选择它,主要是因为它太占内存了,一开始安装好大概有 700MB,用了没一会就接近一个 G 了,虽然它提供了很多的功能,但是跟阅读体验没有太大关系,于是我也忍痛把它卸载,选择了接下来这款更加轻量化的阅读器

Fluent Reader

我现在正在用的一款,也是在文章开始展示的阅读器,个人认为是本地阅读器 UI 巅峰,阅读体验没的说。然而缺点也比较突出,就是功能比较简陋。上面提到的消息推送、分享订阅源、自动生成 RSS 一概没有,甚至没有最小化托盘和开机自启。属于是阅读器丁真,除了颜值啥也没有。好在从爬取订阅源到阅读的这一整套流程做的还是很完善的,一句话来说就是,够用!

正如前文所言,它占用内存相对较小,大概 200MB 多一点,比其他阅读器小很多,也是我选择这款阅读器的重要原因

说起来这些阅读器怎么都是 F 字辈的

订阅 RSS 源

部署 RSSHub

选好心仪的阅读器后,接下来就要去寻找 RSS 源了。一般来讲博客站点都会提供 RSS 订阅的服务,但是 b 站,油管这样的平台并不会直接提供,需要自己制作 RSS。这时就要用到上文提到的 RSSHub 了,开发者会将制作好的 RSS 生成代码放在这里,借助服务器路由就能够订阅各大平台的 RSS。如果没有找到想订阅的网站,也可以通过封装好的函数自己制作

官方提供了一个实例供我们使用,我们可以直接用它来订阅 RSS,然而它并不能完全满足我们的要求。因为官方的服务器位于美国,加上访问人数多,速度会很慢,有时甚至不响应。还有就是无法自主配置环境变量,有些需要 token 的路由无法使用。为了更好的满足自己的需求,我决定自己部署一个 RSSHub

官方文档中给了很多平台的部署方法,我最后选择了 Zeabur,原因说来也简单,它每个月会给五美元的免费额度,是除 Vercel 外给的最多的。文档作者也很贴心,提供了一键部署的按钮,我只需要配置一下域名就好了,整个过程十分愉快

经过测试,如果正常使用的话,每天的费用会在 0.1 美元左右浮动,足够挺过一个月了

部署过程中也是遇到了很多或大或小的问题,都拿来说一下

油管推送内容重复

我在用 RSSHub 订阅油管频道的时候,发现相同的内容,每次刷新后返回的 RSS 发布时间不一样,导致一个信息会在阅读器中多次出现。我百思不得其解,于是去 Github 上提了 issue,也是我的 first issue。没一会就收到了回复,原来是人工智能 Dosu 回答了我的问题。它说我没在环境变量中配置 token,而第三方的爬取工具只能返回“xxx 小时前”格式的内容,就会出现我这样的情况

我一听感觉很有道理,按照它说的配置,果然不重复了,然后 close 了这个 issue。虽然我在文档中看到了这个配置项,但因为标的是 optional,想着多一事不如少一事就没有搞。果然以后还是不能偷懒啊

悲催的是我在接下来又提了一个 issue,然后贡献了自己的第一个 pr,感慨万千,如果这俩顺序反过来该多好

Taptap 部分游戏订阅失败

既然要整理信息,那肯定少不了游戏资讯,有的 b 站官号动态除了更新情报外可能还会推送其他内容,不是我想看的东西,我还需要一个更加干净纯粹的情报站。很快我就把眼光放在了 Taptap 上面,如果有官方入驻的话,这里的信息还是比较及时的

然而我在订阅 Phigros 的官方动态时,发生了这样的报错(找不到图了先将就一下吧):

TypeError: Cannot read properties of undefined (reading 'id_str')

通过格式来看,这个报错很明显是 JS 返回的。也就是说,是代码发生了问题。我们先来看看这部分处理函数的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
async function handler(ctx) {
const lang = ctx.req.param('lang') ?? 'zh_CN';
const appId = ctx.req.param('id');
const appData = await appDetail(appId, lang);
const type = ctx.req.param('type') ?? 'feed';
const sort = ctx.req.param('sort') ?? 'created';
const groupId = appData.group.id;
const appImg = appData.app.icon.original_url || appData.app.icon.url;
const appName = appData.app.title;
const url = `${getRootUrl(false)}/webapiv2/feed/v7/by-group?group_id=${groupId}&type=${type}&sort=${sort}&${X_UA(lang)}`;
const topicsList = await ofetch(url);
const list = topicsList.data.list;
const out = await Promise.all(
list.map(({ moment }) => {
const link = moment.sharing.url;
return cache.tryGet(link, async () => {
const author = moment.author.user.name;
const topicId = moment.topic.id_str;
// raw_text sometimes is "" so || is better than ??
const title = moment.topic.title || moment.topic.summary.split(' ')[0];
let description = moment.topic.summary || '';
if (moment.topic.pin_video) {
description += videoPost(moment.topic.pin_video);
if (moment.topic.footer_images?.images) {
description += imagePost(moment.topic.footer_images.images);
}
} else {
description = await topicPost(appId, topicId, lang);
}
return {
title,
description,
author,
link,
pubDate: parseDate(moment.created_time, 'X'),
};
});
})
);

const ret = {
title: `${appName} - ${typeMap[type][lang]} - TapTap 论坛`,
link: `${getRootUrl(false)}/app/${appId}/topic?type=${type}&sort=${sort}`,
image: appImg,
item: out.filter((item) => item !== ''),
};

ctx.set('json', {
...ret,
appId,
groupId,
topicsList,
});
return ret;
}

大体的逻辑是,获取路由中传入的参数,然后发往 Taptap 的 API 接口,获取到帖子列表后,对帖子进行逐个处理,最终返回 RSS。访问 id_str 的地方只有一处,就是 topicId 的声明部分,它的作用是通过请求这个 id 进一步获取帖子的具体内容,包括贴文和图片

因为我的 Milthm 订阅也是用的这个路由,没有任何问题,所以我推断可能发生了某个特殊情况,导致 json 结构不一致,引发了报错。为此我手搓 url 拿到了响应信息,然后拿显微镜一行一行观察发现果然有一个帖子不符合规范。这个帖子因为转发了其他的帖子,id_str 用的是被转发帖子的,而且被转移到了其他的地方

既然发现了问题,改起来就容易了。我先是去提了一个 issue,探讨接下来的解决方案,得到的答复是将正文和被转发文章的内容全部展示出来。然后我从凌晨一点开始写代码,到了四点完成编写并通过测试,提交 PR,很快啊就被合并了,不亏是歪果仁大半夜的回复效率就是高。通过的一瞬间倦意一扫而空,躺在床上兴奋的睡不着,参与开源的乐趣大概就在于此吧

关于 json 具体结构以及修改后的代码,感兴趣的自己去看吧,篇幅原因不展示了

修改后的代码其实还是有缺陷的。因为缺乏实例,没有涵盖到转发视频的情况,届时可能不会显示视频,甚至还是会报错,那时就需要进一步修改了

b 站订阅视频的风控问题

其实订阅 b 站视频的步骤非常简单,只要在环境变量中添加 token,然后在阅读器中订阅相应的路由就可以了。然而问题就出在更新 token 这个步骤上,b 站的 token 更新频率很频繁,更新之后原来的 token 就失效了,无法用来获取信息,如果一两天手动更换一次 token 的话会很麻烦。于是我在网上找到了这个 issue,有人提到用无痕模式拿到 token 之后能保持较长时间的有效期,我在别的地方还看到借助 CookieCloud 编写脚本实时更新的,但是嘛...哎呀太麻烦懒得搞了,就订阅了一个视频源不想大动干戈,所以最后还是选择手动更新

虽然有一点小小的缺憾,综合来看使用体验已经非常令我满意了

尾声

把这些能想到的东西都安顿好后,我也算是从火星搬回地球了,终于不会再错过游戏更新的信息。找资料的过程也额外发现了不少优质的博客,顺手都添加了订阅。不得不感慨这些技术真的大大方便了人们的生活,希望以后能多多学习吧