Template::Extract という CPAN モジュールがあります。今まで何度か聞いてはいたけど詳しくはみていなかったのですが、改めてドキュメントを見たら、これは便利。
Template::Extract は Template-Toolkit (以下TT)のサブクラスモジュール。TT は Perl におけるテンプレートエンジンの代表的な実装の一つ (他には HTML::Template など。TT の方が遙かに強力、だと思います) です。通常テンプレートエンジンと言えば、あらかじめスクリプトの中で生成した出力(オブジェクトやデータ構造)をテンプレートに渡してやって、それをテンプレートからいじったりするためのものです。Template::Extract はその逆、テンプレートからデータ構造を作ってくれます。
要はですね、例えば HTML から任意のタグで囲まれた箇所だけ抜き出したいなあなんてときに、TT の文法でパターンを記述しておけば、簡単にスクリプト側からその抜き出した箇所を取得することができるというものです。
何か面白いことができそうだなあということで、単純に思いついたのが HTML から RSS を生成するスクリプト。Template::Extract で HTML から必要な箇所を抜き出して、XML::RSS でフィードを生成します。
こんな具合です。
#!/usr/local/bin/perl # ttrss.pl - Generate feed from a site which is grabbable with Template::Extract. # # usage: perl ttrss.pl http://www.example.com/ example.tt # # 2004.01.11 Naoya Ito <naoya@naoya.dyndns.org> use strict; use warnings; use LWP::Simple; use Template::Extract; use XML::RSS; use FileHandle; use URI; if (@ARGV != 2) { print STDERR "usage: $0 <url> <template>\n"; exit(1); } eval { my $uri = URI->new( shift ); my $document = LWP::Simple::get($uri); local $/; # read from file at once my $fh = FileHandle->new( shift ) or die "cannot open template: $!"; my $template = $fh->getline; $fh->close; my $obj = Template::Extract->new; my $ext = $obj->extract( $template, $document); my $rss = XML::RSS->new; $rss->channel( title => $ext->{title}, link => $uri, description => "$ext->{title} RSS feed - generated by ttrss.pl"); for my $item (@{$ext->{items}}) { $rss->add_item( title => $item->{title}, link => URI->new_abs($item->{link}, $uri), description => $item->{description}, ); } print $rss->as_string; }; if (my $err = $@) { die; }
LWP::Simple でリモートのコンテンツを GET して、テンプレートを読み込んだ後、
my $obj = Template::Extract->new; my $ext = $obj->extract( $template, $document);
として、Template::Extract により必要な箇所を抜き出します。抜き出した内容は $ext にネストしたハッシュリファレンスによるデータ構造として保持されます。あとはそのデータ構造を操作して、XML::RSS に渡すだけ。
それでは、どこかのサイトを RSS 化してみようということで、アップルのホットニュース を題材にしてみます。このサイトには最新ニュースが並んでいるのですが、ニュースの記事が書かれている部分の HTML ソースは同じ HTML 論理構造の繰り返しでできているので、抜き出すのもの簡単です。
通常ならここで正規表現を駆使したりするのですが、Template::Extract ですと
<title>[% title %]</title>[% ... %] <!-- Start Top News --> [% FOREACH items %] <p><b class="KB14"><a href="[% link %]">[% title %]</a></b><br /> [% description %]</p> [% END %]
という TT 形式のテンプレートを記述しておいて、先の Template::Extract->extract を呼んでやることで、データ構造として抜き取った内容が取得できます。TT はデータ構造やオブジェクトをテンプレートに渡して、出力を生成しますが、Template::Extract はその逆、テンプレートからデータ構造を作ってテンプレートに戻してくれるというわけです。
このテンプレートを apple_hotnews.tt とか保存しておいて、
perl ttrss.pl http://www.apple.co.jp/hotnews/index.html ./apple_hotnews.tt | nkf --utf8
とスクリプトを実行すると、こんな具合で RSS を生成することができました。cool。
いやー、激しくエレガンスだ! とか思っていたら Spidering Hacks にほとんど同じ内容が載ってるのを発見して鬱...。(そういえば前に Template::Extract + XML::RSS が熱いって、誰か言ってた気がする。) Spidering Hacks では、channel 要素に定義する title, link, description あたりは、テンプレートに独自に定義したヘッダとして埋め込んでおく方法がとられていました。
HTML をパースして RSS を生成という hack ですが、これとはまた別のアプローチで、正規表現を使ったものが Blog Developer's Cookbook '関心空間の RSS を作成する' で詳しく解説されています。
[PR] さくらインターネット のレンタルサーバーなら専用サーバもありますのでTemplate::Extract + XML::RSS が動作する環境の構築も安価に可能です。実際僕はここを利用しています。
今日、Google Newsのキーワード検索結果をRSS形式に変換の上、Movable TypeのページにMT-Rssfeedプラグインで表示するようにしました。このようにすると自分の興味あるキーワードに関連した最新ニュースが終えます。
HTMLのパース部分はご紹介のTemplate::Extract CPANモジュールを使った方がもっと効率よくなりそうに思いました。今度勉強してみます。