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.