快速开发一个 RSSHub 路由
注:由于 RSSHub 已经对整个项目进行了重构,本篇文档所介绍的开发流程不再适用于最新版本的 RSSHub。重构后的 RSSHub 完全使用 TS 进行开发,步骤更加清晰简洁,且官方提供的开发文档很详细,具体可参考官方开发文档。如果你还使用着未重构的老版本 RSSHub,则可以考虑参考本文。—— 2024.4.22 更新
前言
最近写了几个 RSSHub 的路由,由于官方的开发步骤较为杂乱,这里提供一个简易的开发步骤,最后还记录了手动配置部署的过程。
下面以开发 今日热榜 的榜单路由为例进行介绍。
下载安装 RSSHub
1git clone git@github.com:DIYgod/RSSHub.git && cd RSSHub
2pnpm i # npm install -g pnpm
3pnpm run dev # localhost:1200
本文中基本都是用 pnpm 进行操作(pnpm 是新一代包管理器,它解决了 npm 幻影依赖和分身问题并较好地解决了依赖包复用问题,实现了依赖包高效快速的安装),其他操作方式详见官方文档。打开浏览器访问 localhost:1200
即可看到 RSSHub index 页面。
接着在 lib/v2
下开始开发路由,一个文件夹对应一个域,这里创建文件夹 “tophub” 即可。
定义路由规则
首先要分析网站结构,今日热榜一个榜单对应一个唯一的 id,那么可以将这个 id 作为匹配对象:
1// router.js
2module.exports = (router) => {
3 router.get('/list/:id', require('./list'));
4};
这里生成了一个路由规则,用户可通过 /tophub/list/xxxxx
这样的路由来生成 rss,通过同一目录下的 list.js
完成具体抓取和生成逻辑。
定义抓取和生成逻辑
首先引用下依赖包如下:
1// list.js
2const got = require('@/utils/got');
3const cheerio = require('cheerio');
4const config = require('@/config').value;
5const path = require('path');
6const { art } = require('@/utils/render');
got
:用于发起 http 请求cheerio
:一个为服务器 jQuery 实现,可方便地解析页面config
:用于读取配置文件,经过分析发现爬取需使用 cookie,所以需要用户手动配置 cookiepath
:路径生成工具art
:一个模板引擎(art-template),由于生成榜单需要定制 html 界面,所以需要使用它(仓库维护者的要求)
下面是具体逻辑:
1// list.js
2module.exports = async (ctx) => {
3 const id = ctx.params.id;
4 const link = `https://tophub.today/n/${id}`;
5 const response = await got.get(link, {
6 headers: {
7 Referer: 'https://tophub.today',
8 Cookie: config.tophub.cookie,
9 },
10 });
11 const $ = cheerio.load(response.data);
12 // 解析榜单标题
13 const title = $('div.Xc-ec-L.b-L').text().trim();
14 // 解析榜单元素
15 const items = $('div.Zd-p-Sc > div:nth-child(1) tr')
16 .toArray()
17 .map((e) => ({
18 title: $(e).find('td.al a').text().trim(),
19 link: $(e).find('td.al a').attr('href'),
20 heatRate: $(e).find('td:nth-child(3)').text().trim(),
21 }));
22 // 将所有条目标题合一用于生成 GUID
23 const combinedTitles = items.map((item) => item.title).join('');
24 // 渲染榜单
25 const renderRank = art(path.join(__dirname, 'templates/rank.art'), { items });
26
27 ctx.state.data = {
28 title,
29 link,
30 item: [
31 {
32 title,
33 link,
34 description: renderRank,
35 guid: combinedTitles,
36 },
37 ],
38 };
39};
这里通过 jquery 语法读取元素: $('div.Xc-ec-L.b-L')
,具体可以参考 cheerio 官方文档。
由于当榜单中任一个条目顺序或内容发生变化都可认为是更新了,同时 guid
可以唯一标识一个 rss 条目,所以将所有条目标题合一用于生成 guid
即可,下面是关于 guid 的介绍:
guid: A string that uniquely identifies the item.
引自:rss 标准
最后,ctx.state.data
生成了一个 rss,title
用于标识 rss 标题,link
为原网址(tophub.today),item
为一个数组,保存了最新的条目,具体可参考 rss 标准。
编写渲染模版
编辑 template/rank.art
:
1<table>
2 <thead>
3 <tr>
4 <th>排名</th>
5 <th>标题</th>
6 <th>热度</th>
7 </tr>
8 </thead>
9 <tbody>
10 {{each items}}
11 <tr>
12 <td>{{ $index + 1 }}</td>
13 <td>
14 <a href="{{ $value.link }}">
15 {{ $value.title }}
16 </a>
17 </td>
18 <td>{{ $value.heatRate }}</td>
19 </tr>
20 {{/each}}
21 </tbody>
22</table>
这里用 each
操作遍历传入的 items
,$index
为下标,$value
为对应值,具体语法可以参考官方文档。
另外注意传入 items
的方式:
1art(path.join(__dirname, 'templates/rank.art'), { items });
传入的是一个 object,需要用 {} 扩住 items
。
定义路由维护者
1// maintainer.js
2module.exports = {
3 '/list/:id': ['akynazh'],
4};
/list/:id
这个路由对应 github.com/akynazh
这个开发者。
注意同一路由可有多个维护者,feat 和 fix 提交都可以加入该路由的维护者名单。
编写路由文档
在 website/docs/routes
下编写路由对应的文档,方便用户使用,首先确定路由类型,这里可以认为是新媒体,所以编辑 new-media.mdx
即可:
1## 今日热榜 {#jin-ri-re-bang}
2
3:::warning
4由于需要登录后的 Cookie 值才能获取原始链接,所以需要自建,需要在环境变量中配置 `TOPHUB_COOKIE`,详情见部署页面的配置模块。
5:::
6
7### 榜单列表 {#jin-ri-re-bang-bang-dan-lie-biao}
8
9<Route author="akynazh" example="/tophub/list/Om4ejxvxEN" path="/tophub/list/:id" paramsDesc={['榜单id,可在 URL 中找到']} />
{#jin-ri-re-bang}
定义了中文名对应的拼写名,用于快速定位。
:::warning:::
定义了一个重要注意事项模块,其他模块可参考官方文档。
Route
标签定义了一条路由。
支持 Radar 插件
1module.exports = {
2 'tophub.today': {
3 _name: '今日热榜',
4 '.': [
5 {
6 title: '榜单列表',
7 docs: 'https://docs.rsshub.app/routes/new-media#jin-ri-re-bang-bang-dan-lie-biao',
8 source: ['/n/:id'],
9 target: '/tophub/list/:id',
10 },
11 ],
12 },
13};
docs
为文档地址。source
为原路由,target
为目标路由,通过 :id
映射了 id
值,另外 target 还支持更复杂的操作(比如正则),具体可参考官方文档。
配置环境变量
前面我们通过 config.tophub.cookie
读取 cookie,对应为全大写+下划线分隔生成的 KEY,在 RSSHub 根目录下编辑 .env
即可:
1TOPHUB_COOKIE=""
测试
接着,即可在本地进行调试了,通过 curl 或者浏览器访问对应路由,注意观察逻辑是否正确和渲染效果即可。
另外最好也把文档进行测试:
1cd website
2pnpm i
3pnpm run start:zh
部署
测试确保没问题后可以向 RSSHub 提交合并请求,需要格外注意格式,比如新增路由为 feat(route): xxx
。
如果嫌官方处理合并请求太慢,建议自己部署使用(另外需要配置变量的基本都得自己部署):
1git clone git@github.com:DIYgod/RSSHub.git && cd RSSHub
2pnpm i # npm install -g pnpm
3pm2 start lib/index.js --name rsshub # npm install pm2 -g
这里是用 pm2
让 RSSHub 运行在后台,可通过 pnpm start
之类的操作运行在前台。
之后每次更新,拉取完代码后重启即可 pm2 restart rsshub
,但如果有新的依赖,需要先运行 pnpm i
以更新依赖。
可能出现的问题
这里记录下遇到的问题,持续更新。
commit 失败
报错提示说缺少某个依赖(pinyin-pro),是文档相关的。
如果修改了文档,那么需要到 website 下运行 pnpm i
安装相关依赖,以便于 commit 检查。