Last update on .

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

RSS的前生今世

在几年前乃至十几年前,RSS阅读非常流行,甚至不少人认为它是互联网阅读的未来——以后,大家都不用去各种网站了,只用RSS订阅就可以了,而Google推出的RSS订阅服务Google Reader,也一度被认为是最有价值的Google服务之一。

然而事情却悄然发生改变,随着微博、微信、头条等平台用户的增加,社交媒体平台提供的内容不再单纯是像RSS一样,以简单的时间排序不同来源的资料,而是凭借庞大使用者数据结合算法,以此推断用户偏好的内容,让算法筛选内容给平台用户。平台依靠算法,制造用户粘性,通过广告等方式实现盈利。

反过来,RSS似乎没有什么盈利空间,网站提供RSS给订阅者,是纯粹的内容,并不附带广告等可以产生收益的模块。而做RSS阅读器也只是为内容提供一个聚合场所,阅读器本身并不能对内容有所操控。这和利用推送算法运营的内容平台相比,商业前景根本无法相提并论,当Google在2013年宣布关停Google Reader之后,RSS逐渐没落。

最近在Google安卓版浏览器中加入了RSS订阅功能,这也应该算是RSS的某种程度的回归。

不管怎么说,RSS订阅在这个算法肆虐的互联网中,非常有其存在的必要性,像我这样的主动阅读者,订阅一些深度博主,学习分享其内容,也是乐在其中。奥,对了,今天的主题是讲一下Django中如何实现RSS订阅功能。

Django程序中实现RSS功能

Feed 类

Feed 类是一个 Python 类,它表示一个聚合 feed。Feed 可以是简单的(例如,一个“站点新闻”feed,或者一个显示博客最新条目的基本 feed),也可以是更复杂的(例如,一个显示特定类别中所有博客条目的 feed,其中类别是可变的)。Feed类存在于:

from django.contrib.syndication.views import Feed

一个简单的例子

我们深入了解一下Zinnia博客框架代码,来讲解RSS订阅功能如何实现吧,代码有点老,不过我参考了一下Django官网的最新版,这块内容的变化其实不太大。

class ZinniaFeed(Feed):
    """
    Base Feed class for the Zinnia application,
    enriched for a more convenient usage.
    """
    protocol = PROTOCOL
    feed_copyright = COPYRIGHT
    feed_format = FEEDS_FORMAT
    limit = FEEDS_MAX_ITEMS

    def __init__(self):
        if self.feed_format == 'atom':
            self.feed_type = Atom1Feed
            self.subtitle = getattr(self, 'description', None)

    def title(self, obj=None):
        """
        Title of the feed prefixed with the site name.
        """
        return '%s - %s' % (self.site.name, self.get_title(obj))

    def get_title(self, obj):
        raise NotImplementedError

    @property
    def site(self):
        """
        Acquire the current site used.
        """
        return Site.objects.get_current()

    @property
    def site_url(self):
        """
        Return the URL of the current site.
        """
        return '%s://%s' % (self.protocol, self.site.domain)


class EntryFeed(ZinniaFeed):
    """
    Base Entry Feed.
    """
    title_template = 'feeds/entry_title.html'
    description_template = 'feeds/entry_description.html'

    def item_pubdate(self, item):
        """
        Publication date of an entry.
        """
        return item.publication_date

    def item_updateddate(self, item):
        """
        Update date of an entry.
        """
        return item.last_update

    def item_categories(self, item):
        """
        Entry's categories.
        """
        return [category.title for category in item.categories.all()]

    def item_author_name(self, item):
        """
        Return the first author of an entry.
        """
        if item.authors.count():
            self.item_author = item.authors.all()[0]
            return self.item_author.__str__()

    def item_author_email(self, item):
        """
        Return the first author's email.
        Should not be called if self.item_author_name has returned None.
        """
        return self.item_author.email

    def item_author_link(self, item):
        """
        Return the author's URL.
        Should not be called if self.item_author_name has returned None.
        """
        try:
            author_url = self.item_author.get_absolute_url()
            return self.site_url + author_url
        except NoReverseMatch:
            return self.site_url

    def item_enclosure_url(self, item):
        """
        Return an image for enclosure.
        """
        try:
            url = item.image.url
        except (AttributeError, ValueError):
            img = BeautifulSoup(item.html_content, 'html.parser').find('img')
            url = img.get('src') if img else None
        self.cached_enclosure_url = url
        if url:
            url = urljoin(self.site_url, url)
            if self.feed_format == 'rss':
                url = url.replace('https://', 'http://')
        return url

    def item_enclosure_length(self, item):
        """
        Try to obtain the size of the enclosure if it's present on the FS,
        otherwise returns an hardcoded value.
        Note: this method is only called if item_enclosure_url
        has returned something.
        """
        try:
            return str(item.image.size)
        except (AttributeError, ValueError, os.error):
            pass
        return '100000'

    def item_enclosure_mime_type(self, item):
        """
        Guess the enclosure's mimetype.
        Note: this method is only called if item_enclosure_url
        has returned something.
        """
        mime_type, encoding = guess_type(self.cached_enclosure_url)
        if mime_type:
            return mime_type
        return 'image/jpeg'


class LastEntries(EntryFeed):
    """
    Feed for the last entries.
    """

    def link(self):
        """
        URL of last entries.
        """
        return reverse('zinnia:entry_archive_index')

    def items(self):
        """
        Items are published entries.
        """
        return Entry.published.all()[:self.limit]

    def get_title(self, obj):
        """
        Title of the feed
        """
        return _('Last entries')

    def description(self):
        """
        Description of the feed.
        """
        return _('The last entries on the site %(object)s') % {
            'object': self.site.name}

在Zinnia文件夹中,打开Feeds.py,也就是上面的代码,我们主要就看三个类,首先是ZinniaFeed类,直接继承自Django的Feed类,这一层只是将Zinnia设定的一些常量Load进来,以供子类使用,我们看到Zinnia使用的RSS协议是atom。

EntryFeed直接继承自ZinniaFeed,这个类扩展了Feed类中的常用功能,如获得一篇文章的发布日期,获得文章的分类,获得文章作者的名字,获得文章的封面图片(item_enclosure_url)等等。同时EntryFeed还定义了title_template 和 description_template 的模板。模板内容如下。

#entry_title.html
{{ obj.title|safe }}
#entry_description.html
{{ obj.html_lead|safe }}
{{ obj.html_content|safe }}

然后Zinnia就基于EntryFeed,构建了若干个子类,这里就讲LastEntries,这个是文章聚合的核心类,就是重新实现了文章聚合所必需的4个要素 items、title、link 和 description。

这里说一下,其实如果只是简单的构建一个聚合类,将EntryFeed直接继承Django的Feed,也是可以的。

RSS订阅的Url路由

首先,Zinnia的app层的路由:

urlpatterns = [
    url(_(r'^feeds/'), include('zinnia.urls.feeds')),
}

Feeds的路由

urlpatterns = [
    url(r'^$',
        LastEntries(),
        name='entry_feed'),

这样,只要使用路径 http://www.zyylee.com/feeds,就可以添加订阅了。从苹果的AppStore随便下载一个订阅器,输入上面的订阅路径,能够显示出来RSS推送的内容,说明Rss订阅功能已经部署成功了。

评论

No comments yet.

Please log in by using LinkedIn Weibo to leave a comment.