使用 NodeJS 快速搭建代理 RSS 订阅服务

2 阅读2分钟

RSS 阅读器是我的内容消费神器,因为它免受平台限制,把散落于各大内容平台之外的珍贵资源汇集到一处,这类内容往往来自个人或公司的高质量创作,而非单纯的营销信息。

但是,我发现一些内容原生并不支持 RSS 订阅功能。这就促使我手动构建一个订阅服务,以便能够在我的 RSS 阅读器中订阅这些内容。

本文将介绍如何利用 serverless 模式搭建 www.arcblock.io/blog/ 的代理 RSS 订阅服务。

代码已开源:github.com/linchen1987…

RSS 系统结构

首先,了解一下 RSS 系统的三大组成部分:RSS 阅读器、RSS 源、和 RSS 发布器。

  • RSS 阅读器是我们直接用来阅读内容的客户端,如 Feedly feedly.com/
  • RSS 源则是一个可以公开访问的 XML 文件,例如阮一峰的博客 RSS 源 www.ruanyifeng.com/blog/atom.x…
  • RSS 发布器的职责是更新新内容到 RSS 源中,如阮一峰发布新文章时,他的 RSS 发布器便会将文章更新到其博客的 RSS 源中。

image.png

Serverless 的本质

Serverless 概念的核心是无需自己管理服务器。例如,你在自己的服务器上搭建并维护一个博客,这就需要服务器。相反,如果您将文章发布到掘金,可以把掘金视为为您的文章提供了 serverless 服务。

实现步骤

以订阅 www.arcblock.io/blog/ 的内容为例,下面是实现步骤:

  1. 获取信息源:想要订阅的内容列表页即是信息源。因为列表页总会按文章发布时间排序,把最新发布的文章展示在最前面。我想订阅的信息源是 www.arcblock.io/blog/
  2. 解析信息源:找到内容列表页后,先查看是否有 API 可用(通过浏览器的开发者工具查看网络请求)。如果有 API,可以直接从 API 接口获取内容。如果没有,就需要抓取网页内容,并解析出文章。例如,我找到了 Arcblock Blog 的 API:www.arcblock.io/blog/api/bl…

image.png

const thirdPartyApiUrl = 'https://www.arcblock.io/blog/api/blogs?page=1&size=20';
const response = await axios.get(thirdPartyApiUrl);
const data = response.data.data || [];
const blogs = [];

// 为每个博客条目添加到feed
data.forEach((item) => {
  blogs.push({
    title: blog.title,
    description: blog.excerpt,
    guid: blog.id,
    url: `https://www.arcblock.io/blog/${blog.slug}`,
    date: blog.publishTime,
  });
});
  1. 将信息解析成 XML 格式:处理获取到的信息,将其转换成适合 RSS 阅读器读取的 XML 格式。
import RSS from 'rss';

var feed = new RSS({
  title: 'ArcBlock Blog',
  description: 'Latest blogs from arcblock.io',
  feed_url:
  'https://link-general-public.s3.ap-northeast-3.amazonaws.com/arcblock-blog-feed.xml',
  site_url: 'https://www.arcblock.io/blog',
});

blogs.forEach((blog) => {
  feed.item(blog);
});
  1. 发布 XML 至公网:无需搭建自己的服务器,只需将 XML 文件发布到任何支持静态内容托管的服务,如 GitHub、AWS S3、Cloudflare R2、阿里云 OSS 等。在本例中,我将 XML 发布到了 AWS S3。
import fs from 'node:fs';
import path from 'node:path';
import 'dotenv-flow/config';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

export default async function upload(file) {
  const s3Client = new S3Client({});

  const bucketName = process.env.AWS_BUCKET;
  const fileStream = fs.createReadStream(file);

  await s3Client.send(
    new PutObjectCommand({
      Bucket: bucketName,
      Key: path.basename(file),
      Body: fileStream,
      ACL: 'public-read',
      ContentType: 'application/xml',
    })
  );
}
  1. 定时更新:同样,无需自建服务来实现定时更新功能。在本例中,我使用了 GitHub Actions 来定期执行更新操作,每天更新一次。
name: Schedule

on:
  schedule:
    - cron: '0 0 * * *'

jobs:
  main:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - uses: pnpm/action-setup@v2.0.1
        with:
          version: latest

      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '18'
          cache: 'pnpm'

      - name: Setup
        run: npm add -g @antfu/ni

      - name: Install
        run: nci

      - name: Run
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_BUCKET: ${{ secrets.AWS_BUCKET }}
          AWS_REGION: ${{ secrets.AWS_REGION }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: 'node tools/fetch-arcblock-blog.js'

总结

通过上述步骤,你就能搭建起一个能够代理不支持 RSS 的内容源的订阅服务,享受在 RSS 阅读器中阅读这些内容的便捷。

我们不仅要学会如何写代码,还要学会如何使用工具,站在巨人的肩膀上快速搭建我们想要的功能。