XML-RSS-FromHTML-0.01を使ってHTMLをRSSに変換
XML-RSS-FromHTML-0.01 - simple framework for making RSS out of HTML - metacpan.org
Toshimasa Ishibashi氏作成のモジュールで、HTMLからRSSを生成するモジュール。
指定した、URLから、HTMLを取得し、そのHTMLに対して、正規表現でRSSのItemに相当するところを抜き出す、ハンドラ(メソッド)と、XML::RSSに投入するハンドラを書けば、キャッシュも含めて面倒見てくれる。
ためしにAB-ROAD.netのフリー検索結果をRSSにしてみる。

あまり知られてない気もするが、AB-road.netでは、ツアーに対して、フリーワード検索ができる。
↓こんな感じ。
この結果戻ってくるHTMLは見た目も、RSSぽい。
今朝から初めて、約4時間ほどかかりましたが、なんとか、できました。
XML-RSS-FromHTMLのデフォルトの機能に追加して、
- キーワードクエリの追加
を行っています。
改善したいところとか、その他メモ
キャッシュの有効期間で、RSSの再生成が不要なときにエラーになってしまう。
still under check interval time period
要するにキャッシュの更新が不要な場合にこのエラーが出てしまうが、これはエラーではないはず。
更新チェックが完了しているわけだから、正常終了にしてほしい。
クエリを付け加えることで、initハンドラの役割がかげ薄くなってしまった
このモジュールではコンストラクタ内から、init()が呼び出されています。
サンプルでは、このinit()メソッド内で、取得先のHTMLやキャッシュ、RSSのファイル名を指定していますが、クエリを動的に渡す場合にはちょっと困ります。
init自体は、外部から呼ばれるようにはできていないので、僕は、クエリをセットするために、keywordメソッドを追加しました。
そうすると、initのほとんどの機能はkeywordメソッドに移ることになり、思想的にいいのかどうなのかちょっと戸惑っています。
文字化けには、本当悩まされるなあ...
XML::Paeserでパースすると、UTFフラグが必ずついてしまうようです。
このフラグは落としておかないと、文字化け原因になります。(ケースによっては化けないこともあるけれど)
unicodeDowngradeといううれしい機能があるので、このプロパティには1をセットしておきましょう。
initハンドラで
$self->unicodeDowngrade(1); #
としてみました。
しかし、ドキュメントは英語ということもあり、この一文読み落としそうになるので要注意です。
サンプルに入れといてほしいなあ..
unicodeDowngrade
Parsing of RSS files with XML::RSS (actually XML::Parser) results in utf-8 flagged strings. Setting this to a true value will take all these utf-8 flags off, which is sometimes helpfull for non-ascii language codes without using the 'encoding' pragma.
HTMLの中に検索結果が見つからなかった場合。
検索条件にヒットしなかった場合です。
makeItemList returned with 0 item - html parse failure
件数が0のときは↑このエラーが出て、RSSが生成されないんだけど、ItemがなしのRSSになっていいと思うので、これはエラーにしないでほしい。
見つかりませんでしたというItemを1件だけ持たせるのもどうかと思うので..
dc:dateはどうする?
dc:dateは自分でロジックを決めて生成する必要がある。
URLが同じRSSを読み込んだときに、RSSリーダーはこのdc:dateを見て、以前から日付が変わったら未読になったりする。(ならないものもある。個人的未整理)
なくてもいいんだけど、とりあえず、キャッシュがつくられた日でいいかな。
Atom,RSS2.0も同時に生成したいときはどうする?
まあ、これは同時に生成するといっても、別ファイルに書き出されるので、クエリで形式(1 or 2 or Atom)を与えて、そのつど生成でよさそう。
キャッシュの機能は便利
内部のロジックをまだ理解していないのですが、HTMLの変化があるときだけ再生成するようです。
このあたりの面倒さをラップしてくれたのはうれしい。
ソース モジュールAB_ROAD::Free_RSS XML::RSS::FromHTMLのサブクラス
package AB_ROAD::Free_RSS;
use base XML::RSS::FromHTML;
use Encode;
use Encode::Guess qw/euc-jp shiftjis utf8/;
use Digest::MD5 qw(md5 md5_hex md5_base64);
use strict;
sub init {
my $self = shift;
$self->cacheDir('./cache');
$self->feedDir ('./rss');
$self->unicodeDowngrade(1); #これを忘れずに!
}
sub keyword{
my $self = shift;
my ($kw,$enc) = @_;
if (!$enc){
$enc = Encode::Guess->guess($kw); #
}
Encode::from_to($kw,$enc,'utf8');
$self->{kw} = $kw;
my $kw_in_url = $self->{kw};
Encode::from_to($kw_in_url,'utf8','shiftjis',);
$kw_in_url =~ s/(\W)/'%' . unpack('H2', $1)/eg;
$self->name(md5_base64($kw_in_url));
$self->url('http://wordsearch-pkg.ab-road.net/?cs=sjis&ord=s&id=11618&kw='.$kw_in_url.'&x=0&y=0');
}
sub defineRSS {
my $self = shift;
my $xmlrss = shift;
# define your RSS using XML::RSS->channel method
$xmlrss->channel(
title => $self->{kw}.'の検索結果 AB-ROAD 海外ツアー フリーワード検索 RSS',
description => 'generated from http://ab-road.net ',
link => $self->url,
);
}
sub makeItemList {
my $self = shift;
my $html = shift;
# parse HTML and make an item list
#$html = Encode::decode('euc-jp',$html);
Encode::from_to($html,'euc-jp','utf8'); #文字コードはutf8にしておく
my @list = ();
while ($html =~ m|"#0000ff"><A HREF="(.+?)" target="_self">(.+?)</A>.*?<td class="f10">(.+?)</td>|gis){
push(@list,{
link => $1,
title => $2,
description => $3,
});
}
return \@list;
}
sub addNewItem {
my $self = shift;
my ($xmlrss,$eachItem) = @_;
# make your item using XML::RSS->add_item method
$eachItem->{link} =~ s/^\/\?go=//;
$eachItem->{link} =~ s/%([0-9a-f][0-9a-f])/pack("C",hex($1))/egi;
$xmlrss->add_item(
title => $eachItem->{title},
link => $eachItem->{link},
description => $eachItem->{description},
);
}
1;
呼び出し側のソース ここがCGIになる予定
#!/usr/bin/perl -w
use strict;
use lib qw(.);
use AB_ROAD::Free_RSS;
my $kw = 'ロスアンゼルス';
my $enc = 'utf8';
my $rss = AB_ROAD::Free_RSS->new;
$rss->keyword($kw,$enc);
unlink('*.cache');
my $result = $rss->update;
if($result){
print "successfully updated";
}else{
# i.e. "still under check interval time period"
print $rss->updateStatus;
}
で、実はCGI実装のところでまだ未完成です。
できましたら、また書きます。