使用 PHP 构建可定制的 RSS 提要聚合器

领略 RSS 在 Ajax 和 Web 2.0 应用程序中的强大功能

RSS(Rich Site Summary、RDF Site Summary 或 Really Simple Syndication)出现于 20 世纪 90 年代中期。自那之后,先后涌现了多种格式的 RSS,而且其中有几个还声明了所有权归属。尽管格式上存在差异,但 RSS 在将 Web 内容由一个站点分发到多个其他站点方面一直发挥着其有效性。RSS 的流行促进了一类新 Web 软件(称为 提要阅读器,有时也被称为 提要聚合器)的发展。虽然现在已经有几个商业的提要聚合器可用,但开发自己的提要聚合器并将其集成到自己的 Web 应用程序中也很容易。您会发觉本文中给出的这些功能完备的 PHP 代码片段很有用,它们展示了如何使用基于 PHP 的服务器端函数来开发一个可定制的 RSS 提要聚合器。此外,您可以从本文下载获得这些功能完备的 RSS 提要聚合器代码,使用这些代码,您能够立即从中获益。

Senthil Nathan (sen@us.ibm.com), 高级软件工程师, IBM

Photo of Senthil NathanSenthil Nathan 是位于纽约 Hawthorne 的 IBM T.J. Watson Research Center 的一位高级软件工程师。在为不同类型的企业应用程序构建软件方面,他有 22 年经验。他当前感兴趣的领域包括 SOA、Web 服务、Java 2 Platform, Enterprise Edition(J2EE)、PHP、Ruby On Rails、Web 2.0 和 Ajax 开发。



2008 年 2 月 18 日

诸如 Ajax 和 Web 2.0 这样的技术推动了 Web 应用程序的迅速发展,所以应用程序内容的价值就显得异常重要和关键。内容提供者和内容使用者都需要有效的方式来分别发布/连锁和订阅/阅读内容。正是 RSS 的简单性和强大功能才让这种 Web 1.0 时代的技术成为了影响 Web 2.0 乃至其后时代的重要因素。

RSS 为内容提供者提供了一种广泛接受的优雅格式来编码和连锁对其站点内容的周期性更改。内容提供者然后使这些内容对整个 Web 或子 Web 可用,比如公司内的内部连锁。与之相对的是内容使用者,内容使用者常常要在一个站点和其他站点之间跳转和进行页面过滤才能找到所需信息。RSS 提要通过允许用户接收整块的所选类别的信息来消除这类页面跳转。此外,RSS 提要还让用户能够从任意数量的内容提供者处获得所需类别信息的要点。

正如之前提到的,RSS 有两个部分:连锁和提要聚合。RSS 提要对于用户而言的主要好处是其提要聚合能力 — 本文的重点所在。本文将详细介绍如何开发一个能适合内容使用者特定需求的可定制的 RSS 提要聚合器。

要求具备 XML 和 PHP 经验

本文假设您具备基本的 XML 和 PHP 的使用经验和熟悉程度。如果您不具备此方面的知识,请查阅 developerWorks XML 专区developerWorks PHP 专区,这两个专区可以帮您将本文所介绍的内容提升至应用程序级别。

简介

由于基于 Web 的信息资源的深入普及,内容提供者和内容使用者都面临着几个挑战。对于内容提供者而言,最主要的挑战是如何能在众多的竞争对手中脱颖而出,吸引尽量多的用户。相反,对于内容使用者来讲,最大的困难则在于如何能够在众多内容提供者所提供的诸多类别中找到所需的信息。此外,内容使用者还要能够排除混淆所找内容的各种干扰(比如动画图片和 flash 广告)。

由于 Web 在人们生活中的普及,开发人员必须要侧重于帮助用户最快地访问信息并避免围绕所需内容出现任何混乱。内容提供者还需要将动态内容定向给几个不同用户媒介(从 Web 浏览器到手持移动设备)。正如在本文开始所概括的,RSS 减轻了连锁和聚合内容方面的很多负担。如果对 RSS 还不是很熟悉,可用阅读本文 参考资料 部分给出的与 RSS 相关的文章。

RSS 为 Internet 带来了诸多潜力,它可以以多种不同(甚至是未曾想到过的)方式加以使用。一些主要的软件公司的执行官最近都将公司核心的 Internet 战略调整到了发掘 RSS 的能力。有关讨论,请查阅 参考资料 部分中 Web 2.0 Journal 和 AjaxWorld Magazine 的名誉主编 Dion Hinchcliffe 精练而准确的见解。借助 图 1,Hinchcliffe 先生肯定了 RSS 对 Web 2.0 信息生态系统的重要作用。所有主要的 Web 2.0 构建块,比如 wiki、blog、新闻服务、聚合服务、mashup 服务和搜索引擎都使用 RSS 作为连接它们的 “纽带” 以实现 Web 2.0 社会计算的愿景:

图 1. Hinchcliffe 的观点:RSS 在 Web 2.0 中的作用
RSS 在 Web 2.0 中的作用,已获得使用许可

RSS 可用的格式有几个版本。RSS 版本 0.91、0.92 和 2.0 都遵循着一种特殊的 XML 格式,RSS 提要使用这种格式描述。RSS 版本 1.0 使用了与之稍微不同的 XML 格式。这一细微的差异在开发 RSS 提要聚合器时也应考虑进去。在 参考资料 部分所引用的一篇文章中可以找到有关 RSS 历史的有趣介绍。

理解了 RSS 格式的重要性和有效性之后,让我们来深入研究如何开发一个可定制的 RSS 提要聚合器。我们将使用 PHP 语言来开发我们的 RSS 提要聚合器。PHP 提供了几个内置的 Web 和 XML 函数,可以加速开发。此外,用 PHP 编写的代码可以非常容易地运行于 PHP 服务器,比如 Zend Framework 中内置的 Zend Core。要开发提要聚合器,需要安装 PHP 并用 cURL(Client URL)和 XML 包进行配置。我建议您安装 Zend Core PHP 服务器,该服务器可免费获得,而且很容易安装和配置。

让我们先简单介绍一些 RSS 格式的基础知识。

RSS 基础知识

包括 Apple、Microsoft®、Netscape、Userland 和 RSS-DEV 工作组在内的多个公司和组织都对 RSS 今天的发展做出了贡献。由于涉及了很多不同的公司,所以 RSS 格式的版本也多种多样。尤其值得一提的是,RSS 版本 1.0(由 RSS-DEV 开发)与其他版本(0.91、0.92 和 2.0,均共享了一种后向兼容的格式)稍有不同。

清单 1 给出了 RSS 版本 1.0 的高层格式。有关此格式需要特别注意的一点是称为 <rdf> 的根元素,此根元素有一个 <channel> 元素和一个或多个 <item> 元素作为子元素。请注意,在这个格式中,<channel><item> 元素是兄弟元素。

清单 1. RSS 版本 1.0 的骨架格式
<rdf>
     <channel>
          .....
          .....
     </channel>

     <item>
          <title>My Title</title>
          <link>My URL</link>
          <description>My Description</description>
          .....
          .....
     </item>
</rdf>

清单 2 所展示的是 RSS 版本 0.91、0.92 和 2.0 的高层格式。请注意在这种格式中,根元素称为 <rss>,而不是 <rdf>。根元素 <rss> 只有一个子元素,称为 <channel>,而所有 <item> 元素均是 <channel> 元素的子元素。这一点与 RSS 1.0 明显不同。这一细微差异应该在编写解析这些不同的 RSS 格式的代码时考虑进去。对于本篇文章而言,我们感兴趣的是 <item> 元素下的三个子元素。这三个子元素在所有 RSS 格式中都是一致的,这一点很好。

清单 2. RSS 版本 0.91 和 2.0 的骨架结构
<rss>
     <channel>
          .....
          .....

          <item>
               <title>My Title</title>
               <link>My URL</link>
               <description>My Description</description>
               .....
               .....
          </item>
     </channel>
</rss>

有关 RSS 规范的详细信息,请参阅 参考资料 部分给出的相关链接。本文后续的章节将会介绍 RSS 提要聚合器中的各个组件。

功能性组件

本文中所介绍的 RSS 函数由如下的组件构成,稍后会逐一加以解释:

  • 提要阅读器
  • 提要源输入
  • 提要聚合器
  • 提要结果输出

这些简单的组件结合在一起就提供了功能强大的 RSS 提要聚合器函数,此函数可以以多种方式集成到其他的应用程序中。在后续章节,我们将深入探索组成这些组件的各个 PHP 代码片段。图 2 展示了这些功能性组件的概览:

图 2. RSS 功能性组件的概览
RSS 功能性组件的概览

观察图 2 中展示的如下内容:

  • 提要阅读器组件完成大部分的工作并着重于获得由给定提要源提供的提要。提要源 只不过是一种 URL,在这个 URL,特定的内容提供者定期地为给定的信息类别连锁内容。比如,提要源可能指向这样一个 URL,通过该 URL,纽约时报使用基于 XML 的 RSS 格式发布其最新的商业类别/频道的新闻广告。
  • 提要聚合器组件接受几个特定于用户的提要源作为输入,然后它会调用提要阅读器组件来从每个定制的提要源获得所有的提要条目。
  • 提要源输入组件定义和读取特定于用户的提要源的有关细节。提要源细节可以通过存储在系统内存的字符串的形式,借助一个输入文件或作为数据库中的记录提供。
  • 提要结果输出组件存储由某个提要源收到的聚合了的 RSS 提要条目结果。它可以将结果作为系统内存中的字符串存储到文件或数据库表内。

为了简化我们的工作,我们将结合提要源输入和提要结果输出组件,将它们作为提要聚合器的组成部分(您可以从本文的 下载 部分下载完整归档的一组 PHP 源文件)。您可以将这些文件解压缩到您的机器并用自己的一组定制 RSS 提要源运行这些文件。正如在前一节中介绍的那样,要想实现这些操作,先决条件是必须要具备带 XML 和 cURL 库支持的 PHP 环境(参考 结束语 部分以获得将这些代码用于 RSS 提要聚合器的多种方式)。

我们现在就来看看上述各个组件内部的工作原理。

提要阅读器

提要阅读器组件是用提要源进行必须的网络通信以及解析由提要源返回的基于 XML 的 RSS 提要的核心引擎。它主要使用如下三个 PHP 环境的简单、但却功能强大的特性:

  • cURL
  • SimpleXML
  • PHP 数组

cURL 是一种开源库,允许 PHP 程序使用不同的协议,比如 HTTP、FTP、Telnet 等连接到远端服务器。它提供了一组包装器函数集合以执行各种动作,比如 HTTP POST、HTTP GET 和 HTTP PUT。要使用 cURL 函数,必须启用 PHP 环境中的 libcurl 包。PHP 服务器,比如 Zend Core,可经由 Zero Core 管理程序通过一次鼠标单击来启用 cURL。

SimpleXML 是 PHP 的另一扩展。由其名字可以看出,这种扩展可以简化 XML 的处理,不论是从 XML 文档读取还是向 XML 文档写入,都是如此。在 PHP 版本 5 或之后的版本,该扩展都会默认启用。

数组是基础 PHP 语言的一部分,并可简化对集合数据结构的处理。我们将使用 PHP 数组来收集 RSS 提要条目结果。

此提要阅读器组件的源代码在一个称为 rss_feed_reader.php 的 PHP 文件内。它由如下三个定制函数组成:

  • get_rss_feeds
  • perform_curl_operation
  • parse_rss_feed_xml

随后的章节会给出有关这三个函数代码的细节。此外,源代码还配有详尽的文档解释以帮助您理解其逻辑。

清单 3 所示,get_rss_feeds 是主要的业务逻辑函数,可从 提要聚合器组件调用。该函数接受如下的三个引用值作为来自调用者的输入:

  • RSS 提供者名称
  • RSS 提供者 URL
  • 调用者想要从给定的提要提供者处获得的 RSS 提要条目的最大数量

此函数调用另一个函数来执行 cURL 网络操作,即从此提要提供者的远端 URL 获取基于 XML 的 RSS 提要内容。然后创建三个数组,分别存放三个元素(title、link、description)各自的集合,我们将会从所收到的 RSS 提要内容的每个提要条目中收集到这些信息。您可能还记得,清单 1 中显示了它们都是所收到的 RSS 提要内容中的 <item> 元素的子元素。然后,该函数将所收到的 RSS 内容字符串和这三个数组传递给另一个函数,此函数解析 XML 内容并收集 RSS 提要条目。如果解析 RSS 提要内容的结果为 false,就意味着在所收到的 RSS 提要内容中存在错误。在发生错误的情况下,此函数会向调用者返回一个空数组。若解析结果为 true,就意味着 提要条目被成功解析而且三个数组现在包含有所收到的每个提要条目的标题、URL 和描述。之后,此函数会创建一个新的结果数组以返回给调用者。在结果数组的前 5 个索引中,此函数分别存储了提供者名称、收到的提要条目的总数、标题数组、URL 数组和描述数组。最后,它向调用者返回这个结果数组。

清单 3. get_rss_feeds 函数
function get_rss_feeds(& $rss_provider_name, & $rss_provider_url,
   & $max_rss_items_required) {	
   // Check if the max_rss_items_required is 0
   if ($max_rss_items_required <= 0) {
      // Return an empty array.
      $empty_array = array();
      return($empty_array); 			
   } // End of if ($max_rss_items_required <= 0)
	
   // Let us go ahead and fetch the RSS contents from the given RSS provider.
   $received_rss_feeds = perform_curl_operation($rss_provider_url);
	
   // Is it empty?
   if (empty($received_rss_feeds)) {
      // Return an empty array.
      $empty_array = array();
      return($empty_array); 	
   } // End of if (empty($received_rss_feeds))
	
   // We have a non-empty result from the RSS feed provider.
   // Create three empty arrays to hold the values from the received rss feed items.
   $rss_feed_title_array = array();
   $rss_feed_url_array = array();
   $rss_feed_description_array = array();

   // We can now parse the individual RSS feed items.
   $parser_result = parse_rss_feed_xml($received_rss_feeds, $max_rss_items_required,
      $rss_feed_title_array, $rss_feed_url_array, $rss_feed_description_array);
	
   // Check if we were able to parse the RSS feed XML content.
   if ($parser_result == true) {
      // We have successfully parsed the RSS feed results.
      // Create an array and fill it with the results as 
      // described in the function description comments above.
      $result_array = array();
      // Send the rss provider name.
      $result_array[0] = $rss_provider_name;
      // Tell how many rss feed items are being returned. 
      $result_array[1] = sizeof($rss_feed_title_array);
      // Send the array containing different RSS feed titles.
      $result_array[2] = $rss_feed_title_array;
      // Send the array containing different RSS feed URLs.
      $result_array[3] = $rss_feed_url_array;
      // Send the array containing different RSS feed descriptions.
      $result_array[4] = $rss_feed_description_array;
      // Return the result array now.
      return($result_array);		
   } else {
      // We were not successful in parsing the RSS feed items.
      // Return an empty array as the result.
      $empty_array = array();
      return($empty_array); 			
   } // End of if ($parser_result == true)
} // End of function get_rss_feeds

清单 4 所示,perform_curl_operation 函数执行一个 HTTP GET 操作来获得给定的远端 URL 的内容;幸而有 PHP cURL 库,才可以如此优美地实现这一功能。此函数接受被引用的远端 URL 值作为来自调用者的输入参数。在我们的示例中,调用者是 get_rss_feeds 函数,该函数我们之前已经介绍过了。perform_curl_operation 函数首先初始化一个新的 cURL 会话。然后设置各种 cURL 选项,比如远端 URL、不在响应内包括 HTTP 报头、如果出现位置 HTTP 报头就遵循此位置以及指导 cURL 从 curl_exec 函数以字符串形式返回 HTTP 响应。然后,它调用 curl_exec 函数,该函数会连接到远端 URL 并获得此时可用的 RSS 提要内容。在执行这个网络行为期间,curl_exec 函数会一直阻塞直到 HTTP 操作完成。然后,它关闭此 cURL 会话并向调用者返回所收到的 RSS 提要内容。

清单 4. perform_curl_operation 函数
function perform_curl_operation(& $remote_url) {
   $remote_contents = "";
   $empty_contents = "";

   // Initialize a cURL session and get a handle.
   $curl_handle = curl_init();

   // Do we have a cURL session?
   if ($curl_handle) {
      // Set the required CURL options that we need.
      // Set the URL option.
      curl_setopt($curl_handle, CURLOPT_URL, $remote_url);
      // Set the HEADER option. We don't want the HTTP headers in the output.
      curl_setopt($curl_handle, CURLOPT_HEADER, false);
      // Set the FOLLOWLOCATION option. We will follow if location header is present.
      curl_setopt($curl_handle, CURLOPT_FOLLOWLOCATION, true);
      // Instead of using WRITEFUNCTION callbacks, we are going to receive the
      // remote contents as a return value for the curl_exec function.
      curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, true);

      // Try to fetch the remote URL contents.
      // This function will block until the contents are received.
      $remote_contents = curl_exec($curl_handle);
      // Do the cleanup of CURL.
      curl_close($curl_handle);
      
      // Check the CURL result now.
      if ($remote_contents != false) {
         return($remote_contents);
      } else {
         return($empty_contents);
      } // End of if ($remote_contents != false)  	
   } else {
      // Unable to initialize cURL.
      // Without it, we can't do much here.
      return($empty_contents);
   } // End of if ($curl_handle)
} // End of function perform_curl_operation

清单 5 所示,parse_rss_feed_xml 函数负责从所收到的 RSS 提要内容中获得各个 提要条目。该函数接受所有其输入参数作为引用值。其输入参数包括所收到的 RSS 提要内容字符串、用户希望收到的提要条目的最大数量以及三个数组,所有提要条目的标题、URL 和描述都将在其中返回给调用者。如果还没有体验过 PHP SimpleXML 扩展的简单性,那么您将有机会在这个函数中感受一下。与在其他范型中使用的复杂的 XML 解析技术不同,PHP 中的 SimpleXML 让您可以将 XML 结构作为 PHP 对象结构进行处理。

第一步是加载所收到的 RSS 提要内容字符串并获得对等的对象结构。在开始解析之前,我们必须确定收到的 RSS 提要内容使用的编码方式,是 RSS 版本 1.0 还是任何其他的 RSS 版本。可实现此目的的一种方法是考察 <item> 元素是根元素的子元素还是 <channel> 元素的子元素。可以参考 RSS 基础知识 一节来回顾各种 RSS 不同版本间的细微格式差别。确定了 RSS 格式版本以及所收到的 XML 内容的有效性之后,它会遍历所有的 <item> 元素(本例中实际上是 PHP 对象)并解析 title、link 和 description 字段。这三个值中的每个值都将被添加到其各自的数组中,这些数组作为引用的输入参数传递给 parse_rss_feed_xml 函数。当遍历完所需的最大数量的 RSS 提要条目后,该函数会返回 true。若它不能解析其中的任意一个 RSS 提要条目,该函数则会返回 false。

清单 5. parse_rss_feed_xml 函数
function parse_rss_feed_xml(& $received_rss_feeds, 
   & $max_rss_items_required, & $rss_feed_title_array, 
   & $rss_feed_url_array, & $rss_feed_description_array) {
   /*
   We will tap into the elegance of the PHP SimpleXML API to
   parse these RSS feeds encoded in XML format.

   There are multiple versions of RSS out there namely 0.91, 0.92, 1.0 and 2.0
   The basic difference between these versions comes down to one of the 
   following two formats.
    
   1) <rss><channel><item>...</item><item>...</item><item>...</item></channel></rss>
   2) <rdf><channel>...</channel><item>...</item><item>...</item><item>...</item></rdf>
    
   In format 1, <item> elements are the children of the <channel> element.
   In format 2, <item> elements are direct children of the root element <rss> or <rdf>.
   In other words, in format 2, <item> elements are siblings of the <channel> element. 
   RSS version 1.0 uses format 2, whereas all the other versions follow format 1.
   In both these formats, we are interested only in the children between 
   <item>...</item>.
   Our parsing logic here should handle both of these formats.	
   */	
   // To begin with load the XML string to get a SimpleXML object representation.
   $xml = simplexml_load_string($received_rss_feeds);

   // Is it a valid XML document.
   if ((is_object($xml) == false) || (sizeof($xml) <= 0)) {	
      // XML parsing error. Return now.
      return(false);
   } // End of if ((is_object($xml) == false) ...
	
   // Now we have to determine, if we have the <item> elements as the 
   // children of the <channel> element i.e. Format 1 above or
   // if we have the <item> elements as the direct children of the 
   // <rss> or <rdf> root element i.e. Format 2 above.
   $obj1 = $xml->item;
	
   if ((is_object($obj1) == false) || (sizeof($obj1) <= 0)) {
      // <item> elements are not direct children of the document root element.
      // In that case, it is not format 2. It should be as in format 1.
      // Move to the <channel> element so that will be our new root.
      $xml = $xml->channel;
   } // End of if ((is_object($obj1) == false) ...
	
   // Check for XML validity one more time from we can parse this.
   if ((is_object($xml) == false) || (sizeof($xml) <= 0)) {
      // XML parsing error. Return now.
      return(false);
   } // End of if ((is_object($xml) == false) ...
	
   // Initialize a variable to count the <item> elements retrieved.
   $count_of_rss_items_retrieved = 0;
	
   // Stay in a loop and collect the details from the <item> elements.
   foreach ($xml->item as $item) {
      // At this stage, we have access to the <item> elements one at a time.
      // We don't know how many <item> elements are there. 
      // Let us read the title, link and description elements.
      $rss_feed_title = trim(strval($item->title));
      $rss_feed_url = trim(strval($item->link));
      $rss_feed_description = trim(strval($item->description));
      // Let us now add these values to the array references we have.
      array_push($rss_feed_title_array, $rss_feed_title);
      array_push($rss_feed_url_array, $rss_feed_url);
      array_push($rss_feed_description_array, $rss_feed_description);
      // We have to filter out specific number of <item> elements 
      // as required by the user. Let us try to do that now.		
      $count_of_rss_items_retrieved++;    	
      
      if ($count_of_rss_items_retrieved >= $max_rss_items_required) {
	      // Exit from this loop now.
	      break;
      } // End of if ($count_of_rss_items_retrieved >= $max_rss_items_required)   	
   } // End of foreach ($xml->item as $item)
	
   if ($count_of_rss_items_retrieved > 0) {
      // At last, it turned out to be fruitful.
      return(true);
   } else {
      // All the hard work didn't yield anything.
      // Better luck next time.
      return(false);
   } // End of if ($count_of_rss_items_retrieved > 0)	
} // End of function parse_rss_feed_xml

至此,我们已经介绍了所有主要由提要阅读器组件完成的任务;现在让我们来看看提要源输入组件。

提要源输入

提要源输入是这个系统中最简单的组件。它的工作就是获取用户已定制的提要源清单。此组件有一个函数,在程序调用的最早阶段由提要聚合器调用。我们在本文的一开始就提到过,提要源信息可以在程序数据结构中、数据库表中或一个文件中指定。在我们的示例中,我们希望能以一个 XML 文件的格式提供提要源。提要源输入组件的逻辑只是简单地从文件中读取,而文件的名字则以函数输入参数的形式传递。它读取 XML 文件内容并向调用者返回一个字符串。我们在前面提到过,这个组件与提要聚合器组件一起被放在同一个源文件中(rss_feed_aggregator.php)。清单 6 显示了读取提要源输入文件内容所涉及到的简单逻辑:

清单 6. get_list_of_rss_feed_sources 函数
function get_list_of_rss_feed_sources($input_xml_file) {
   //Read the XML contents from the input file.
   file_exists($input_xml_file) or die("Could not find file " . $input_xml_file);
   $xml_string_contents = file_get_contents($input_xml_file); 
   // Return the XML contents now to the caller.
   return($xml_string_contents);
} // End of function get_list_of_rss_feed_sources

提要源输入文件的内容包括一个或多个 XML 元素,这些元素提供了有关提要提供者名字、提要提供者的 URL 以及用户想从提要提供者处收到的 RSS 提要条目的最大数量的信息。清单 7 显示了这个提要源输入 XML 文件的格式:

清单 7. 提要源输入 XML 文件格式
<?xml version="1.0" encoding="UTF-8"?>
<ListOfRssFeedSources>
   <!-- This is the data set for RSS Feed Provider 1 -->
   <RssFeedSourceInfo>
      <rssFeedProviderName>Barron's: Markets</rssFeedProviderName>
      <rssFeedProviderUrl>
         http://online.barrons.com/xml/rss/3_7517.xml
      </rssFeedProviderUrl>
      <maximumRssItemsToBeReturned>5</maximumRssItemsToBeReturned>
   </RssFeedSourceInfo>

   <!-- There can be more RSS Feed Provider elements defined here. -->
</ListOfRssFeedSources>

提要聚合器

提要聚合器是一个包装器组件,用来包装提要阅读器组件以实现本文的主要目标:创建一个可定制的 RSS 提要聚合函数。它使用了 SimpleXML PHP 扩展,该扩展在本文前面的部分讨论过。此外,它还使用了一套定制逻辑来实现 RSS 提要的聚合。PHP 文件 rss_feed_aggregator.php 中有此组件的源代码。

清单 8 中所示,提要聚合器组件有一个名为 aggregate_rss_feeds 的函数。它接受输入文件名称作为函数参数,在该文件中指定了关于 RSS 提要源的细节。如果在没有输入参数的情况下被调用,它将使用一个名为 rss_feed_sources.xml 的默认文件名。首先,它调用提要源输入组件以获得字符串格式的 XML 结构,有关 RSS 提要源的细节在该结构中指定。然后,它用 SimpleXML 扩展将字符串格式的 XML 内容转换成一个 PHP 对象。接下来,它将遍历我们已有的所有提要源并检索提要提供者的名称、提要提供者的 URL 和用户想从提要提供者处收到 RSS 提要条目的最大数量。然后,它调用名为 get_rss_feeds 的一个提要阅读器组件函数,该函数在前面的部分也做过解释。如果从提供者那里获得了一个成功的 RSS 提要条目的结果,那么它就会调用提要结果输出组件。一旦对所有这些提要资源进行了遍历,此函数会在结束时输出一份提要聚合活动的摘要。

清单 8. aggregate_rss_feeds 函数
function aggregate_rss_feeds($input_xml_file = RSS_FEED_SOURCES_FILE_NAME) {
   // Declare a variable to track the current 
   // RSS feed source being processed.
   $feed_source_sequence_number = 0;
   // Let us get the list of RSS feed sources.
   // In our case, we will read them from an input file.
   $xml_string_contents = get_list_of_rss_feed_sources($input_xml_file);

   /*
   We will tap into the elegance of the PHP SimpleXML API to
   parse these RSS feeds encoded in XML format.	
   */
   // To begin with, load the XML string to get a SimpleXML object representation.
   $xml = simplexml_load_string($xml_string_contents);

   // Is it a valid XML document.
   if ($xml == false) {
      print ("Sorry. Your RSS feed sources input file contains invalid data.\n");
      // XML parsing error. Return now.
      return;
   } // End of if ($xml == false)	
	
   print ("\n");
	
   /*
   Stay in a loop and get the RSS feeds from each source.
   The document root element of the input xml file is <ListOfRssFeedSources>
   Under the root element, we will have one or more blocks of data with the
   following format.

   <RssFeedSourceInfo>
      <rssFeedProviderName>....</rssFeedProviderName>
      <rssFeedProviderUrl>....</rssFeedProviderUrl>
      <maximumRssItemsToBeReturned>....</maximumRssItemsToBeReturned>
   </RssFeedSourceInfo>	

   We are going to iterate over all the <RssFeedSourceInfo> elements.
   */
   foreach ($xml->RssFeedSourceInfo as $feed_source) {
      // Read the details about the next feed source from the input file.
      $feed_source_sequence_number++;		
      $rss_provider_name = trim(strval($feed_source->rssFeedProviderName));
      $rss_provider_url = trim(strval($feed_source->rssFeedProviderUrl));
      $max_rss_items_required =
         trim(strval($feed_source->maximumRssItemsToBeReturned));
      print ("Getting RSS feeds from $rss_provider_name ...\n");
		
      // Go and get the RSS feeds now from this feed source.
      $rss_feeds_result_array = 
         get_rss_feeds($rss_provider_name, $rss_provider_url, $max_rss_items_required);
			
      if (empty($rss_feeds_result_array) == false) {
         // We will store only if we receive one or more RSS feed results.
         // The result array format is explained in the store function called below.
         store_rss_feed_results($feed_source_sequence_number, 
            $rss_feeds_result_array);
      } // End of if (empty($rss_feeds_result_array) == false)
   } // End of foreach ($xml->RssFeedSourceInfo as $feed_source)

   print ("\nFinished getting RSS feeds from $feed_source_sequence_number " .
      "feed sources.\n\n");
   print ("You can view the received feed items in the .\feed_results directory.\n\n");
   print ("Feeds from each active feed source are stored in separate files.\n\n");
   print ("These files are named NNN_rss_feed_items.txt, where NNN corresponds to\n" .
      "the sequence number of the order in which the feed source is\n" .
      "listed in your $input_xml_file file.\n");
} // End of function aggregate_rss_feeds

提要结果输出

提要结果输出组件用来存储提要结果。此组件有一个函数,当所有 RSS 提要条目均被从收到的 RSS 提要 内容中解析时,提要聚合器将会调用这个函数。在本文的一开始我们曾讨论过,可以将 RSS 提要结果发送到浏览器或其他独立程序,也可以将其储存到程序数据结构、数据库表或文件中。在本示例中,我们会把从每个 RSS 提要提供者处收到的提要结果存储到各自的文件中。所有结果文件都将存储在名为 feed_results 的子目录下。这个子目录将在其中运行提要聚合器程序的目录下自动创建。

清单 9 中所示,名为 store_rss_feed_results 的这个提要结果输出组件的惟一函数有两个函数输入参数。第一个参数是一个文件序列号,用来组成结果文件名。结果文件名的格式是 NNN_rss_feed_items.txt,其中的 NNN 将会被第一个函数参数值所取代。第二个参数是一个 PHP 嵌套数组,包含从某个特定 RSS 提要提供者处收到的所有 RSS 提要条目。这个结果数组将有 5 个元素并且每个数组元素都将包括以下所列值的其中一个:

a[0] = RSS 提要提供者的名称
a[1] = 从提要提供者处收到的提要条目的数量
a[2] = (rss_feed_title_array) RSS 提要条目标题的数组
a[3] = (rss_feed_url_array) RSS 提要条目 URL 的数组
a[4] = (rss_feed_description_array) RSS 提要条目描述的数组

三个数组 a[2]、a[3] 和 a[4] 的每个索引中的内容综合在一起就能提供关于所收到的 RSS XML 中某个特定 RSS 提要条目的所有信息。例如,rss_feed_title_array[0]、rss_feed_url_array[0] 和 rss_feed_description_array[0] 这三者组合起来就对应了所收到的 RSS XML 内容中的第一个 RSS 提要条目。

首先,如果 feed_results 子目录尚不存在,此函数将会创建一个。接下来,它要为给定的 RSS 提要提供者创建一个具有惟一名称的文件。然后,此函数会遍历结果数组并会把结果数组中所有提要条目的 RSS 提要条目细节都写入该文件。至此,您应当已经很熟悉了,一个 RSS 提要条目应该包括提要的标题、提要的 URL 和关于这个提要的简单描述。对于这些所收到的 提要条目,内容使用者可以快速地浏览某个选定的提要频道的标题和描述。对于所选定的几个感兴趣的提要条目,还可以链接到其 URL 以获得有关该具体提要的所有信息。这就是使用 RSS 提要聚合的好处所在。

清单 9. store_rss_feed_results 函数
function store_rss_feed_results($file_sequence_number, $result_array) {
   // Let us first check if a subdirectory named "feed_results" exists.
   if (file_exists(RSS_FEED_RESULTS_DIRECTORY) == false) {
      // Directory doesn't exist. Create it now.
      mkdir(RSS_FEED_RESULTS_DIRECTORY);		
   } // End of if (file_exists(RSS_FEED_RESULTS_DIRECTORY) == false)
	
   // Form the file name.
   $result_file_name = sprintf("%s/%03d%s", RSS_FEED_RESULTS_DIRECTORY,
      $file_sequence_number, RSS_FEED_RESULTS_FILE_NAME_SUFFIX);
	
   // If this file already exists from previous runs, simply delete it.
   // We will overwrite it with the latest feed data.
   if (file_exists($result_file_name) == true) {
      unlink($result_file_name);
   }

   // Create and open the file.
   $handle = fopen($result_file_name, FILE_CREATE_WRITE_FLAG);
	
   if ($handle == false) {
      // File creation failed. Return now.
      return;
   } 
	
   // We can start writing the received RSS feeds into this file.
   // Write the Feed provider sequence number.
   $feed_provider_number = FEED_PROVIDER_SEQUENCE_NUMBER . 
      $file_sequence_number . NEW_LINE;
   fwrite($handle, $feed_provider_number);
   // Write the Feed provider name.
   $feed_provider_name = RSS_FEED_PROVIDER_NAME . $result_array[0] . NEW_LINE;
   fwrite($handle, $feed_provider_name);
   // Write the number of feed items received.
   $number_of_received_rss_feeds = RECEIVED_RSS_FEEDS_CNT . 
      $result_array[1] . NEW_LINE;
   fwrite($handle, $number_of_received_rss_feeds);

   $rss_feed_title_array = $result_array[2];
   $rss_feed_url_array = $result_array[3];
   $rss_feed_description_array = $result_array[4];

   // Stay in a loop and write the title, URL and Description.
   for($cnt=0; $cnt < sizeof($rss_feed_title_array); $cnt++) {
      $feed_item_separator = FEED_ITEM_SEPARATOR_LINE . NEW_LINE;
      fwrite($handle, $feed_item_separator);
      $feed_item_sequence_number = FEED_ITEM_SEQUENCE_NUMBER . 
         ($cnt+1) . NEW_LINE;
      fwrite($handle, $feed_item_sequence_number);
      $feed_item_title = FEED_ITEM_TITLE . 
         $rss_feed_title_array[$cnt] . NEW_LINE;
      fwrite($handle, $feed_item_title);
      $feed_item_url = FEED_ITEM_URL .
         $rss_feed_url_array[$cnt] . NEW_LINE;
      fwrite($handle, $feed_item_url);
      $feed_item_description = FEED_ITEM_DESCRIPTION . NEW_LINE .
         $rss_feed_description_array[$cnt] . NEW_LINE;
      fwrite($handle, $feed_item_description);		
   } // End of for($cnt=0; $cnt < sizeof($rss_feed_title_array), $cnt++)

   $feed_item_separator = FEED_ITEM_SEPARATOR_LINE . NEW_LINE;
   fwrite($handle, $feed_item_separator);	
   fclose($handle);
} // End of function store_rss_feed_results

至此,我们在这个程序中使用的所有功能性组件就介绍完了。希望您对这些组件如何协同工作已经有了一个很好的理解。接下来,我们看看 RSS 提要聚合器是如何工作的。

让 RSS 提要聚合器开始工作

下载 部分,您可以下载一个包括以下文件的压缩文件。

  • rss_feed_aggregator\rss_feed_reader.php
  • rss_feed_aggregator\rss_feed_aggregator.php
  • rss_feed_aggregator\rss_feed_sources.xml

将这三个文件解压缩到您的机器中。请确保已经具备了 PHP 环境,例如一个已经用 cURL 和 SimpleXML 配置好了的 Zend Core 环境。其中的一个文件( rss_feed_sources.xml)是一个示例提要源输入 XML 文件,它包括 22 个不同的 RSS 提要源。这些特定的提要源提供了商业和金融类/频道中的 RSS 提要。如果愿意,可以定义您自已的输入 XML 文件。

要想让 RSS 提要聚合器开始工作,必须使用下面的命令语法(注意此命令语法末的最后一个标记是一个可选的命令行参数):

php -f rss_feed_aggregator.php <Feed sources input XML filename>

developerWorks Ajax 资源中心
请访问 Ajax 资源中心 这里是免费工具、代码和有关开发 Ajax 应用程序的信息的一站式中心。通过由 Ajax 专家 Jack Herrington 主持的这个 Ajax 社区论坛, 您可以找到志同道合的人,并很可能从他那里获得您目前所存疑问的答案。

我们在前面讲过,如果没有提供命令行参数,那么此应用程序将会使用默认的提要源输入 XML 文件(rss_feed_sources.xml)。假设使用了默认的提要源输入 XML 文件并且一切运行正常,应该会看到如图 3 所示的结果:

图 3. 运行 RSS 提要聚合器得到的结果
RSS 提要聚合器结果

在运行 RSS 提要聚合器的目录中,将会出现一个名为 feed_results 的新子目录。该子目录下有几个文件,包含从每个 RSS 提要提供者处收到的 RSS 提要条目,而这些提要提供者则由用户在提要源输入 XML 文件中指定。

结束语

RSS 出现已经有几年了。它的流行得益于 Web 2.0 技术,比如 wiki、blog、mashup、社会网络门户及其他的信息聚合服务。本文总结了 RSS 格式的简单之处,这种简便性让 RSS 格式在与其他新兴的 Web 技术进行信息集成方面成为了一个极佳之选。由于信息就是 Web 的全部,因此 RSS 将会继续在如何以一种强大而有效的方式连锁和传播信息方面起到决定性的作用。

本文详细介绍了如何借助功能完备的 PHP 脚本开发提要聚合器。您可以用多种方式使用本文中所提供的 PHP 源代码:作为独立工具、作为共享库用于已有的 PHP 服务器端程序或作为 SOAP/REST Web 服务函数加入到企业面向服务的架构(Service-Oriented Architecture,SOA)中。

(Web)生活中有很多很棒的事物,形式却很简单。RSS 只是其中之一。


下载

描述名字大小
RSS 提要聚合器代码wa-aj-rssphp.zip10119 KB

参考资料

学习

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文
  • 查阅 developerWorks PHP 专区 获得全面的 PHP 项目资源。
  • developerWorks XML 专区 寻找所有需要了解的有关 XML 的信息。
  • PHP Manual 阅读 PHP API 文档。
  • 了解更多有关 Zend Core 的细节。
  • 阅读 O'Reilly Media 的 What is RSS?
  • 阅读 Harvard Law 的 RSS History,其中记载了大多数设计者所讲述的 RSS 的发展大事记。
  • 阅读另一个版本的 RSS 历史
  • 查看这个来自 Harvard Law 的 RSS 2.0 示例文件
  • 查阅 UserLand RSS Central 的 How you can use RSS 中的这个章节。
  • 随着 Web 2.0 在开发界的日益流行,您会在 developerWorks Web 开发专区 找到不断更新和发展的大量相关资源。
  • Dion Hinchcliffe 的 Web 2.0 Blog 涉及了诸多主题,包括 RSS is the Web 2.0 Pipe
  • Introduction to RSS feeds”(James Lewin,developerWorks,2000 年 11 月)介绍了 RSS 格式以及一些开源 Perl 模块,可以让您轻松处理 RSS 文件。
  • 在“RSS 简介”(Vincent Lauria,developerWorks,2006 年 3 月)查找更多有关 RSS、Atom 和提要阅读器的内容。
  • RSS 2.0 内容提要”(James Lewin,developerWorks,2003 年 12 月)回顾了 RSS 2.0 并介绍了 RSS 的新发展,让您即刻就能了解这种重要的格式。
  • 使用 RSS 和 Atom 实现新闻联合”(Ying Ying Lin,developerWorks,2006 年 9 月)展示了如何用 RSS 和 Atom 连锁格式实现这一通用的信息发布架构以简化处理和避免人为错误。
  • 在 “使用 XML:提供更友好的 RSS 和 Atom 提要”(Benoit Marchal,developerWorks,2006 年 10 月)中学习能帮助您站点的访问者阅读和理解 RSS 和 Atom 提要的技巧。
  • 在 “Ajax RSS 阅读器”(Jack D Herrington,developerWorks,2007 年 4 月)构建一个 Ajax RSS 阅读器。
  • 教程 “扩展 RSS 应用的创新用法”(Jonathan Levin,developerWorks,2007 年 12 月)展示了为人熟知的 RSS 格式的联合属性在仿真简单关系型数据库功能方面的一种创新应用。
  • 阅读 Web 2.0 Journal 这一世界领先的 Web 2.0 资源信息。

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, Open source, XML
ArticleID=290095
ArticleTitle=使用 PHP 构建可定制的 RSS 提要聚合器
publish-date=02182008