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実装のところでまだ未完成です。
できましたら、また書きます。