iRSSの日記

はてなダイアリーiRSSの日記の続き

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にしてみる。

screenshot
あまり知られてない気もするが、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実装のところでまだ未完成です。
できましたら、また書きます。