使用 Bash 脚本解析 RSS 新闻源

作者:Jim Hall
我参与了几个自由软件项目,包括一两个我维护网站的项目。对于其中一个项目,我们目前正在更新网站。我们的项目可能与其他自由软件项目类似。我们使用托管服务来提供几项关键服务,包括新闻,但我们在我们拥有的 Web 服务器上运行我们的网站。在我们的案例中,我们的大部分项目在 SourceForge 上运行,并在第三方服务上运行网站,因此新闻和网站位于不同的系统上。

毫不奇怪,我们的项目使用 RSS 源从 SourceForge 拉取新闻项目,以显示在项目网站上。以前,我们的网站使用 PHP 脚本来缓存 RSS 文件的副本,然后在访问者访问我们首页时解析它。这效果很好,但如果远程新闻服务器速度慢或暂时无法访问,则有些脆弱。

然而,当我们进行网站更新时,我希望改进我们显示新闻项目的方式。我想自动化新闻服务,以下载 RSS 源的副本,解析它,并将新闻项目保存到网站可以直接包含的本地文件中。这降低了网站的复杂性,并且意味着每次首页加载速度都会更快。

最初,我将我们之前的 PHP 网站代码改编成一个可以从命令行运行的 PHP 脚本。它完成了工作,但我认为肯定有更好的方法。我想知道我是否可以使用 Bash 脚本解析 RSS 源——结果证明,你可以!这很简单!

RSS 简要入门

您可能知道 RSS 只是 XML 的一种形式,而 XML 本身是一种简单的标记语言。XML 元素是由标签分隔的值。标签是以小于号 (<) 开头,以大于号 (>) 结尾的任何标记。标签有三种类型

  1. 开始标签,用于标记值的开始(例如 <item>)。

  2. 结束标签,用于标记值的结束(</item>)。

  3. “空元素”标签,可能包含作为属性的值(<feedburner:info uri="linuxjournalcom" />)。

在 RSS 源中,大多数值都由开始和结束标签分隔,例如指向 Linux Journal 的此链接


<link>https://linuxjournal.cn/</link>

RSS 源具有明确定义的格式。在一些描述新闻网站或“频道”的标题信息之后,RSS 源在 <item>...</item> 元素内显示单独的新闻项目。每个新闻项目都包含标题、链接、发布日期、完整描述和其他字段。一个示例 RSS 文件可能如下所示


<xml version="1.0" encoding="UTF-8" ?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
   <channel>
      <title>RSS title</title>
      <link>http://www.example.com/</link>
      <description>RSS description</description>
      <language>en</language>
      <item>
         <title>News item title</title>
         <link>http://www.example.com/link/to/news/item/</link>
         <guid isPermaLink="true">identifier-5f4b02697d2006f72648ebd0d9c6ce96</guid>
         <description>Full news item text.</description>
         <pubDate>Fri, 01 Jul 2016 17:41:07 +0000</pubDate>
      </item>
   </channel>
</rss>

虽然在大多数新闻源中,RSS 数据在传输时没有缩进或换行符。例如,上面的示例 RSS 更可能看起来像这样


<xml version="1.0" encoding="UTF-8" ?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>RSS title</title><link>http://www.example.com/</link><description>RSS
description</description><language>en</language><item><title>News item
title</title><link>http://www.example.com/link/to/news/item/</link><guid
isPermaLink="true">identifier-5f4b02697d2006f72648ebd0d9c6ce96</guid><description>Full
news item text.</description><pubDate>Fri, 01 Jul 2016 17:41:07
+0000</pubDate></item></channel></rss>

当从 RSS 源拉取新闻项目时,我只关心几个元素

  • 新闻项目的标题 (<title>)。

  • 指向完整新闻项目的链接 (<link>)。

  • 新闻项目发布的日期 (<pubDate>)。

  • 新闻项目的全文 (<description>)。

方便的是,所有这些值都用开始和结束标签标记。这是使用 Bash 脚本解析 RSS 源的关键假设。对于 RSS 解析器,我只想抓取被开始和结束标签包围的值。

“Read”语句

您可能不认为 Bash 可以解析数据文件,但它可以,通过一些巧妙的思考。Bash,像之前的其他 UNIX shell 一样,可以通过内置的 read 语句一次从文件中读取一行。

默认情况下,read 语句读取一行数据并将其拆分为字段。通常,read 使用空格和制表符拆分字段,并使用换行符结束每一行,但您可以通过设置内部字段分隔符 (IFS) 值和行尾分隔符来更改此行为。

要使用 read 解析 RSS 源,请将 IFS 设置为大于号 (>),并将分隔符设置为小于号 (<)。每次 Bash 读取一行时,它都会读取到下一个 <(标签的开始),然后在每个 >(标签的结尾)处拆分该数据。此示例代码读取输入并将数据拆分为 TAGVALUE 变量


local IFS='>'
read -d '<' TAG VALUE

让我们探讨一下这是如何工作的。考虑这个简单的输入


<title>RSS title</title>

第一次 read 解析此行时,它会在第一个 < 符号处停止。由于 < 是输入的第一个字符,这意味着 Bash 找到一个空字符串。拆分的 TAGVALUE 字符串也是空的,这可能看起来很奇怪,但这没关系,因为 RSS 解析器无论如何都不会关注空标签。

下次 Bash 读取输入时,它会得到 title>RSS title 并在下一个 < 符号处停止。然后 read 在 > 符号处拆分该行,这将使 TAG 包含 titleVALUE 包含 RSS title

第三次 read 解析输入时,它会读取剩余的文本并在文件末尾停止。Bash 将字符串 /title> 拆分为包含 /titleTAG 和一个空的 VALUE

一个简单的解析器

有了这个 read 魔术,就可以很容易地用 Bash 解析 RSS 文件。让我们从一个简单的 RSS 解析器开始,它显示 < > 之间的标签和 { } 之间的值,这样您就可以看到标签和值的开始和结束位置。首先,您需要一个名为 xmlgetnext 的 Bash 函数来使用 read 解析数据,因为您将在脚本中一次又一次地执行此操作


xmlgetnext () {
   local IFS='>'
   read -d '<' TAG VALUE
}

请注意,您在本地定义 IFS,同时将 TAGVALUE 保留为全局变量。通常,Bash 函数中的变量及其值在函数及其调用者之间共享,因此您只需要将 IFS 声明为本地变量。您希望调用脚本看到 TAGVALUE 的值。

然后,当您解析 RSS 输入文件并打印结果 TAGVALUE 时,您可以使用 while 循环。Bash 脚本非常简洁


#!/bin/sh
xmlgetnext () {
   local IFS='>'
   read -d '<' TAG VALUE
}

cat $1 | while xmlgetnext ; do
   echo "<$TAG>{$VALUE}"
done

现在,让我们使用本文前面示例 RSS 文件。从该输入来看,简单解析器应打印此内容


<>{}
<xml version="1.0" encoding="UTF-8" ?>{}
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">{}
<channel>{}
<title>{RSS title}
</title>{}
<link>{http://www.example.com/}
</link>{}
<description>{RSS description}
</description>{}
<language>{en}
</language>{}
<item>{}
<title>{News item title}
</title>{}
<link>{http://www.example.com/link/to/news/item/}
</link>{}
<guid isPermaLink="true">{identifier-5f4b02697d2006f72648ebd0d9c6ce96}
</guid>{}
<description>{Full news item text.}
</description>{}
<pubDate>{Fri, 01 Jul 2016 17:41:07 +0000}
</pubDate>{}
</item>{}
</channel>{}

解析 RSS 新闻源

请记住,RSS 源在 <item>...</item> 元素内显示单独的新闻项目。此外,每个新闻项目都包含标题、链接、发布日期和完整描述,分别表示为 <title>...</title><link>...</link><pubDate>...</pubDate><description>...</description>。要通过 Bash 脚本解析此内容,您需要查找每个 <title><link><pubDate><description> 标签,然后在找到结束 </item> 标签时编写相关的 html 代码。

大多数网站使用文章标题作为标题显示新闻项目,例如 <h3> 标题,并在文章正文中显示完整文章文本。如果源尚未用 <p><div> 标记包围文章文本,则您需要提供该标记。您可能还希望包含文章的发布日期和指向原始文章的链接以获取更多详细信息。有了这些假设和 xmlgetnext 函数,就可以很容易地在 Bash 中解析 RSS 新闻源


#!/bin/sh
xmlgetnext () {
   local IFS='>'
   read -d '<' TAG VALUE
}

cat $1 | while xmlgetnext ; do
   case $TAG in
      'item')
         title=''
         link=''
         pubDate=''
         description=''
         ;;
      'title')
         title="$VALUE"
         ;;
      'link')
         link="$VALUE"
         ;;
      'pubDate')
         pubDate="$VALUE"
         ;;
      'description')
         description="$VALUE"
         ;;
      '/item')
         cat<<EOF
<article>
<h3><a href="$link">$title</a></h3>
<p>$description
<span class="post-date">posted on $pubDate</span></p>
</article>
EOF
         ;;
      esac
done

当解析器到达第一个 <item> 标签时,脚本将标题、链接、pubDate 和描述变量归零,并在解析器到达下一个 </item> 标签时创建 HTML 代码。在此期间,脚本存储它在 RSS 源文章中找到的标题、链接、pubDate 和描述值。针对示例 RSS 文件运行此脚本,您应该看到此输出


<article>
<h3><a href="http://www.example.com/link/to/news/item/">News item title</a></h3>
<p>Full news item text.
<span class="post-date">posted on Fri, 01 Jul 2016 17:41:07 +0000</span></p>
</article>

您可以根据此脚本进行一些改进。例如,发布日期不太容易阅读。让我们以更人性化的格式显示它,并将日期包含在 <time>...</time> HTML 元素中,以便屏幕阅读器可以更好地理解它。为此,您需要将发布日期转换为 ISO-8601 时间戳,并创建“MM/DD/YY HH:MMam”格式的“显示”日期


#!/bin/sh
xmlgetnext () {
   local IFS='>'
   read -d '<' TAG VALUE
}

cat $1 | while xmlgetnext ; do
   case $TAG in
      'item')
         title=''
         link=''
         pubDate=''
         description=''
         ;;
      'title')
         title="$VALUE"
         ;;
      'link')
         link="$VALUE"
         ;;
      'pubDate')
         # convert pubDate format for <time datetime="">
         datetime=$( date --date "$VALUE" --iso-8601=minutes )
         pubDate=$( date --date "$VALUE" '+%D %H:%M%P' )
         ;;
      'description')
         description="$VALUE"
         ;;
      '/item')
         cat<<EOF
<article>
<h3><a href="$link">$title</a></h3>
<p>$description
<span class="post-date">posted on <time
datetime="$datetime">$pubDate</time></span></p>
</article>
EOF
         ;;
      esac
done

此脚本生成的 HTML 既更容易让人理解,也更容易让屏幕阅读器解释


<article>
<h3><a href="http://www.example.com/link/to/news/item/">News item title</a></h3>
<p>Full news item text.
<span class="post-date">posted on <time datetime="2016-07-01T12:41-05:00">07/01/16
12:41pm</time></span></p>
</article>

如果完整文章文本包含 HTML 标签,RSS 会将 HTML < 和 > 转换为 &lt; 和 &gt;。RSS 解析器需要撤消这些更改。您可以使用 sed 流编辑器通过内联编辑来完成此操作


#!/bin/sh
xmlgetnext () {
   local IFS='>'
   read -d '<' TAG VALUE
}

cat $1 | while xmlgetnext ; do
   case $TAG in
      'item')
         title=''
         link=''
         pubDate=''
         description=''
         ;;
      'title')
         title="$VALUE"
         ;;
      'link')
         link="$VALUE"
         ;;
      'pubDate')
         # convert pubDate format for <time datetime="">
         datetime=$( date --date "$VALUE" --iso-8601=minutes )
         pubDate=$( date --date "$VALUE" '+%D %H:%M%P' )
         ;;
      'description')
         # convert '&lt;' and '&gt;' to '<' and '>'
         description=$( echo "$VALUE" | sed -e 's/&lt;/</g' -e 's/&gt;/>/g' )
         ;;
      '/item')
         cat<<EOF
<article>
<h3><a href="$link">$title</a></h3>
<p>$description
<span class="post-date">posted on <time
datetime="$datetime">$pubDate</time></span></p>
</article>
EOF
         ;;
      esac
done

有了这个,我就拥有了更新项目网站所需的一切,以便它可以更高效


wget --quiet -O $rsslocalfile $rssfeedurl
./parse.sh $rsslocalfile > $newslocalfile.html

现在我可以自动化这个每小时的任务,以检索 RSS 源的副本,解析它,并将新闻项目保存到网站可以合并的本地文件中。这降低了网站的复杂性,只需通过使用 Bash 脚本解析 RSS 新闻源来完成少量额外工作。

Jim Hall 是一位开源软件倡导者和开发人员,可能最出名的是 FreeDOS 的创始人。Jim 在 GNOME 等开源软件项目的可用性测试中也非常活跃。在工作中,Jim 是 Hallmentum 的 CEO,这是一家 IT 执行咨询公司,帮助 CIO 和 IT 领导者进行战略规划和组织发展。

加载 Disqus 评论