HTML::Feature::Engine::LDRFullFeed - WedataにあるLDR Full FeedのSITEINFOを使ってWebページの本文を抽出するPerlモジュール
LDR Full FeedのSITEINFOがWedataに移動して便利になったので、そろそろHTML::Featureのエンジンが必要だと思って書いてみた。HTML::FeatureについてはHTML::Feature - 重要部分を抽出するモジュール - - download_takeshi’s diaryを、エンジンの拡張についてはHTML::Featureはエンジンをいろいろ拡張できるよ! - download_takeshi’s diaryを参照ください。
HTML::Feature::Engine::LDRFullFeed
koyachiさんのWebService::Wedataを使ってWedataからSITEINFOを取得、そのSITEINFOをpriority順にソートして、最初にURLがマッチしたSITEINFOのXPathでHTML::Elementをとってくるという仕組み。LDRFullFeedの今の実装みてないのでかなり適当な気もするけど、動いてるので多分大丈夫。priority→typeの仕様変更に対応。
package HTML::Feature::Engine::LDRFullFeed; use strict; use warnings; use base qw(HTML::Feature::Engine); use HTML::Feature::Result; use HTML::TreeBuilder; use HTML::TreeBuilder::XPath; use WebService::Wedata; sub run { my ($self, $c, $html_ref, $opt) = @_; die unless $opt->{url}; return $self->_extract($c, $html_ref, $opt->{url}); } sub _tag_cleaning { my ($self, $html) = @_; return unless $html; # preprocessing $html =~ s{<!-.*?->}{}xmsg; $html =~ s{<script[^>]*>.*?<\/script>}{}xmgs; $html =~ s{ }{ }xmg; $html =~ s{"}{\'}xmg; $html =~ s{\r\n}{\n}xmg; $html =~ s{^\s*(.+)$}{$1}xmg; $html =~ s{^\t*(.+)$}{$1}xmg; # control code ( 0x00 - 0x1f, and 0x7f on ascii) for ( 0 .. 31 ) { my $control_code = '\x' . sprintf( "%x", $_ ); $html =~ s{$control_code}{}xmg; } $html =~ s{\x7f}{}xmg; return $html; } sub _extract { my ($self, $c, $html_ref, $url) = @_; my $result = HTML::Feature::Result->new; my $root = HTML::TreeBuilder::XPath->new_from_content($$html_ref); my @contents; my $siteinfo = $self->_detect_siteinfo($c, $url); if ($siteinfo) { @contents = $self->_xpath2elems($siteinfo->{data}->{xpath}, $root); return unless (@contents); if (my @title = $self->_xpath2elems('//title', $root)) { $result->title($title[0]->as_text); } } else { return; } my $element = HTML::TreeBuilder->new_from_content( $self->_tag_cleaning( join '', map {$_->as_HTML('<>"&')} @contents ) ); $root->delete; $result->element($element); return $result; } sub _xpath2elems { my ($self, $xpath, $context) = @_; my $nodes; if (eval { $nodes = $context->findnodes($xpath); }) { return $nodes->get_nodelist; } return; } sub _detect_siteinfo { my ($self, $c, $url) = @_; my $wedata = WebService::Wedata->new; $wedata->{ua} = $c->user_agent; my $i = 0; my %priority = qw/ SBM 1000 INDIVIDUAL 100 IND 100 SUBGENERAL 10 SUB 10 GENERAL 1 GEN 1 /; my $db = $wedata->get_database('LDRFullFeed'); for my $item ( sort { $a->{data}->{priority} <=> $b->{data}->{priority} } map { $_->{data}->{priority} ||= ($_->{data}->{type}) ? $priority{$_->{data}->{type}} : 0; $_; } @{$db->get_items} ) { if (($item->{data}->{url}) && ($url =~ /$item->{data}->{url}/)) { return $item; } } return; } 1;
HTML::Featureをちょっといじる
HTML::Featureはデフォではuser_agentが変更できないのと、Engineが自分の処理してるページのURLを知る事ができないって仕様になってて、特に後者はSITEINFOを使う上で致命的になる。ので$self->engine->runにオプションも渡せるようにして、HTML::Feature::Engine::LDRFullFeed側でそこからURLを貰ってくるという実装にしてます。でもこれだとHTML::Feature::parseにURLを2個渡さなくちゃいけなくてださいので、なんとかしてほしいなー>id:download_takeshi
package HTML::Feature_; use base qw/HTML::Feature/; sub user_agent {_user_agent(@_)}; sub _user_agent { my $self = shift; return $self->{user_agent} ||= SUPER->_user_agent; } sub _run { my ($self, $html_ref, $opts) = @_; $opts ||= {}; local $self->{element_flag} = exists $opts->{element_flag} ? $opts->{element_flag} : $self->{element_flag}; $self->engine->run($self, $html_ref, $opts); }
extract.pl
今の仕様だとテスト書くのがめんどくさかったので、とりあえず適当にコマンドラインで使えるサンプルをオレオレtsubuanをベースに書いた。たぶんCGIでもうごきます。CGIで動かしたときの仕様はtsubuan互換、コマンドラインでの実行例は以下のようなかんじ。SITEINFOが存在しなかったときにはデフォルトEngineのTagStructureが動くようにしてます。
% perl extract.pl http://d.hatena.ne.jp/fuba/20070401/1175418910 LDRFullFeed <html><head></head><body><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%c3%e6%b3%d8%c0%b8">中学生</a>からは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%a4%cf%a4%c6%a4%ca%a5%a2%a5%f3%a5%c6%a5%ca">はてなアンテナ</a>! <a class="keyword" href="http://d.hatena.ne.jp/keyword/%a4%cf%a4%c6%a4%ca%a5%a2%a5%f3%a5%c6%a5%ca%cd%df%a4%b7%a4%a4%a1%aa">はてなアンテナ欲しい!</a><p class="sectionfooter"><a href="/fuba/20070401/1175418910">Permalink</a> | <a href="/fuba/20070401/1175418910#c">コメント(0)</a> | <a href="/fuba/20070401/1175418910#tb">トラックバック(0)</a> | 18:15</body></html>