PHP で作成するカスタマイズ可能な RSS フィード・アグリゲーター

RSS の威力を Ajax と Web 2.0 アプリケーションで発揮させる

RSS (Rich Site Summary、RDF Site Summary、あるいは Really Simple Syndication) が登場したのは 1990年代中頃です。それ以来、RSS 形式にはいくつかの変形が生まれ、その所有権をめぐる論争も起きました。しかし形はどうであれ、1 つの Web サイトから多数の Web サイトに Web コンテンツを配信する際に RSS が役立つことには変わりありません。RSS の人気はフィード・リーダー、あるいはフィード・アグリゲーターとも呼ばれる新しい種類の Web ソフトウェアの成長を可能にしました。市販のフィード・アグリゲーターはいくつかあるものの、自分なりのフィード・アグリゲーターを作成して自分の Web アプリケーションに統合するのも簡単です。フィード・アグリゲーターを独自に作成する際には、この記事で紹介する十分機能的な PHP コード・スニペットが役に立つはずです。このコードは、PHP をベースとしたサーバー・サイドの関数を使ってカスタマイズ可能な RSS フィード・アグリゲーターを開発する実例となります。さらに、記事に付属の RSS フィード・アグリゲーター・コードの完全版をダウンロードして、そのまま利用することもできます。

Senthil Nathan (sen@us.ibm.com), Senior Software Engineer, IBM

Senthil NathanSenthil Nathan は、ニューヨーク州 Hawthorne にある IBM T.J. Watson Research Center のシニア・ソフトウェア・エンジニアです。22 年間、各種エンタープライズ・アプリケーションを対象としたソフトウェアの作成に取り組んできました。現在専門としているのは、一連の話題の技術、SOA、Web サービス、ストリーム・プログラミング、Java 2 Platform、J2EE (Enterprise Edition)、PHP、Ruby On Rails、Web 2.0、そして Ajax の開発です。



2008年 1月 22日

Ajax、Web 2.0 といった技術が Web アプリケーションの成長を急速に促すなかで、アプリケーションのコンテンツの価値が重要かつ中心的な役割を担っています。コンテンツには、コンテンツ・プロバイダーが公開/配信するのにも、コンテンツ利用者が購読/読み込みを行うのにも効果的な方法が必要となります。そんななか、RSS はその単純さと優れた能力とによって、この Web 1.0 時代の発明を Web 2.0 の時代、そしてその先の時代における重要な構成要素に仕立て上げます。

RSS は、コンテンツ・プロバイダーがそのサイトのコンテンツに対して行う定期的な更新をコード化して配信するための簡潔な形式として広く受け入れられています。コンテンツ・プロバイダーは RSS を使って、そのコンテンツを Web 全体、あるいは Web のサブセット (会社内での配信など) に公開します。コンテンツ・プロバイダーの対極にいるのはコンテンツ利用者です。彼らはサイト間をジャンプし、必要な情報そのものを探すために、情報を消化する間もなくページを次々に絞り込むという習慣に捕われています。このようなページ間の移動を不要にするのが、RSS フィードの魔法です。RSS フィードは利用者がカテゴリーを選択し、そのカテゴリーに関する情報を 1 つの塊として受け取ることを可能にします。しかも RSS フィードを使えば、コンテンツ・プロバイダーの数がいくら多くても、利用者は選択したカテゴリーに関する情報を受け取ることができます。

前述したように、RSS には配信とフィード集約という 2 つの部分があります。利用者にとって RSS フィードの主なメリットとなるのはフィード集約機能です。そこで、この記事ではフィード・アグリゲーターに焦点を当て、コンテンツ利用者の特定のニーズに合わせてカスタマイズ可能な RSS フィード・アグリゲーターを開発する方法を詳しく説明します。

XML および PHP の経験が必要です

この記事では、読者に基本的な XML と PHP の経験および知識があることを前提とします。この前提条件に当てはまらない方は、developerWorks XML ゾーンdeveloperWorks PHP エリアにアクセスしてください。どちらも経験、知識をこの記事のアプリケーション・レベルにまで引き上げるのに役立ちます。

はじめに

Web ベースの情報源が普及するなか、コンテンツ・プロバイダーとコンテンツ利用者にはいくつかの課題が突きつけられています。コンテンツ・プロバイダーにとっての大きな課題は、並み居る競争相手から抜きん出て、できるだけ多くの利用者を自分たちのコンテンツに引き付けることです。逆にコンテンツ利用者にとっての大きな課題は、数々のコンテンツ・プロバイダーが提供する無数のカテゴリーのなかから素早く必要な情報を探し出す能力です。コンテンツ利用者にはさらに、アクセスしようとしているコンテンツを目立たなくさせる (アニメーション化されたグラフィックや派手な広告などの) 気の散る内容を排除するという苦労もあります。

Web は日常生活に浸透していることから、開発者は、利用者が情報に素早くアクセスし、目的とするコンテンツの周りにある不要なコンテンツを避けられるよう、重点的に取り組まなければなりません。一方コンテンツ・プロバイダーにとっては、動的コンテンツを利用者の Web ブラウザーからハンドヘルドのモバイル機器に至るまでの各種媒体に配信する必要性がますます高まってきています。この記事の冒頭で概説したように、RSS はコンテンツの配信とアグリゲーションに関するさまざまな問題を軽減します。RSS をよく知らない方は、「参考文献」セクションに記載されている RSS 関連の記事を読むとある程度、その背景を理解できるはずです。

さまざまな (そしておそらく予期しない) 方法で使用することができる RSS は、インターネットに大きな可能性をもたらします。言うまでもなく、一部の主要なソフトウェア会社の経営陣は最近になって中核的なインターネットの役割を再調整し、RSS の機能を活用するようになっています (この話題については、Dion Hinchcliffe 氏 (高い評価を集めている Web 2.0 Journal および AjaxWorld Magazine の創刊者兼編集長) がまとめた簡潔でわかりやすい記事を読んでください)。Hinchcliffe 氏が図 1 で主張していることは、RSS が Web 2.0 情報エコシステムを可能にする仕組みです。ウィキ、ブログ、ニュース・サービス、アグリゲーション・サービス、マッシュアップ・サービス、そして検索エンジンなど、Web 2.0 の主要なビルディング・ブロックはいずれも、ビルディング・ブロック同士を結び付ける手段として RSS を使って、Web 2.0 のソーシャル・コンピューティングの構想を実現しようとします。

図 1. Hinchcliffe 氏の見解: Web 2.0 における RSS の役割
Hinchcliffe 氏の見解: Web 2.0 における RSS の役割

使用されている RSS 形式には複数のバージョンがあります。RSS バージョン 0.91、0.92、2.0 では特定の XML 形式に準拠して RSS フィードが記述されますが、RSS バージョン 1.0 が使用する XML 形式は他とは微妙に異なります。そのため RSS フィード・アグリゲーターを開発する際には、この微妙な違いを考慮に入れる必要があります。RSS の興味深い歴史については、「参考文献」セクションで紹介している記事の 1 つで読むことができます。

RSS 形式の重要性と有用性を理解してもらったところで、ここからはカスタマイズ可能な RSS フィード・アグリゲーターの開発について詳しく説明していきます。今回、RSS フィード・アグリゲーターの開発に使用するのは PHP 言語です。PHP に備わった複数の組み込み Web 関数と XML 関数は開発の迅速化につながるからです。さらに、PHP で作成されたコードは、Zend Framework が組み込まれている Zend Core などの PHP サーバーで簡単に実行することができます。フィード・アグリゲーターを開発するには、お使いのマシンに PHP がインストールされていて、cURL (Client URL) および XML パッケージと一緒に構成されている必要があります。Zend Core PHP サーバーは無料で入手できる上に、簡単にインストールして構成できるため、このサーバーをインストールすることをお勧めします。

それではまず、RSS 形式の基本について簡単に説明しましょう。

RSS の基本

RSS は、Apple、Microsoft®、Netscape、Userland、RSS-DEV 作業グループをはじめ、複数の会社やグループの貢献によって現在の形になっています。このように多数の会社が関与したため、RSS 形式には複数のバージョンができる結果となりました。バージョン間の違いで最も明白な点は、RSS バージョン 1.0 (RSS-DEV により開発) は他のバージョン (0.91、0.92、2.0 ではいずれも後方互換性を持つ形式を使用) とは若干異なる形式を使用することです。

リスト 1 に、RSS バージョン 1.0 の大まかな形式を示します。この形式で特に注目すべき点は、ルート要素は <rdf> という名前で、1 つの <channel> 要素と子要素として 1 つ以上の <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 の大まかな形式です。この形式では、ルート要素には <rdf> ではなく <rss> という名前が付けられています。ルート要素 <rss> が子として持つのは 1 つの <channel> 要素だけで、すべての <item> 要素は <channel> 要素の子になります。これは、RSS 1.0 との顕著な違いです。このように異なる RSS 形式の構文解析をするコードでは、この多少の違いを考慮に入れなければなりません。この記事では、<item> 要素の下にある 3 つの子要素に興味の対象を絞ります。この 3 つの子要素はどの 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 機能は、以下のコンポーネントで構成されます。この 4 つのコンポーネントについて記載順に説明します。

  • フィード・リーダー
  • フィード・ソース入力
  • フィード・アグリゲーター
  • フィード結果出力

上記の単純なコンポーネントを組み合わせて、他のアプリケーションにさまざまな方法で統合できる強力な RSS フィード・アグリゲーター機能を実現します。各コンポーネントを構成する PHP コード・スニペットの内容については以降のセクションで検討します。図 2 は、機能コンポーネントの概要です。

図 2. RSS 機能コンポーネントの概要
RSS 機能コンポーネントの概要

図 2 では、以下の点に注目してください。

  • ジョブの大半をこなすフィード・リーダー・コンポーネントは、所定のフィード・ソースが提供するフィードを取得することに重点を置きます。フィード・ソースは、特定のコンテンツ・プロバイダーが、指定された情報カテゴリーのコンテンツを定期的に配信するための URL でしかありません。フィード・ソースは例えば、ニューヨーク・タイムズがビジネス・カテゴリー/チャネルに関する最新のニュース宣伝文を XML ベースの RSS 形式で公開するための URL を指定します。
  • フィード・アグリゲーター・コンポーネントは、ユーザーが指定したフィード・ソースを入力として使用してフィード・リーダー・コンポーネントを呼び出し、カスタマイズされたフィード・ソースごとに、すべてのフィード項目を取得します。
  • フィード・ソース入力コンポーネントは、ユーザーが指定したフィード・ソースの詳細を定義し、それを読み取ります。フィード・ソースの詳細は、システム・メモリーに保存されたストリングという形式で指定することも、あるいは入力ファイルを使用したり、データベース内のレコードとして指定することもできます。
  • フィード結果出力コンポーネントは、アグリゲートされた RSS フィード項目の結果を特定のフィード・ソースから受け取って保存します。結果はシステム・メモリー内のストリングとして保存することも、ファイルやデータベース・テーブルに保存することもできます。

作業を簡単にするため、ここではフィード・ソース入力コンポーネントとフィード結果出力コンポーネントを組み合わせてフィード・アグリゲーター・コンポーネントの一部にすることにします (「ダウンロード」セクションから、この 2 つのコンポーネントを完全に文書化した PHP ソース・ファイル一式をダウンロードすることができます)。この 2 つのファイルをお使いのマシンに解凍すれば、独自にカスタマイズした RSS フィード・ソースのセットと併せて実行することができます。ただしそのためには、前のセクションで説明したように XML および cURL ライブラリーをサポートする PHP 環境が前提条件となります (RSS フィード・アグリゲーターのコードをさまざまに利用する方法については、「まとめ」セクションを参照してください)。

ここからはいよいよ、コンポーネントごとの内部の動作に目を向けます。

フィード・リーダー

フィード・リーダー・コンポーネントは、フィード・ソースとの間で必要なネットワーク通信を行い、フィード・ソースから返された XML ベースの RSS フィードを構文解析するコア・エンジンです。フィード・リーダーが主に使用するのは、以下の単純ながらも強力な PHP 環境の 3 つの機能です。

  • cURL
  • SimpleXML
  • PHP の配列

cURL は、PHP プログラムが HTTP、FTP、Telnet などの各種プロトコルを使用してリモート・サーバーに接続することを可能にするオープンソースのライブラリーです。このライブラリーには、HTTP POST、HTTP GET、HTTP PUT といったアクションを実行する一連のラッパー関数が用意されています。cURL 関数を使用するには、PHP 環境で libcurl パッケージを使用できるようにしなければなりません。Zend Core などの PHP サーバーでは、Zend Core 管理プログラムを使ってマウスのシングル・クリックで cURL を簡単に使用可能にすることができます。

SimpleXML も同じく PHP の拡張機能です。その名前からわかるように、この拡張機能は XML 文書からの読み取り操作であろうと、XML 文書への書き込み操作であろうと、XML を簡単に扱えるようにします。PHP バージョン 5 以降では、この拡張機能がデフォルトで有効に設定されています。

配列は基本 PHP 言語に含まれる機能で、これによってコレクション・データ構造は扱いやすくなります。ここでは PHP の配列を使って RSS フィード項目の結果を収集します。

フィード・リーダー・コンポーネントのソース・コードは、rss_feed_reader.php という名前の PHP ファイルにあります。このコードは、以下の 3 つのカスタム関数で構成されています。

  • get_rss_feeds
  • perform_curl_operation
  • parse_rss_feed_xml

以降の段落では、上記の関数を実現するコードについて詳しく説明するとともに、ロジックを理解しやすいように十分にソース・コードを文書化します。

リスト 3 に示されているように、get_rss_feeds はフィード・アグリゲーターコンポーネントから呼び出される主要なビジネス・ロジック関数です。この関数は呼び出し側からの入力として、以下の 3 つの参照値を受け入れます。

  • RSS プロバイダーの名前
  • RSS プロバイダーの URL
  • 呼び出し側が特定フィード・プロバイダーからの取得を希望する最大 RSS フィード項目数

この関数のロジックは cURL ネットワーク操作を行うため、フィード・プロバイダーのリモート URL から XML ベースの RSS フィード・コンテンツを取得する関数を呼び出し、3 つの要素 (title、link、description) のいずれかの集合を保持する 3 つの配列を作成します。配列に保持される要素は、受け取った RSS フィード・コンテンツに含まれるすべてのフィード項目から収集することになります。リスト 1 を思い出してください。この 3 つの要素は受け取った RSS フィード・コンテンツに含まれる <item> 要素の子要素です。続いて関数のロジックは、受け取った RSS コンテンツのストリングと 3 つの配列を別の関数に渡します。その関数が、XML コンテンツを構文解析して RSS フィード項目を収集します。RSS フィード・コンテンツの構文解析の結果が false であれば、それはつまり、受け取った RSS フィード・コンテンツにエラーが含まれているということです。その場合、この関数は空の配列を呼び出し側に返します。構文解析の結果が true の場合はフィード項目が正常に解析されたことを意味し、この場合は受け取ったすべてのフィード項目のタイトル、URL、説明が 3 つの配列に含まれることになります。解析が完了すると、その関数は呼び出し側に返す新しい結果配列を作成し、結果配列の上位 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 ヘッダーが存在する場合にはそのロケーションに従うためのオプション、HTTP レスポンスを curl_exec 関数からのストリングとして返すように cURL に指示するオプションなどがあります。オプションを設定した上で、perform_curl_operation 関数は 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、説明が含まれる 3 つの配列があります。PHP SimpleXML 拡張機能の簡単さをまだ体験したことがなければ、この関数を見ればそれがわかるはずです。他のパラダイムで採用されている複雑な XML 構文解析技術とは異なり、PHP のSimpleXML は XML 構造を PHP オブジェクト構造として操作することを可能にします。

一番最初のステップはただ単に、受け取った RSS フィード・コンテンツのストリングをロードし、それに相当するオブジェクト構造を取得するだけの話です。それから構文解析を開始するわけですが、その前に、受け取った RSS フィード・コンテンツのエンコードに RSS バージョン 1.0 が使われているか、それとも他の RSS バージョンが使われているかを判断しなければなりません。1 つの判断方法は、<item> 要素がルート要素の子であるか、<channel> 要素の子であるかをチェックすることです。RSS のバージョン間での微妙な形式の違いについて思い出すには、「RSS の基本」セクションを参照してください。RSS 形式のバージョンが判別され、受け取った XML コンテンツの有効性が確認されると、この関数はすべての <item> 要素 (この例の場合、実際には PHP オブジェクトです) を繰り返し処理してタイトル、リンク、そして説明の各フィールドを解析します。この 3 つの値のそれぞれが、この関数に参照先入力引数として渡された該当する配列に追加されます。必要な RSS フィード項目の最大数に対する繰り返し処理が完了すると、関数は true を返します。RSS フィード項目を 1 つも解析できなかった場合には、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

フィード・リーダー・コンポーネントが実行する主なタスクについての説明はこれですべてです。次は、フィード・ソース入力コンポーネントの説明に移ります。

フィード・ソース入力

フィード・ソース入力は、このシステムのなかで最も単純なコンポーネントで、その役割はユーザーがカスタマイズしたフィード・ソースのリストを取得することです。このコンポーネントには 1 つの関数があり、プログラムの起動時には真っ先にフィード・アグリゲーターによってこの関数が呼び出されます。記事の最初で説明したように、フィード・ソースの情報はプログラム・データ構造、データベース・テーブル、あるいはファイル内で指定することができます。この例の場合、フィード・ソースは 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

フィード・ソース入力ファイルの内容には、1 つ以上の 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 フィード項目の最大数を取得します。その上で、フィード・リーダー・コンポーネントが持つ関数のうちの 1 つ、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 フィード結果を保存するために使用します。このコンポーネントが持つ関数は 1 つで、この関数は、受け取った RSS フィード・コンテンツからすべての RSS フィード項目が解析された時点でフィード・アグリゲーターによって呼び出されます。記事の最初で説明したように、RSS フィード結果はブラウザーに送信することも、別のスタンドアロン・プログラムに送信することも可能です。あるいは、プログラム・データ構造、データベース・テーブル、またはファイルに保存することもできます。この例では、RSS フィード・プロバイダーから受け取ったフィード結果をプロバイダーごとのファイルに保存し、すべての結果ファイルを feed_results サブディレクトリーに保存します。このサブディレクトリーは、フィード・アグリゲータープログラムが実行されるディレクトリーの下に自動的に作成されます。

リスト 9 を見るとわかるように、フィード結果出力コンポーネントが持つ関数は 1 つだけです。この store_rss_feed_results 関数は 2 つの関数入力引数を取ります。最初の引数はファイル・シーケンス番号で、この番号が結果ファイルの名前を構成するために使われます。結果ファイルの名前は NNN_rss_feed_items.txt という形式で、NNN の部分がこの最初の関数引数の値になります。この関数が取る 2 番目の引数は、特定の RSS フィード・プロバイダーから受け取ったすべての RSS フィード項目が含まれる、ネストされた PHP の配列です。この結果配列には 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 フィード項目の説明からなる配列

3 つの配列、a[2], a[3]、および a[4] の各索引に含まれる内容すべてがまとまって、受け取った RSS XML に示された 1 つの特定 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

お使いのマシンに上記の 3 つのファイルすべてを解凍します。Zend Core などの PHP 環境が cURL および SimpleXML で構成されていることを確認してください。提供されるファイルのうちの 1 つ (rss_feed_sources.xml) はサンプル・フィード・ソース入力 XML ファイルで、ここには 22 種類の RSS フィード・ソースが含まれます。これらのフィード・ソースは Business and Finance カテゴリー/チャネルの RSS フィードを提供するものですが、独自の入力 XML ファイルを定義してもらっても構いません。

RSS フィード・アグリゲーターを動作させるには、以下のコマンド構文を使用します (コマンド構文の終わりにある最後のトークンはオプションのコマンド・ライン引数です)。

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

developerWorks の Ajax リソース・センター
Ajax resource center にアクセスしてください。ここは、無料のツール、コード、そして Ajax アプリケーションの開発に関する情報が用意されたワンストップ・ショップです。また、Ajax のエキスパートである Jack Herrington がホストする活発な Ajax コミュニティー・フォーラムは、あなたが今まさに探している答えを持っているかもしれない開発者仲間と交流する手段となります。

前にも説明したように、コマンドライン引数が指定されない場合には、アプリケーションはデフォルトのフィード・ソース入力 XML ファイル (rss_feed_sources.xml) を使用します。デフォルトのフィード・ソース XML ファイルを使用したとして、すべてが正しく機能すると図 3 に記載する結果が表示されるはずです。

図 3. RSS フィード・アグリゲーターの実行結果
RSS フィード・アグリゲーターの実行結果

上記から、RSS フィード・アグリゲーターを実行したディレクトリーのなかに feed_results という新しいサブディレクトリーが作成されていることがわかります。このサブディレクトリーに置かれている複数のファイルに、ユーザーがフィード・ソース入力 XML ファイルに指定した RSS フィード・プロバイダーのそれぞれから受け取った RSS フィード項目が含まれます。

まとめ

RSS が登場してから何年も経っていますが、ウィキ、ブログ、マッシュアップ、ソーシャル・ネットワーク・ポータルなどの情報集約サービスをはじめとする Web 2.0 技術の誕生によって、その人気は急上昇しています。この記事で要約した RSS 形式の単純さによって、RSS は他の新しい Web 技術との情報統合に最適な選択肢となっています。Web では情報がすべてです。そのため、情報を広く配信するための強力かつ便利な方法を決定する上で、RSS はこれからも中心的な役割を担っていくはずです。

この記事では、十分機能的な PHP スクリプトを用いてフィード・アグリゲーターを開発する方法について詳しく説明しました。この記事に付属の PHP ソース・コードは、さまざまな方法で使用することができます。例えば、スタンドアロンのツールとして使用することも、共有ライブラリーとして既存の PHP サーバー・サイド・プログラムで使用することも、あるいは SOAP/REST Web サービス機能として使用してエンタープライズ SOA (サービス指向アーキテクチャー) に加えることもできます。

(Web を利用した) 生活のなかでの素晴らしいことの多くは、単純な形をしています。RSS は単にその 1 つでしかありません。


ダウンロード

内容ファイル名サイズ
RSS feed aggregator codewa-aj-rssphp.zip10119 KB

参考文献

学ぶために

  • developerWorks PHP エリアには包括的な PHP プロジェクト・リソースが揃っています。
  • developerWorks XML ゾーンは XML に関するあらゆる疑問を見つけるための情報源です。
  • PHP Manual の PHP API マニュアルを読んでください。
  • Zend Core の詳細を学んでください。
  • O'Reilly Media で掲載している「What is RSS?」を読んでください。
  • Harvard Law にアクセスして、RSS History を読んでください。RSS 形式の大部分を設計した設計者が RSS のこれまでの歴史を説明しています。
  • 別の視点からの RSS の歴史を読んでください。
  • Harvard Law によるこの RSS 2.0 サンプル・ファイルに目を通してください。
  • UserLand RSS Central の RSS の使用方法に関するセクションを読んでください。
  • developerWorks Web development ゾーンには、開発世界で今注目を集めている Web 2.0 に関する資料が続々と追加されています。
  • Dion Hinchcliffe's Web 2.0 Blog では、RSS is the Web 2.0 Pipe をはじめ、さまざまなトピックを掘り下げて検討しています。
  • RSSニュース・フィードの紹介」(James Lewin著、developerWorks、2000年11月) では RSS 形式に着目し、RSS ファイルを簡単に操作できるようにするオープン・ソースの Perl モジュールについて検討しています。
  • シンジケーションの概要」(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 シンジケーション形式を使った一般的なニュース公開アーキテクチャーを実装する方法を紹介しています。
  • RSS や Atom フィードを身近にする」(Benoit Marchal 著、developerWorks、2006年10月) で、サイトのビジターが RSS および Atom フィードを読んで理解できるようにする手法を学んでください。
  • Ajax RSS リーダー」(Jack D Herrington 著、developerWorks、2007年4月) を読んで、Ajax RSS を作成してみてください。
  • チュートリアル「Push RSS to new limits」(Jonathan Levin 著、developerWorks、2007年12月) では、よく知られた RSS 形式の結合プロパティーを使って単純なリレーショナル・データベースの機能をエミュレーションする革新的な方法を紹介しています。
  • 世界を代表する Web 2.0 の情報源、Web 2.0 Journal を読んでください。

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Open source, XML
ArticleID=290604
ArticleTitle=PHP で作成するカスタマイズ可能な RSS フィード・アグリゲーター
publish-date=01222008