県内図書書像マップの関連図書検索のロジックを変えてみた
WebcatPlusにISBNを喰わせて連想検索を行い、それぞれのISBNと書名をJSONで返す部分を書き直した。
以前、Web::Scraperでスプレイプしようとした時に図書情報のページの構造がtableのネストが多く、また書名やISBNの位置がかなり不定なのでちょっとムリかなと思っていたが、久しぶりにWebcat Plusが吐くHTMLを眺めたら必要な文字列にすべて同一のclass名が付いていることを発見
大雑把にこんな感じにコードを書くと
my $uri = URI->new($ISBN_SEARCH_URL_PREFIX . $isbn . $ISBN_SEARCH_URL_SUFFIX); print Dump scraper { process '//a[@href=~/DocDetail/]', 'values' => ['@href', \&get_detail]; }->scrape($uri); sub get_detail{ my $associative = scraper { process '//font[@class="fs100"]', 'values[]' => 'TEXT'; result 'values'; }->scrape($_); }
まるっといい感じで取れる
--- values: - ' フィリップ K.ディック著 ; 友枝康子訳 -- 早川書房, 1989.2, 375p. -- (ハヤカワ文庫 ; SF-807) ' - '<BA33052047>' - ' 所蔵図書館 11館' 〜 略 〜 - タイトル - ' 流れよわが涙、と警官は言った [ナガレヨ ワガ ナミダ ト ケイカン ワ イッタ] ' - ' (ハヤカワ文庫 ; SF-807) ' - 責任表示 - ' フィリップ K.ディック著 ; 友枝康子訳 ' - 資料種別 - ' ' 〜 略 〜 - 形態事項 - ' 375p ; 16cm ' - ISBN - ' 4150108072 ' - 内容著作注記 - ' ' 〜 略 〜
必ず"タイトル"の後に書名、"ISBN"のあとにISBN(付番されてないときはないが)が存在
頭から配列舐めていって"タイトル"と"ISBN"の次のデータを引っ張るればもってこれる
フラグ作って読み飛ばしとか格好わるいしなと思ったが、List::UtilかList::MoreUtilsに何かあるはず。
first_index BLOCK LIST
Returns the index of the first element in LIST for which the criterion in BLOCK is true. Sets $_ for each item in LIST in turn:
my @list = (1, 4, 3, 2, 4, 6);
printf "item with index %i in list is 4", firstidx { $_ == 4 } @list;
__END__
item with index 1 in list is 4Returns -1 if no such item could be found.
first_index is an alias for firstidx.
http://search.cpan.org/~vparseval/List-MoreUtils-0.22/lib/List/MoreUtils.pm
で、最終的にこう書いた。多分まだまだ直す余地あり
#!/usr/bin/perl use FindBin::libs; use strict; use warnings; use Business::ISBN; use CGI; use Data::Alias; use Encode; use JSON; use List::MoreUtils qw( first_index ); use Readonly; use URI; use Web::Scraper; Readonly my $ISBN_SEARCH_URL_PREFIX => 'http://webcatplus-equal.nii.ac.jp/libportal/EqualFromForm?' . 'hdn_from=top&txt_title=&radio_partialMatch=1&txt_author=&' . 'authorPartialMatch=0&txt_publisher=&txt_year1=&txt_year2=&' . 'txt_isbn=' ; Readonly my $ISBN_SEARCH_URL_SUFFIX => '&txt_keyword=&check_book=on&check_magazine=on&' . 'select_sorttype=0&select_dmax=10&x=0&y=0' ; # Associative Search Readonly my $ASSOCIATIVE_SEARCH_URL_PREFIX => 'http://webcatplus.nii.ac.jp/assoc.cgi?hdn_mode=equal_assoc&' . 'select_dmax=20&check_dsel=1%2C' ; Readonly my $BLANK => q{}; my $json = new JSON; $json->pretty; my $details; my $obj = { flg => JSON::false, }; my $q = new CGI; my $isbn = $q->param('isbn'); # ISBNから連想検索用のNCID等を取得 my $uri = URI->new($ISBN_SEARCH_URL_PREFIX . $isbn . $ISBN_SEARCH_URL_SUFFIX); my $uris = scraper { process '//a[@href=~/DocDetail/]', 'values' => ['@href', \&get_associative]; result 'values'; }->scrape($uri); # 連想検索の結果からそれぞれの詳細ページ for my $url (@{$uris}) { $details = scraper { process '//font[@class="fs100"]', 'values[]' => ['TEXT', \&trim]; result 'values'; }->scrape($url); set_title(); } # テーブルが一件でもあればTrue if ($obj->{series}) { $obj->{flg} = JSON::true; } print $q->header(-type => "application/x-javascript; charset=utf-8"); print $json->canonical->encode($obj); sub get_associative{ # uriの=以降がNCID my (undef, $ncid) = split /=/, $_->as_string; my $uri = URI->new($ASSOCIATIVE_SEARCH_URL_PREFIX . $ncid); my $associative = scraper { process '//a[@href=~/DocDetail/]', 'values[]' => '@href'; result 'values'; }->scrape($uri); } sub trim { my $val = $_; $val =~ s{\A \s* | \s* \z}{}gxm; $val = encode('UTF-8', $val); return $val; } sub set_title { my $ix = first_index{ $_ eq 'ISBN' } @{ $details }; return if @{ $details }[$ix + 1] =~ /^[^\d{9]/; my $temp = @{ $details }[ $ix + 1 ]; $temp =~ s{ \s | [)] }{}gxm; $temp =~ s{ [(] }{>}gxm; my @tbls = split /[,]/, $temp; # タイトルを取得(読みは除く) $ix = first_index{ $_ eq 'タイトル' } @{ $details }; my ($title, undef) = split /\s\[/, @{ $details }[ $ix + 1 ]; for my $tbl (@tbls) { my ($cd, $sub_ttl) = split /[>]/, $tbl; next unless $cd; # 常にISBN-10に変換(ASINとして用いるため) my $isbn = Business::ISBN->new($cd)->as_isbn10->isbn; if ($sub_ttl) { ${ $obj->{series} }{ $isbn } = $title . ' ' . $sub_ttl; } else { ${ $obj->{series} }{ $isbn } = $title; } } }
多分あと二回ぐらいゼロから書き直すと結構マシになりそう