Django添加Web Feed消息聚合

作为一个博客爱好者,我是很喜欢RSS/Atom Feed的方式,可以很方便的订阅别人的博客、新闻等内容,经常浏览别人的博客,发现RSS Feed Reader都会显示一个添加订阅的图标,挺方便的。

但是作为一个前端开发,自己的博客居然没有RSS/Atom订阅,不能忍,果断花2小时加上再说。

1. 什么是RSS/Atom

引用维基百科的解释:

RSS(英文全称:RDF Site Summary 或 Really Simple Syndication),中文译作简易资讯聚合,也称聚合内容,是一种消息来源格式规范,用以聚合多个网站更新的內容并自动通知网站订阅者。使用RSS后,网站订阅者便无需再手动查看网站是否有新的內容,同时 RSS 可將多个网站更新的內容进行整合,以摘要的形式呈现,有助于订阅者快速获取重要信息,并选择性地点阅查看。

Atom是一对彼此相关的标准。Atom供稿格式(Atom Syndication Format)是用于网站消息来源,基于XML的文档格式;而Atom出版协定(Atom Publishing Protocol,简称AtomPub或APP)是用于新增及修改网络资源,基于HTTP的协议。

它借鉴了各种版本RSS的使用经验,被许多的聚合工具广泛使用在发布和使用上。Atom供稿格式设计作为RSS的替代品;而Atom出版协定用来取代现有的多种发布方式(如Blogger API和LiveJournal XML-RPC Client/Server Protocol)。Google提供的多种服务正在使用Atom。Google Data API(GData)亦基于Atom。

简单来说,就是RSS/Atom提供了一种统一的数据格式规范,网站可以将内容通过这种规范进行编码生成,订阅者通过RSS/Atom阅读器解析这些数据,就能实现在不打开网站的情况下实现网站内容的阅读。

2. Django的Feed聚合框架

Django 提供了一个高级的 feed 聚合生成框架来创建 RSS 和 Atom Feed。

同时还提供了一个低级别的 feed 生成 API。如果你想在网络内容之外或以其他较低级别的方式生成 feed,可以使用这个 API。

一个简单的例子

from django.contrib.syndication.views import Feed
from django.urls import reverse
from policebeat.models import NewsItem

class LatestEntriesFeed(Feed):
    title = "Police beat site news"
    link = "/sitenews/"
    description = "Updates on changes and additions to police beat central."

    def items(self):
        return NewsItem.objects.order_by('-pub_date')[:5]

    def item_title(self, item):
        return item.title

    def item_description(self, item):
        return item.description

    # item_link is only needed if NewsItem has no get_absolute_url method.
    def item_link(self, item):
        return reverse('news-item', args=[item.pk])

要连接到这个 feed 的 URL,在你的 URLconf 中放入一个 Feed 对象的实例。例如:

from django.urls import path
from myproject.feeds import LatestEntriesFeed

urlpatterns = [
    # ...
    path('latest/feed/', LatestEntriesFeed()),
    # ...
]

指定 feed 的类型

默认情况下,这个框架中产生的 feed 使用 RSS 2.0。

要改变这一点,可以在你的 Feed 类中添加一个 feed_type 属性,像这样:

from django.utils.feedgenerator import Atom1Feed

class MyFeed(Feed):
    feed_type = Atom1Feed

自定义 feed 对象

一般情况下,默认提供的 api 已经能够完成消息聚合了,但是如果我们需要在生成内容中增加新的字段,又或者提供 django feed 框架没有的聚合消息,那就需要自定义 feed 对象了。

一个简单例子:

class iTunesFeed(Rss201rev2Feed):
    def root_attributes(self):
        attrs = super().root_attributes()
        attrs['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
        return attrs

    def add_root_elements(self, handler):
        super().add_root_elements(handler)
        handler.addQuickElement('itunes:explicit', 'clean')

我这次要提供的是Atom Feed,默认已经提供这种类型,但是唯一的问题是默认并没有 content 字段,所以需要自定义一下:

from django.contrib.syndication.views import Feed
from django.urls import reverse
from django.utils.feedgenerator import Atom1Feed
from policebeat.models import NewsItem

class Atom1FeedExtend(Atom1Feed):
    def add_item_elements(self, handler, item):
        super().add_item_elements(self, handler, item)
        handler.addQuickElement('content', item.get('content', ''), { "type": "html" })

class LatestEntriesFeed(Feed):
    feed_type = Atom1FeedExtend
    title = "Police beat site news"
    link = "/sitenews/"
    description = "Updates on changes and additions to police beat central."

    def items(self):
        return NewsItem.objects.order_by('-pub_date')[:5]

    def item_title(self, item):
        return item.title

    def item_description(self, item):
        return item.description

    # item_link is only needed if NewsItem has no get_absolute_url method.
    def item_link(self, item):
        return reverse('news-item', args=[item.pk])
    
    def item_extra_kwargs(self, item):
        return {
          "content": item.content
        }

到这里,服务端已经是准备好了。

3. 页面提供RSS/Atom Feed订阅入口

有人可能会想,这里有什么要做的,就是提供个链接给阅读器就行了啊。

是的,说的没错,但是只能说说对了一半。提供订阅链接很简单,一个 a 标签,href 填 feed 地址就行,用户复制或者点击就能触发订阅器的订阅提示。

但是就像我开头说的,基本上网站如果提供了 feed 消息聚合了,那么订阅器就会自动的提示用户,这是怎么做到的呢,这就要说一下目前 RSS/Atom 阅读器的主动探测方式了。

这里就以 RSSHub Radar 为例,介绍下阅读器是怎么主动检测页面的 RSS/Atom 链接的。

首先遍历页面所有的链接肯定是不可取的,还在标准中指定了一种特殊 MIME 类型的 link 标签来指明 RSS/Atom 链接,link[type="application/rss+xml"] 和 link[type="application/atom+xml"],RSSHub Radar 正是通过这个标签来检测页面是否有自带 RSS,具体实现在这里

只要提供了这个 link 标签,那基本上阅读器都会能主动提示订阅了。

4. 参考

5. 最后

本博客的 Atom 订阅已经上线,欢迎订阅。