Published on Blog Developer's Cookbook.
Printer friendly version of http://blog.bulknews.net/cookbook/blosxom/rss/rss_auto_discovery.html.


RSS Auto Discovery

by miyagawa at Wed, 22 Oct 2003 14:45

あるサイトの RSS を自動で探し出すにはどのようにすればよいでしょう? "Syndicate This Site (XML)" のリンクを見て探す、でしょうか。デザインをカスタマイズしていたらどうでしょう?

HTML の link タグを使用すると、RSS の URL を機械的に抽出することができます。今回はこの RSS Auto-Discovery と呼ばれる手法を実装してみます。


RSS の埋め込み

「あ、このサイト便利、RSS Aggregator ソフトに追加しよう!」と思ったとき、まず必要なのは、そのサイトの RSS です。これを調べるには通常、ページ内にある [RSS] や [XML] などのアイコン、また "Syndicate This Site (XML)" などのテキストを探します。

しかしデザインをカスタマイズした Weblog だったりするとなかなか不便(*1)。数が多いときには RSS Aggregator が自動でとりこんでくれないものかな、と考えるのがエンジニア人情というものです。で、やはり考えた人がいました。

HTML の LINK タグ要素を使用して、その Weblog に関連づいた RSS の URL を、機械的に抽出できる仕組み RSS Auto Discovery が考案されたわけです。LINK タグの記述は、

<link rel="alternate" type="application/rss+xml"
 title="RSS" href="{URL for RSS}" />

のようになります(*2)。SharpReader などの RSS Aggregator, また BlogRollingBloglines といったメタ Blog サービス(*3)では、この機能を使って URL から自動で RSS を抜き出してくれます。よってユーザが、いちいち RSS の URL を探す手間がはぶけるというわけです。

サンプルコード

引数に URL を指定すると、その URL の中に link タグで埋めこまれた RSS の URL を調べるコマンドラインスクリプトを作成しました。コードは List 1 のようになります。

# 特定のタグと属性を抽出する場合は HTML::Parser より HTML::TokeParser
必要なモジュールを use します。今回は HTML のパース処理に必要な HTML::TokeParser、また URL から HTML を取得する LWP::Simple を使用します。

HTML のパースをする CPAN モジュールというと、HTML::Parser モジュールが標準的ですが、今回のように特定のタグと属性を抽出する、という目的にはあまり向きません。またこの程度の簡単な HTML 破片であれば、正規表現を使っても十分実用には耐えるとは思いますが、link タグ中の属性の順番が変わったら、など考えるといろいろやっかいですので、HTML::Parser へのトークン毎のインタフェースを提供するラッパー、 HTML::TokeParser を使用しています。

use LWP::Simple;

my $url = shift;
my $rss = discover_rss($url);

if ($rss) {
    print "RSS for $url\n=> $rss\n";
} else {
コマンドラインの第1引数で与えられた URL に対し、discover_rss を実行し、RSS の URL を取得します。取得した結果に応じて、出力結果を変えています。

}

# URL から RSS を Auto-Discovery して返す
# 複数の RSS Feed には未対応
sub discover_rss {
    my $url = shift;

    # HTTP GET
    my $html = get($url) or die "Can't get $url";

    # HTML::TokeParser で link タグを取得
    my $parser = HTML::TokeParser->new(\$html);
LWP::Simple::get で 取得した HTML に対し、HTML::TokeParser オブジェクトを生成します。続いて、get_tag メソッドで link タグを順次取得します。ここで link タグのアトリビュートは $token->[1] にハッシュリファレンスで格納されています。ここでは、

$attr = {
  rel => 'alternate',
  type => 'application/rss+xml',
  href => 'http://example.com/rss.rdf',
  title => 'RSS',
};

のような値となります。よって reltype を調べ、マッチした要素の href 属性が RSS の URL となります(*4)

実行例

コマンドラインからいくつかの URL に対して実行してみます。

% ./discover_rss.pl http://blog.bulknews.net/cookbook/
RSS for http://blog.bulknews.net/cookbook/
=> http://blog.bulknews.net/cookbook/blosxom/index.rss10

% ./discover_rss.pl http://www.movabletype.org/
RSS for http://www.movabletype.org/
=> http://www.movabletype.org//index.xml

このように Auto Discovery に対応した HTML を利用しているサイトでは、RSS 情報が機械的に抽出できます。

ちなみに Movable Type や blosxom では、デフォルトのテンプレートで、この RSS Auto Discovery に対応しています。その他のツールでも、自らテンプレートを編集することができれば、対応することができます。

HTML::RSSAutodiscovery

実は、このプログラムでは、車輪の再発明をしてしまいました。CPAN にある HTML::RSSAutodiscovery を使用すれば、

use HTML::RSSAutodiscovery;

my $url  = "http://www.diveintomark.org/";
my $html = HTML::RSSAutodiscovery->new();
print $html->parse($url)->[0]->{href}, "\n";

で、ほぼ同様のことが出来ます。ただ今回は、この RSS Auto Discovery の仕様を理解するために、このモジュールは使用しませんでした。

Listings

List 1: discover_rss.pl
#!/usr/local/bin/perl -w
# RSS Auto-Discovery の実装

use strict;

# 特定のタグと属性を抽出する場合は HTML::Parser より HTML::TokeParser
use HTML::TokeParser;
use LWP::Simple;

my $url = shift;
my $rss = discover_rss($url);

if ($rss) {
    print "RSS for $url\n=> $rss\n";
} else {
    print "No RSS discovered for $url\n";
}

# URL から RSS を Auto-Discovery して返す
# 複数の RSS Feed には未対応
sub discover_rss {
    my $url = shift;

    # HTTP GET
    my $html = get($url) or die "Can't get $url";

    # HTML::TokeParser で link タグを取得
    my $parser = HTML::TokeParser->new(\$html);
    while (my $token = $parser->get_tag("link")) {
        my $attr = $token->[1];

        # <link rel="alternate" type="application/rss+xml" href="" />
        # title 要素がないサイトもいくつかあったので甘い判定
        if ($attr->{rel} eq 'alternate'
            && $attr->{type} eq 'application/rss+xml') {
            return $attr->{href};
        }
    }

    return;
}
*1) Movable Type なら、ほとんどのユーザが Syndicate This Site (XML) を使用していますが、他のツールではそうでもありません
*2) 実際は1行で書くのが一般的です。
*3) Blog から何かの情報を抽出し、配信するサイトの総称
*4) title 属性は使用していないサイトもあるようなのでチェックしていません。

Copyright©2002-2003 Tatsuhiko Miyagawa