一つ前のエントリで記述した RSS auto-discovery ですが、RssRolling でも利用して、巡回先サイトの HTML から RSS の URL を抽出するのに使っています。
どんな実装か、軽く紹介してみます。(RssRolling は Perl で書いているので、ここで紹介するコードも Perl です。)
作ったモジュールは2つ。1つは HTML::RSSAutodiscovery を軽くラッピングした RssUrlFinder.pm、もう1つは探し出した RSS の URL を保存しておく RssUrlCache.pm。RssUrlFinder.pm は以下のような実装になります。
package RR::RssUrlFinder;
use strict;
use warnings;
use base qw (Class::Accessor);
use HTML::RSSAutodiscovery;
__PACKAGE__->mk_accessors( qw( html_url is_found ) );
sub rss_url {
my $self = shift;
die "please set html_url before discovery" if (not $self->html_url);
unless ( $self->is_found ) {
my $html = HTML::RSSAutodiscovery->new;
if ( my $result = $html->parse( $self->html_url ) ) {
$self->{_result} = $result->[0]->{href};
$self->is_found(1);
}
}
return $self->{_result};
}
1;
ちょっと手抜きなんですが、HTML::RSSAutodiscovery を使って見つかった RSS の URL のうち一番最初のものを採用して呼び出し元に返すメソッド、rss_url を用意しています。複数見つかった場合にはアレなんですが、どうもこの方法でほとんどのケースに対応できるっぽいので、とりあえずはこんなところ。コンストラクタやアクセサを書くのが面倒なので、Class::Accessor を利用しています。
use RR::RssUrlFinder;
my $url = "http://naoya.dyndns.org/~naoya/mt/";
my $rss_url = RR::RssUrlFinder->new( { html_url => $url } )->rss_url;
とかして利用します。
一方、RssUrlCache.pm はちょっと長めです。RssRolling は 30 分おきにリストアップされたすべてのサイトを巡回していますが、毎回 RSS の URL を探し出すのは効率が悪いので、一度見つけたものは HTML の URL とペアで保存しておく、そのためのクラスとなっています。
package RR::RssUrlCache;
use strict;
use warnings;
use base qw (Class::Accessor);
use XML::Simple;
__PACKAGE__->mk_accessors(qw(cache_file));
sub addUrl {
my $self = shift;
my %args = @_;
my $doc_url = $args{doc_url};
my $rss_url = $args{rss_url};
$self->{_weblogs}->{weblog}->{$doc_url} = { xmlUrl => $rss_url };
}
sub searchRssUrl {
my $self = shift;
my %args = @_;
my $doc_url = $args{doc_url};
if (exists $self->{_weblogs}->{weblog}->{$doc_url}) {
return $self->{_weblogs}->{weblog}->{$doc_url}->{xmlUrl};
} else {
return;
}
}
sub load {
my $self = shift;
my %args = @_;
my $file = $args{file} || $self->cache_file;
if ( -e $file ) {
my $parser = XML::Simple->new;
$self->{_weblogs} = $parser->XMLin ( $file,
KeyAttr => { weblog => '+htmlUrl' },
);
} else {
return;
}
}
sub save {
my $self = shift;
my %args = @_;
my $file = $args{file} || $self->cache_file;
my $parser = XML::Simple->new;
$parser->XMLout( $self->{_weblogs},
KeyAttr => {'weblog' => 'htmlUrl'},
OutputFile => $file,
RootName => 'weblogs',
XMLDecl => '<?xml version="1.0"?>' );
}
1;
RssUrlFinder.pm は、データの保持に XML ファイルを使うようにしました。load メソッドで読み出し、save メソッドで書き出しです。パーサーは XML::Simple を使ってます。XML ファイルの中は以下のようなフォーマットにしました。
<?xml version="1.0" ?> <weblogs> <weblog xmlUrl="http://www.example.com/rss.xml" htmlUrl="http://www.example.com/" /> <weblog xmlUrl="http://naoya.dyndns.org/index.rdf" htmlUrl="http://naoya.dyndns.org/" /> ... </weblogs>
OPML のフォーマットに少しだけ似てます。実際には RssUrlCache.pm がデータ構造と動作をカプセル化するので、中で XML が使われていることは意識する必要はありません。
RssUrlFinder.pm と組み合わせて以下のように利用してます。
use RR::RssUrlFinder;
use RR::RssUrlCache;
my $url = "http://naoya.dyndns.org/~naoya/mt/";
my $rc = RR::RssUrlCache->new( { cache_file => "./rssurl.xml" } );
$rc->load;
my $rss_url = "";
unless ( $rss_url = $rc->searchRssUrl( doc_url => $url )) {
$rss_url = RR::RssUrlFinder->new({ html_url => $url })->rss_url;
if ($rss_url) {
$rc->addUrl( doc_url => $url, rss_url => $rss_url );
} else {
warn "cannot determine RSS URL from $url";
}
}
$rc->save;
print $rss_url;
キャッシュから RSS の URL を探して、なかったら RR::RssUrlFinder で探す、という感じです。ときどき RSS をうまく探せない場合があるのですが、XML で保存しているのでマニュアルで追加したり編集したりするのが楽で良い感じです。
結構うまく動いてます。;)