RSS 获取信息指南(二)

发布时间 2023-04-02 15:46:32作者: 晨米酱

实现自定义 RSS 链接的本质是对网页内容进行爬取。而爬取的方式可以分为两种:

  1. 通过 API 获取内容,只要知道内容是从哪个 API 获取的,就可以使用 fetchaxios 获取内容,并将其转换成 RSS 模板。

  2. 通过 HTML 文档获取内容,不同的页面渲染方式需要不同的爬取策略。具体渲染内容可以参考我的另一篇文章:SSR、CSR、SSG 和混合式渲染

    • 对于通过 CSR 渲染的 HTML 页面,例如使用 React 或 Vue 构建的单页面应用,需要使用中间浏览器爬虫(例如 puppeteer)将页面渲染出来,然后解析页面内容
    • 对于通过 SSR、SSG 渲染的 HTML 页面,可以省略页面渲染步骤,直接解析内容
    • 对于混合渲染,根据需要判断并选择上述方式

    判断哪种渲染方式也很简单,通过浏览器控制台的网络查看 document 的内容即可判断。如果里面只有 HTML 模板(例如通过 <div id="root"></div> 加载内容),而没有实际内容,说明是 CSR 方式,反之亦然。解析内容需要使用第三方库,例如 cheerio,它可以像 jQuery 一样操作 HTML 文档内容,然后将解析后的内容填充到 RSS 模板中

优先推荐使用 API 方式获取内容,其次使用 html 页面解析方式

RSSHub 项目实现RSS链接也是依据上述原理,具体查看项目文档:制作自己的 RSSHub 路由

自定义 RSSHub

如果你想添加新的 RSSHub 规则,只需按照项目文档的规范即可实现。另外,如果你不喜欢每次查看信息都需要跳转到外部链接才能看到全部内容,例如阅读bilibili文章,或者因为其他原因,只需要改变原有的HTML解析方式即可实现。下面通过一个例子来说明:

专栏:渔民职业又怎样

rss订阅地址:https://rsshub.app/bilibili/readlist/521896

// 源码爬取方式如下,每篇文章只有简要描述,没有全部内容,需要跳转链接

const got = require('@/utils/got');

module.exports = async (ctx) => {
    const listid = ctx.params.listid;
    const listurl = `https://www.bilibili.com/read/readlist/rl${listid}`;

    const response = await got({
        method: 'get',
        url: `https://api.bilibili.com/x/article/list/web/articles?id=${listid}&jsonp=jsonp`,
        headers: {
            // 反爬取设置
            Referer: listurl,
        },
    });
    const data = response.data.data;

    ctx.state.data = {
        title: `bilibili 专栏文集 - ${data.list.name}`,
        link: listurl,
        image: data.list.image_url,
        description: data.list.summary ? data.list.summary : '作者很懒,还木有写简介.....((/- -)/',
        item:
            data.articles &&
            data.articles.map((item) => ({
                title: item.title,
                author: data.author.name,
                // 每篇文章只有简要描述,没有全部内容,需要跳转链接
                description: `${item.summary}…<br><img src="${item.image_urls[0]}">`,
                pubDate: new Date(item.publish_time * 1000).toUTCString(),
                link: `https://www.bilibili.com/read/cv${item.id}/?from=readlist`,
            })),
    };
};

改造源码解析:

module.exports = async (ctx) => {
    // 省略部分代码
    const item = await Promise.all(
        // 爬取每一篇文章的内容
        data.articles.map(async (item) => {
            const art_url = `https://www.bilibili.com/read/cv${item.id}/?from=readlist`;
            // 使用缓存策略或获取页面内容
            const itemData = await ctx.cache.tryGet(
                art_url,
                async () =>
                    (
                        await got({
                            method: 'get',
                            url: art_url,
                            headers: {
                                Referer: listurl,
                            },
                        })
                    ).data
            );
            // cheerio 解析内容
            const content = cheerio.load(itemData);
            const eDescription = `<img src="${item.image_urls[0]}">` + content('#read-article-holder').html();
            const publishDate = parseDate(item.publish_time * 1000);
            const single = {
                title: item.title,
                author: data.author.name,
                link: art_url,
                // 文章全部内容
                description: eDescription,
                pubDate: publishDate,
            };
            return single;
        })
    );
    ctx.state.data = {
        // 省略部分代码
        item,
    };
};

上面通过进一步爬取文章来获取内容,由于这个过程需要更长的响应时间,建议对文章数量进行限制,并将程序部署在本地或响应速度较快的服务器上。当然,这只是一个简单的改进方式,仅展示了爬取操作的基本流程。具体要实现什么功能还需要自己编写代码实现。

参考