Published on Blog Developer's Cookbook.
Printer friendly version of http://blog.bulknews.net/cookbook/blosxom/opml/blogrolling2opml.html.
BlogRolling を利用して RSS 巡回先を管理 (OPML編)
by miyagawa at Sat, 25 Oct 2003 01:35
BlogRolling を利用して RSS の巡回先を管理できることは紹介しました。実は複数の RSS をまとめて記述する方式として一般的になってきているのが、OPML という XML フォーマットです。今回は BlogRolling で作成した URL リストを OPML に変換してみます。
OPML
OPML は opml.org に仕様が規定されている XML フォーマットの1つで、基本的には Outline Processor Markup Language が示す通り、アウトラインプロセッサ向けのデータ記述言語です。
ただ、Dave Winer の主催する Radio Userland で自分の Subscribe している RSS リストを Export する際のフォーマットとして OPML が使用されたのがきっかけで、現在では主要な RSS Aggregator が、巡回先データを Import/Export する際のデファクトスダンダードとして OPML を利用しています。
よって、BlogRolling で講読している Blog のリストから OPML を作成できれば、RSS Aggregator への Import もごく簡単に行えるというわけです。実は前回も紹介した通り、BlogRolling それ自体が OPML での出力をサポートしています。http://rpc.blogrolling.com/opml.php?r=$BlogRollingId で、巡回先を OPML フォーマットで取得することができます。しかし、BlogRolling で出力される OPML は List 1 のようなフォーマットになっており(*1)、RSS と同様、各サイトのシンジケーション RSS は記述されていません。今回のスクリプトでは、RSS Auto Discovery を使って、RSS も記述された OPML を作成します。
サンプルスクリプト
基本的には blogrolling2rss の大部分が再利用できます。テキストで RSS 一覧を出力していた部分を OPML に変更するだけです。BlogRolling の RSS から、RSS 講読先 OPML を出力するスクリプトは List 2 のようになります。
use HTML::RSSAutodiscovery;
use LWP::Simple;
use Storable;
use Template;
use XML::RSS;
使用するモジュールを use します。追加したのは Template モジュールのみです。
my $rss = XML::RSS->new();
$rss->parse($xml);
my $title = $rss->{channel}->{title};
OPML の title 属性に使用するため、RSS の channel/title を取得します。
my @sites;
for my $item (@{$rss->{items}}) {
$cache->{$item->{link}} ||= discover_rss($item->{link}) or next;
push @sites, { title => $item->{title},
link => $item->{link},
rss => $cache->{$item->{link}} };
}
各 item について、RSS の URL を Auto-Discovery で検索した後、@sites 配列にハッシュリファレンスを push します。
my $tt = Template->new();
$tt->process("blogrolling2opml.tt",
{ sites => \@sites, title => $title },
"mySubscription.opml");
Template オブジェクトを new し、テンプレートファイル blogrolling2opml.tt (List 3) を処理します。テンプレートに渡す変数として、サイト情報の配列 sites と、OPML のタイトルとなる RSS channel の title をハッシュリファレンスで渡します。処理された結果は mySubscription.opml ファイルに出力されます(*2)。
その他の部分の処理は、blogrolling2rss と同様ですので省略し、テンプレートの中身をみてみましょう。
<?xml version="1.0" encoding="utf-8" ?>
<opml version="1.0">
<head>
<title>BlogRolling OPML for [% title | html %]</title>
</head>
XML 宣言を行い、opml 要素を開始します。opml の下には head 要素と body 要素がぶら下がり、head の中に title があるというツリー構造となっています(*3)。
<body>
[% FOREACH site = sites -%]
<outline
title="[% site.title | html %]"
description="Feed for [% site.title | html %] via BlogRolling"
htmlUrl="[% site.link | html %]"
type="rss"
xmlUrl="[% site.rss | html %]" />
[% END -%]
</body>
body 要素の中で、sites についてループして、outline 要素を記述します。この outline 要素1つに、巡回先 1サイトの情報を格納していきます。
opml.org/spec には、
What is an <outline>?
An <outline> is an XML element, possibly containing one or more attributes, and containing any number of <outline> sub-elements.
とだけ記述されており、outline がどのような attribute を持つかの記述はありません。ここでは Radio Userland の OPML に記述されている、
| title | RSS Feed 名 |
| description | RSS Feed の記述 |
| htmlUrl | Blog の URL |
| type | Feed タイプ(rss) |
| xmlUrl | RSS の URL |
を模倣して採用しています。各種 RSS Aggregator に OPML をインポートする際にも、これらの情報が埋まっていれば大概の場合は、大丈夫なようです(*4)。
実行例
blogrolling2opml.pl をコマンドラインから実行します。
% ./blogrolling2opml.pl
うまくいけば何も出力されず、mySubscription.opml という OPML ファイル(List 4)が出来上がります。このファイルを、OPML からインポート可能な RSS Aggregator に食わせてみましょう。ここでは SharpReader と FeedDemon で実験してみましたが、どちらもうまくインポートできました(*5)。
Listings
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- OPML generated by Blogrolling.com -->
<opml version="1.1">
<head>
<title></title>
<dateModified></dateModified>
<ownerName>Blogroll Owner</ownerName>
<ownerEmail>opml@blogrolling.com</ownerEmail>
</head>
<body>
<outline text="HAIL 2 U !! - Weblog" type="link"
url="http://www2u.biglobe.ne.jp/%7Ekyo-n/blog/blosxom.cgi"
lastmod="Last updated: 07:42:40 GMT on Friday, October 24"
target="" />
<outline text="blog.bulknews.net" type="link"
url="http://blog.bulknews.net/mt/"
lastmod="Last updated: 05:54:17 GMT on Friday, October 24"
target="" />
<outline text="Blog Developer's Cookbook" type="link"
url="http://blog.bulknews.net/cookbook/blosxom"
target="" />
<outline text="Shibuya Perl Mongers" type="link"
url="http://shibuya.pm.org/" target="" />
<outline text="NDO::Weblog" type="link"
url="http://naoya.dyndns.org/" target="" />
</body>
</opml>
#!/usr/local/bin/perl -w
# blogrolling2opml - build OPML subscription from BlogRolling
use strict;
use HTML::RSSAutodiscovery;
use LWP::Simple;
use Storable;
use Template;
use XML::RSS;
our $CacheFile = "rss.cache";
our $BlogRollingId = "fd16d26c9ad1029c21a48955b8c19731";
my $cache = eval { Storable::retrieve($CacheFile) } || {};
my $url = "http://rpc.blogrolling.com/rss.php?r=$BlogRollingId";
my $xml = LWP::Simple::get($url);
my $rss = XML::RSS->new();
$rss->parse($xml);
my $title = $rss->{channel}->{title};
my @sites;
for my $item (@{$rss->{items}}) {
$cache->{$item->{link}} ||= discover_rss($item->{link}) or next;
push @sites, { title => $item->{title},
link => $item->{link},
rss => $cache->{$item->{link}} };
}
my $tt = Template->new();
$tt->process("blogrolling2opml.tt",
{ sites => \@sites, title => $title },
"mySubscription.opml");
Storable::nstore($cache => $CacheFile);
sub discover_rss {
my $url = shift;
my $discovery = HTML::RSSAutodiscovery->new();
my $rss = $discovery->parse($url);
unless (@$rss) {
warn "no RSS found for $url\n";
return;
}
return $rss->[0]->{href};
}
<?xml version="1.0" encoding="utf-8" ?>
<opml version="1.0">
<head>
<title>BlogRolling OPML for [% title | html %]</title>
</head>
<body>
[% FOREACH site = sites -%]
<outline
title="[% site.title | html %]"
description="Feed for [% site.title | html %] via BlogRolling"
htmlUrl="[% site.link | html %]"
type="rss"
xmlUrl="[% site.rss | html %]" />
[% END -%]
</body>
</opml>
<?xml version="1.0" encoding="utf-8" ?>
<opml version="1.0">
<head>
<title>BlogRolling OPML for blog.bulknews.net</title>
</head>
<body>
<outline
title="blog.bulknews.net"
description="Feed for blog.bulknews.net via BlogRolling"
htmlUrl="http://blog.bulknews.net/mt/"
type="rss"
xmlUrl="http://blog.bulknews.net/mt/index.rdf" />
<outline
title="Blog Developer's Cookbook"
description="Feed for Blog Developer's Cookbook via BlogRolling"
htmlUrl="http://blog.bulknews.net/cookbook/blosxom"
type="rss"
xmlUrl="http://blog.bulknews.net/cookbook/blosxom/index.rss10" />
<outline
title="HAIL 2 U !! - Weblog"
description="Feed for HAIL 2 U !! - Weblog via BlogRolling"
htmlUrl="http://www2u.biglobe.ne.jp/%7Ekyo-n/blog/blosxom.cgi"
type="rss"
xmlUrl="http://www2u.biglobe.ne.jp/%7Ekyo-n/blog/blosxom.cgi/index.rss" />
<outline
title="Shibuya Perl Mongers"
description="Feed for Shibuya Perl Mongers via BlogRolling"
htmlUrl="http://shibuya.pm.org/"
type="rss"
xmlUrl="http://shibuya.pm.org/blosxom/index.rss10" />
<outline
title="NDO::Weblog"
description="Feed for NDO::Weblog via BlogRolling"
htmlUrl="http://naoya.dyndns.org/"
type="rss"
xmlUrl="http://naoya.dyndns.org/~naoya/mt/index.rdf" />
</body>
</opml>
Copyright©2002-2003 Tatsuhiko Miyagawa