拓展阅读

RSS 一种简洁优雅的数据订阅方式

RSSHub Everything is RSSible 开源、易于使用且可扩展的 RSS 提要生成器

RSS 介绍

RSS(Really Simple Syndication)是一种用于发布网站更新的标准格式。

它允许用户获取网站内容的最新更新,而无需访问网站本身。RSS通常用于博客、新闻网站、音频和视频网站等,让用户可以通过一个统一的接口跟踪多个网站的更新。

以下是 RSS 的一些关键概念和特点:

  1. XML 格式:RSS使用XML(可扩展标记语言)格式来组织数据。XML是一种结构化数据格式,易于解析和处理。

  2. Feed:RSS提供的数据源称为Feed,它包含网站的更新内容。Feed通常包括标题、摘要、发布时间、作者等信息,以及内容的链接。

  3. 订阅器(Feed Reader):订阅器是用于读取和展示RSS Feed的应用程序或服务。用户可以通过订阅器订阅感兴趣的Feed,并在订阅器中查看最新更新,而无需访问原始网站。

  4. 订阅:用户通过订阅器添加感兴趣的Feed,订阅器会定期检查这些Feed,并在有新内容时通知用户。用户可以随时取消订阅。

  5. 聚合站点:有些网站会聚合多个来源的内容,并提供一个统一的RSS Feed。这种聚合网站可以帮助用户更方便地跟踪多个来源的更新。

  6. 更新频率:每个Feed都有一个更新频率,表示内容更新的时间间隔。一些Feed可能每小时更新,而其他的可能每天或每周更新。

  7. 协议和标准:RSS有多个版本和变种,包括RSS 0.91、RSS 1.0、RSS 2.0以及Atom。这些标准定义了Feed的结构、元素和语法规则。

  8. 许可协议:发布RSS Feed的网站通常会指定使用该Feed的许可协议,例如使用条件、允许的用途等。

总的来说,RSS提供了一种方便的方式来订阅和浏览网站的更新内容,使用户可以更有效地获取感兴趣的信息。

虽然在过去几年里,随着社交媒体和其他内容聚合平台的兴起,RSS的使用量有所下降,但它仍然是许多用户和网站的重要工具。

比如老马个人的 rss 订阅

https://houbb.github.io/feed.xml

RSS 有什么用?为什么需要 RSS

RSS 在互联网上扮演着重要的角色,其用途和需求有以下几个方面:

  1. 信息聚合:RSS 提供了一种方便的方式来聚合多个网站的内容。通过订阅感兴趣的网站的 RSS Feed,用户可以将这些内容集中在一个地方进行阅读,而无需逐个访问每个网站。

  2. 及时更新:RSS 可以及时地通知用户有关网站内容的更新。当网站发布新的文章、新闻或其他类型的内容时,用户可以通过 RSS 订阅器立即收到通知,而不必等待或主动检查网站。

  3. 个性化阅读:通过订阅感兴趣的 RSS Feed,用户可以根据自己的兴趣和需求定制阅读内容。这种个性化阅读方式比传统的浏览器书签或收藏夹更灵活。

  4. 节省时间:RSS 可以帮助用户节省时间,避免浪费在逐个网站上检查更新的时间。用户只需在订阅器中浏览最新的 RSS Feed,即可获取所有感兴趣的网站的更新。

  5. 跨平台使用:RSS 可以在各种设备和平台上使用,包括桌面电脑、笔记本电脑、智能手机和平板电脑。无论用户在何处或使用何种设备,他们都可以方便地访问和阅读 RSS 订阅的内容。

综上所述,RSS 在信息获取和阅读方面具有重要的作用,它提供了一种高效、个性化和方便的方式来管理和浏览互联网上的内容,因此备受用户青睐。

类似的信息订阅方式还有哪些?

除了RSSHub以外,还有一些其他的信息订阅方式,包括但不限于:

  1. Email订阅:许多网站提供了Email订阅服务,用户可以通过输入自己的Email地址订阅网站的更新。每当网站发布新的内容时,用户会收到一封包含更新内容的电子邮件。

  2. 社交媒体关注:用户可以通过在社交媒体平台上关注自己感兴趣的网站、博客或个人账号来获取最新的更新。这些平台通常会推送用户感兴趣的内容,例如Twitter的关注和推文、Facebook的订阅和页面更新等。

  3. 应用程序推送通知:一些应用程序提供了推送通知服务,用户可以通过这些应用程序接收到他们关注的网站或应用的最新更新。这种方式通常需要用户在应用程序中进行订阅或设置。

  4. 内容聚合平台:一些内容聚合平台(如Flipboard、Feedly等)提供了类似于RSS的功能,用户可以在这些平台上订阅自己感兴趣的内容,然后在平台上浏览和阅读最新的更新。

  5. 自动化工具和服务:一些自动化工具和服务(如IFTTT、Zapier等)可以帮助用户设置各种类型的信息订阅和通知。用户可以使用这些工具和服务来创建定制的订阅规则,以满足自己的特定需求。

总的来说,信息订阅方式多种多样,用户可以根据自己的偏好和需求选择适合自己的方式来获取感兴趣的内容更新。

学习网址

以下是一些学习 RSS 语法的资料,包括网址:

  1. RSS 2.0 规范
  2. W3C RDF Site Summary (RSS 1.0) 规范
    • 网址:https://www.w3.org/TR/REC-rdf-syntax/
    • 描述:W3C提供了关于RDF Site Summary(RSS 1.0)的规范。RSS 1.0基于RDF(资源描述框架)语法,具有一些与RSS 2.0不同的特性。
  3. RSS Tutorial - w3schools
  4. RSS 2.0 Validator
    • 网址:https://validator.w3.org/feed/
    • 描述:W3C提供的RSS 2.0验证工具,可用于验证RSS 2.0 Feed的语法和格式是否符合规范。
  5. RSS 2.0 vs Atom 1.0

这些资源涵盖了RSS的基础知识、语法规范以及验证工具,可以帮助您深入了解和学习RSS。

RSS

RSS 指 Really Simple Syndication(真正简易联合)。

  • RSS 使您有能力聚合(syndicate)网站的内容

  • RSS 定义了非常简单的方法来共享和查看标题和内容

  • RSS 文件可被自动更新

  • RSS 允许为不同的网站进行视图的个性化

  • RSS 使用 XML 编写

语法

RSS 2.0 的语法很简单,也很严格。

RSS 如何工作

RSS 用于在网站间分享信息。 使用 RSS,您在名为聚合器的公司注册您的内容。 步骤之一是,创建一个 RSS 文档,然后使用 .xml 后缀来保存它。然后把此文件上传到您的网站。接下来,通过一个 RSS 聚合器来注册。 每天,聚合器都会到被注册的网站搜索 RSS 文档,校验其链接,并显示有关 feed 的信息,这样客户就能够链接到使他们产生兴趣的文档。

RSS 实例

RSS 文档使用一种简单的自我描述的语法。

<?xml version="1.0" encoding="ISO-8859-1" ?>
<rss version="2.0">

<channel>
  <title>W3Schools Home Page</title>
  <link>http://www.w3schools.com</link>
  <description>Free web building tutorials</description>
  <item>
    <title>RSS Tutorial</title>
    <link>http://www.w3schools.com/rss</link>
    <description>New RSS tutorial on W3Schools</description>
  </item>
  <item>
    <title>XML Tutorial</title>
    <link>http://www.w3schools.com/xml</link>
    <description>New XML tutorial on W3Schools</description>
  </item>
</channel>

</rss>

文档中的第一行:XML 声明 - 定义了文档中使用的 XML 版本和字符编码。此例子遵守 1.0 规范,并使用 ISO-8859-1 (Latin-1/West European) 字符集。 下一行是标识此文档是一个 RSS 文档的 RSS 声明(此例是 RSS version 2.0)。

下一行含有 <channel> 元素。此元素用于描述 RSS feed。
<channel> 元素有三个必需的子元素:
<title> - 定义频道的标题。(比如 w3school 首页)
<link> - 定义到达频道的超链接。(比如 www.w3school.com.cn)
<description> - 描述此频道(比如免费的网站建设教程)
每个 <channel> 元素可拥有一个或多个 <item> 元素。
每个 <item> 元素可定义 RSS feed 中的一篇文章或 "story"。
<item> 元素拥有三个必需的子元素:
<title> - 定义项目的标题。(比如 RSS 教程)
<link> - 定义到达项目的超链接。(比如 http://www.w3school.com.cn/rss)
<description> - 描述此项目(比如 w3school 的 RSS 教程)
最后,后面的两行关闭 <channel> 和 <rss> 元素。

注释

在 RSS 中书写注释的语法与 HTML 的语法类似:

<!-- This is an RSS comment -->

因为 RSS 也是 XML,请记住:

  • 所有的元素必许拥有关闭标签

  • 元素对大小写敏感

  • 元素必需被正确地嵌套

  • 属性值必须带引号

RSS <channel>

元素可描述 RSS feed。

以下三个元素是必须的子元素:

<title> - 定义频道的标题。(比如 w3school 首页)
<link> - 定义到达频道的超链接。(比如 www.w3school.com.cn)
<description> - 描述此频道(比如免费的网站建设教程)
ELEM Desc
<category> 可选的。为 feed 定义所属的一个或多个种类。
<cloud> 可选的。注册进程,以获得 feed 更新的立即通知。
<copyright> 可选。告知版权资料。
<description> 必需的。描述频道。
<docs> 可选的。规定指向当前 RSS 文件所用格式说明的 URL。
<generator> 可选的。规定用于生成 feed 的程序。
<image> 可选的。在聚合器呈现某个 feed 时,显示一个图像。
<language> 可选的。规定编写 feed 所用的语言。
<lastBuildDate> 可选的。定义 feed 内容的最后修改日期。
<link> 必需的。定义指向频道的超链接。
<managingEditor> 可选的。定义 feed 内容编辑的电子邮件地址。
<pubDate> 可选的。为 feed 的内容定义最后发布日期。
<rating> 可选的。feed 的 PICS 级别。
<skipDays> 可选的。规定忽略 feed 更新的天。
<skipHours> 可选的。规定忽略 feed 更新的小时。
<textInput> 可选的。规定应当与 feed 一同显示的文本输入域。
<title> 必需的。定义频道的标题。
<ttl> 可选的。指定从 feed 源更新此 feed 之前,feed 可被缓存的分钟数。
<webMaster> 可选的。定义此 feed 的 web 管理员的电子邮件地址。

RSS <item>

元素可定义 RSS feed 中的一篇文章或 “story”。

以下三个元素是必须的子元素:

<title> - 定义频道的标题。(比如 w3school 首页)
<link> - 定义到达频道的超链接。(比如 www.w3school.com.cn)
<description> - 描述此频道(比如免费的网站建设教程)
ELEM Desc
<author> 可选的。规定项目作者的电子邮件地址。
<category> 可选的。定义项目所属的一个或多个类别。
<comments> 可选的。允许项目连接到有关此项目的注释(文件)。
<description> 必需的。描述此项目。
<enclosure> 可选的。允许将一个媒体文件导入一个项中。
<guid> 可选的。为项目定义一个唯一的标识符。
<link> 必需的。定义指向此项目的超链接。
<pubDate> 可选的。定义此项目的最后发布日期。
<source> 可选的。为此项目指定一个第三方来源。
<title> 必需的。定义此项目的标题。

实例

将自己的网站生成RSS。

1) 直接通过 网络爬虫扒取自己的网站, 样式未抓取, 懒得做。

此法跳过。

2) 利用 JAR 自己构建。

文章放在本地, 全部为 markdown 编写。

  • 依赖jar

<properties>
    <rsslibj.version>1.0RC2</rsslibj.version>
    <exml.version>7.0</exml.version>
</properties>


<dependencies>
    <dependency>
        <groupId>rsslibj</groupId>
        <artifactId>rsslibj</artifactId>
        <version>${rsslibj.version}</version>
    </dependency>
    <dependency>
        <groupId>exml</groupId>
        <artifactId>exml</artifactId>
        <version>${exml.version}</version>
    </dependency>
</dependencies>
  • 随便写的测试类

注意:

  1. 因为代码的实现比较粗糙, 会导致文章中的 site.url 会被替换成为真正的网络地址(http://houbb.github.io)。所以本文的RSS看起来应该有些错误。

  2. 文章中涉及到的常量替换不必特别关注, 大多是由于jekyll和markdown的特殊性导致的。

/**
 * FileUtil Tester.
 *
 * @author houbinbin
 * @version 1.0
 * @since 02/14/2017
 */
public class RssBuildTest {

    /**
     * 读取此根路径下的所有文件
     */
    private static final String BASE_PATH = "/Users/houbinbin/IT/code/houbb.github.io/_posts";

    /**
     * 获取所有 markdown 文件列表
     * @since 1.7
     * @return
     */
    public static List<String> getAllMdFileList() {
        List<String> fileList = new LinkedList<>();
        Path path = Paths.get(BASE_PATH);

        try(DirectoryStream<Path> pathDirectoryStream = Files.newDirectoryStream(path,
                BlogMDConstant.MD_FILE_PATTERN)) {
            for(Path p : pathDirectoryStream) {
                File file = p.toFile();
                fileList.add(file.getAbsolutePath());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return fileList;
    }

    /**
     * 获取文章正文
     * @param fullContent
     * @return
     */
    public static final String getArticleContent(String fullContent) {
        String result;
        int start = fullContent.indexOf(BlogMDConstant.INDEX_LIST);
        /**
         * 如果有 这个标签
         * 则将目录索引截取掉。
         */
        if(start > 0) {
            result = fullContent.substring(start+BlogMDConstant.INDEX_LIST.length());
        } else {
            int secondIndex = getSecondThreeDashEndIndex(fullContent);  //--- 文章头结尾
            result  = fullContent.substring(secondIndex);
        }

        return result;
    }

    /**
     * 获取第二个 --- 的结束下标。
     * 后面是正文 开始的地方
     * @return
     */
    private static final int getSecondThreeDashEndIndex(String fullContent) {
        int index = fullContent.indexOf(BlogMDConstant.THREE_DASH, BlogMDConstant.THREE_DASH.length());
        return index;
    }


    /**
     * 根据文章URL构建真实的 RSS item 对象
     * @param articleURL 文章的本地URL
     * @return
     * @throws ParseException
     */
    private static Item buildItemForArticleWithJAR(String articleURL) throws ParseException {
        String content = FileUtil.getFileContent(articleURL);


        String title = BlogMDConstant.getForArticleTitle(content, BlogMDConstant.TITLE_PATTERN);
        String pubDate = BlogMDConstant.getForArticleTitle(content, BlogMDConstant.DATE_PATTERN);
        String published = BlogMDConstant.getForArticleTitle(content, BlogMDConstant.PUBLISHED_PATTERN);
        String description = getArticleContent(content);
        description = description.replace(BlogMDConstant.MD_ROOT_PATH, BlogMDConstant.NET_ROOT_PATH);   //替换为真实网络路径
        pubDate = pubDate.replace(BlogMDConstant.ARTICLE_TITLE_APPEND_TIME, BlogMDConstant.EMPTY);  //移除最后的时间添加字符串
        if(!BlogMDConstant.isPublished(published)) {
            return null;
        }

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(BlogMDConstant.DATE_TIME_FORMAT);
        Date date = simpleDateFormat.parse(pubDate);
        String netArticleUrl = buildArticleNetURL(articleURL);
        Item item = new Item();
        item.setTitle(title);
        item.setLink(netArticleUrl);
        item.setDcDate(date);
        item.setDescription(MarkdownUtil.markdownToHtml(description));
        return item;
    }


    /**
     * 根据本地地址构建
     * 1) 网络根地址+URL生成规则构建的URL
     * @param articleURL
     * @return
     */
    private static String buildArticleNetURL(String articleURL) {
        //// TODO: 17/2/15 实现
        return articleURL;
    }

    public static void main(String[] args) throws ParseException, IOException, IllegalAccessException, ClassNotFoundException, InstantiationException {

        Channel channel = new Channel();
        channel.setTitle("Echo blog");
        channel.setLink("https://houbb.github.io/");
        String rssName = "blog.xml";

        List<String> stringList = getAllMdFileList();
        for(String articlePath : stringList) {
            Item item = buildItemForArticleWithJAR(articlePath);
            if(null == item) {
                continue;
            }
            channel.addItem(item);
        }

        File file = new File(rssName);

        if (!file.exists()) {
            file.createNewFile();
        }

        PrintWriter writer = new PrintWriter(new FileWriter(file));
        writer.write(channel.getFeed("rss"));
        writer.flush();
        writer.close();
    }
}

常量类:

/**
 * @author houbinbin
 * @version 1.0
 * @on 17/2/14
 * @since 1.7
 */
public final class BlogMDConstant {

    /**
     * 空字符串
     */
    public static final String EMPTY = "";

    /**
     * 文章题头 后面添加的时间 字符串
     */
    public static final String ARTICLE_TITLE_APPEND_TIME = " +0800";

    public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";

    /**
     * markdown 文件正则匹配
     */
    public static final String MD_FILE_PATTERN = "*.md";

    /**
     * 换行符
     */
    public static final String RETURN_LINE = "\n";

    /**
     * 三个横线
     * 主要是markdown文件头
     */
    public static final String THREE_DASH = "---";

    /**
     * 标题
     */
    public static final String TITLE_PATTERN = "title:";

    /**
     * 日期
     */
    public static final String DATE_PATTERN = "date:";

    /**
     * 是否发布
     */
    public static final String PUBLISHED_PATTERN = "published:";


    /**
     * 文章的内容标题索引
     */
    public static final String INDEX_LIST = "* any list\n" +
            "{:toc}";

    /**
     * markdown 文件中 的默认根路径
     */
    public static final String MD_ROOT_PATH = "";

    /**
     * 网络真实  根路径
     */
    public static final String NET_ROOT_PATH = "http://houbb.github.io";



    /**
     * 当前文章是发布的
     * @param result
     * @return
     */
    public static final boolean isPublished(String result) {
        return "true".equalsIgnoreCase(result);
    }


    /**
     *
     layout: post
     title: RSS
     date:  2017-02-12 00:17:37 +0800
     categories: [Tool]
     tags: [rss]
     published: true
     * 获取文章表头对应的内容
     * 比如: 输入 "date:"
     * 则返回对应的时间字符串。
     * @param content 文章全文
     * @param pattern 对应的表头
     * @return
     */
    public static final String getForArticleTitle(final String content, final String pattern) {

        int startIndex = content.indexOf(pattern);
        int endIndex = content.indexOf(RETURN_LINE, startIndex);
        String lineContent = content.substring(startIndex, endIndex);
        int startSplitIndex = lineContent.indexOf(":")+1;  //时间这一栏
        String result = lineContent.substring(startSplitIndex);
        return result.trim();
    }
}