Skip to content

拒绝算法推荐,用 RSS 构建属于自己的信息流

Posted on:February 18, 2022 at 12:45 PM

Table of Contents

Open Table of Contents

算法推荐带来了什么,带走了什么

马克思说“人是社会关系的总和”,我们想获取更高质量的输入,就需要从不同渠道去与高水平的人建立关系,从不同渠道去获取高质量的信息。

在当前的 Web2.0 时代,每个人都可以在不同的地方发声,我们获取信息的渠道变得多种多样,例如公众号、博客、论坛、视频网站、问答社区等,为了降低获取信息的成本、提升信息的质量,一些技术手段也被应用在了信息获取上,比如算法推荐,它让搜索引擎更加强大,服务推送更加贴合用户。但在带来了一些好处的同时,它也带走了一些东西,比如下面的典型场景:

特别是随着技术的发展和应用的成熟,算法推荐已经快随处可见,那么我们是否能在算法推荐之外再创造一片“净土”,构建一个完全由自己管理的信息流?这就是今天要分享的主题 —— RSS。

RSS 是什么

一个典型的 RSS 订阅源数据格式

一个典型的 RSS 订阅源数据格式

RSS 原意为资源描述框架站点摘要(RDF Site Summary),后演变为简易信息聚合(Really Simple Syndication),是一种消息来源格式规范,用以聚合网站内容更新。RSS 作为早期互联网的产物,随着互联网巨头的快速崛起、信息壁垒的建立,并以 2013 年 7 月 15 日 Google Reader 的服务终止为节点,作为一种「过时」技术濒临消亡。但幸运的是,RSS 在互联网浪潮的冲击下,虽然经历了式微的低谷,依然有一批信仰互联网精神的用户坚守到迎来复兴。

它和算法的本质区别在于:RSS 是信息消费者主导的「主动信息聚合」,算法是信息生产者主导的「被动信息聚合」。 而这,正是选择之争。

我们常见的信息来源可能有这些:

通过 RSS 我们就可以将这些来源的信息过滤、聚合在一起,主动构建属于自己的信息流,有效地提交信息的质量。

RSS 的发展历史

开始讲如何应用之前先介绍一下 RSS 的发展历史,主要是早期创造相关的内容,因为这里特别想提到一个人 Aaron Swartz,我们在场程序员应该都是他的作品的受益者,我们可以先看一下他的一部分作品或经历:

这个老哥的信念是公共知识、信息和期刊是人类的共有财产,不应该被少数人掌握和利用。也是因为这个愿景,利用 MIT 的网络下载了大量的 JSTOR 出版社的图书资源想公开出来,恰逢当时美国有几例不良黑客事件,FBI 借机拿他“杀鸡儆猴”,控告了他五项重罪。

这期间保释出来以后又积极带领了反对《禁止网络盗版法案》的运动,因为这个法案损害了互联网的自由性,让网站岌岌可危,一个侵权声明就可以关停别人的网站。在成功阻止了这个由大多数参议员和大型企业主导的法案后,FBI 又给他上面的事情加了 9 项重罪,累计可能面临 50 年监禁加上 100 万美元罚款,除非他认罪才有机会减刑。最后压力过大在公寓里崩溃了。

有一部电影叫《互联网之子》是关于他的记录片,这个老哥从小学的年纪就用开始用计算机做了很多有意思的事情,很可惜英年早逝了,他的技术、信念、行动力都让人敬佩,我当时看完《互联网之子》也深受触动,所以这里讲 RSS 特地讲一下他,相当于一个小致敬。

部署自己的 RSS 服务

讲完了故事,接下来讲讲实际的,我们能用到的 RSS 服务。像上面说的,谷歌 13 年停止运营 Google Reader 以后,RSS 技术其实已经濒临消亡了,因此剩下的为数不多的 RSS 服务免费的都不那么好用,都需要冲钱才能变得更强。这里我们可以选择直接冲钱,比如使用 feedly、inoreader 之类的 RSS 订阅服务,直接将各种信息源聚合在一起。我更推荐自己找一台服务器部署一个自己的服务,这会更加可控和有意思。我们可以选择部署一个 Tiny Tiny RSS 服务,社区已经有了现成的部署方案 Awesome-TTRSS,直接使用 docker-compose 就可以部署:

准备工作

服务配置文件

一、docker-compose.yml

这里我们使用 docker compose 来部署服务,因此先写一个 docker-compose.yml 文件:

version: "3"
services:
  ttrss:
    image: wangqiru/ttrss:latest
    container_name: ttrss
    environment:
      - SELF_URL_PATH=https://rss.yourdomain.com/ # 换成你的域名
      - DB_PASS=password # 定义你的数据库密码
      - PUID=1000
      - PGID=1000
    volumes:
      - feed-icons:/var/www/feed-icons/
    networks:
      - public_access
      - service_only
      - database_only
    stdin_open: true
    tty: true
    restart: always

  mercury:
    image: wangqiru/mercury-parser-api:latest
    container_name: mercury
    networks:
      - public_access
      - service_only
    restart: always

  opencc: #
    image: wangqiru/opencc-api-server:latest
    container_name: opencc
    environment:
      - NODE_ENV=production
    networks:
      - service_only
    restart: always

  database.postgres:
    image: postgres:13-alpine
    container_name: postgres
    environment:
      - POSTGRES_PASSWORD=password # 数据库密码,同上
    volumes:
      - ~/postgres/data/:/var/lib/postgresql/data
    networks:
      - database_only
    restart: always

  caddy:
    image: caddy:2
    container_name: caddy
    restart: always
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy-config:/config
      - ./caddy-data:/data
    networks:
      - public_access
    environment:
      - DOMAIN=https://rss.yourdomain.com # 换成你的域名
      - EMAIL=email@email.com # 换成你的邮箱,用于自动申请 SSL 证书
      - LOG_FILE=/data/access.log

volumes:
  feed-icons:

networks:
  public_access:
  service_only:
    internal: true
  database_only:
    internal: true

按照你自己的情况改掉上面有注释的几行配置

二、Caddyfile

Caddy 是一个类似 Nginx 的 web 服务器,它相比 Nginx 更加简单易用,性能也不错,最大的特点是能够自动申请 SSL 证书,让我们的服务以 HTTPS 的形式透出。

{$DOMAIN}:443 {
  log {
    level INFO
    output file {$LOG_FILE} {
      roll_size 10MB
      roll_keep 10
    }
  }

  # Use the ACME HTTP-01 challenge to get a cert for the configured domain.
  tls {$EMAIL}

  # This setting may have compatibility issues with some browsers
  # (e.g., attachment downloading on Firefox). Try disabling this
  # if you encounter issues.
  encode gzip

  # Proxy everything else to Rocket
  reverse_proxy ttrss:80 {
       # Send the true remote IP to Rocket, so that ttrss can put this in the
       # log, so that fail2ban can ban the correct IP.
       header_up X-Real-IP {remote_host}
  }
}

这一个文件不需要改,里面的占位符会自动根据 docker-compose.yml 里定义的环境变量替换。

在上述两个文件都配置好并放到同一个目录下后,可以手动运行

docker compose up -d

安装 docker: https://github.com/docker/docker-install

安装 docker compose: https://docs.docker.com/compose/cli-command/#install-on-linux

安装并启动完后,可以访问配置的域名,通过 admin 账户和前面设置的密码,以管理员用户登录,进行一些基本配置以及用户管理,创建一个自己使用的非管理员用户:

Untitled

之后再以非管理员用户登录,就可以开始定制和使用了。

一、订阅源分类管理 - 进入“偏好设置”

Untitled

二、订阅新的信息源到对应类别中

Untitled

三、开始使用

RSS 订阅界面

RSS 订阅界面

移动客户端

如果是安卓用户,推荐使用 Feedly 客户端连接到自己的服务上。

如果是 IOS 用户,好像 Feeder 和 Reeder 都不错,不过没有实际使用过。

RSS 使用技巧

以下是一些 RSS 的使用技巧,可以让 RSS 的使用变得更加顺滑。

RSSHub Radar

很多时候,我们并不清楚各种来源的信息是否支持以 RSS 的方式订阅,或者一个网站里面有哪些内容可以订阅,比如 Github 是不是能订阅,有哪些内容可以订阅。

这里推荐一个工具 RSSHub Radar,这是一个浏览器扩展,通过它我们可以快速发现各种网站已有的订阅源,一般是网站自身提供的,或者社区里的爱好者贡献的。

Github 仓库的订阅源

Github 仓库的订阅源

RSSHub - 万物皆可 RSS

如果一个网站没有现成可用的订阅源怎么办?我们可以借助 RSSHub 这个开源项目,将任何信息源转换为 RSS 源,实现“万物皆可 RSS”。

创建示例

在官方仓库的 lib/routes 路径下添加 js 脚本,就可以创建新的 RSS 路由,例如 /lib/routes/bilibili/coin.js。以下是一些创建的示例,分为获取数据和将数据转换为指定的数据结构两步。

一、获取数据

// 发起 HTTP GET 请求
const response = await got({
  method: "get",
  url: `https://api.bilibili.com/x/space/coin/video?vmid=${uid}&jsonp=jsonp`,
  headers: {
    Referer: `https://space.bilibili.com/${uid}/`,
  },
})

const data = response.data.data // response.data 为 HTTP GET 请求返回的数据对象
// 这个对象中包含了数组名为 data,所以 response.data.data 则为需要的数据
// 发起 HTTP GET 请求
const response = await got({
  method: "get",
  url: "https://www.douban.com/explore",
})

const data = response.data // response.data 为 HTTP GET 请求返回的 HTML,也就是简书首页的所有 HTML
const $ = cheerio.load(data) // 使用 cheerio 加载返回的 HTML
const list = $("div[data-item_id]")
// 使用 cheerio 选择器,选择带有 data-item_id 属性的所有 div 元素,返回 cheerio node 对象数组

// 注:每一个 cheerio node 对应一个 HTML DOM
// 注:cheerio 选择器与 jquery 选择器几乎相同
// 参考 cheerio 文档:https://cheerio.js.org/
// 使用 RSSHub 提供的 puppeteer 工具类,初始化 Chrome 进程
const browser = await require("@/utils/puppeteer")()
// 创建一个新的浏览器页面
const page = await browser.newPage()
// 访问指定的链接
const link = "https://sspai.com/series"
await page.goto(link)
// 渲染目标网页
const html = await page.evaluate(
  () =>
    // 选取渲染后的 HTML
    document.querySelector("div.new-series-wrapper").innerHTML
)
// 关闭浏览器进程
browser.close()
const $ = cheerio.load(html) // 使用 cheerio 加载返回的 HTML
const list = $("div.item") // 使用 cheerio 选择器,选择所有 <div class="item"> 元素,返回 cheerio node 对象数组

二、返回指定的数据格式

ctx.state.data = {
  // 源标题
  title: `${name} 的 bilibili 投币视频`,
  // 源链接
  link: `https://space.bilibili.com/${uid}`,
  // 源说明
  description: `${name} 的 bilibili 投币视频`,
  //遍历此前获取的数据
  item: data.map(item => ({
    // 文章标题
    title: item.title,
    // 文章正文
    description: `${item.desc}<br><img src="${item.pic}">`,
    // 文章发布时间
    pubDate: new Date(item.time * 1000).toUTCString(),
    // 文章链接
    link: `https://www.bilibili.com/video/av${item.aid}`,
  })),
}

// 至此本路由结束

免费部署自己的 RSSHub

RSSHub 是一个社区项目,其官方服务由各方赞助提供,因此订阅的人很多,官方服务的稳定性可能没有那么高。我们可以选择自行部署 RSSHub,生成自己的服务。

这里推荐一个免费易用的做法,直接利用 Vercel 的 Serverless Function 部署 RSSHub。

点击这个链接,就可以一键实现服务的部署,部署成功后我们还可以配置一个自定义的域名,实现更方便的访问。

Untitled

订阅源推荐

参考引用

  1. 基于 RSS 的信息体系构建

.