未感想管理システム

昨日、こんなことを考えてた。

  • kkobayashi 大体イメージできてきた。後は実装だな about 14 hours ago from tmitter
  • kkobayashi たまに気まぐれで感想書く回があったりするけど…それは見切りをこまめにupdateすることで対応するか about 14 hours ago from tmitter
  • kkobayashi 1ダイアリーからシリーズもののタイトルと話数を抜き出す 2カレンダーから1にマッチするものをリストする でOKかな ... about 14 hours ago from tmitter
  • kkobayashi ダイアリーなら向こう50日のエントリーから、ローカルなら全探索で。しょぼいカレンダーのエントリーは…ローカルにリストするか、手頃なエントリーにアイキャッチャーをつけるか。特にリモートへのこだわりはないが…SPOCの観点からはリモートか。 ... about 14 hours ago from tmitter
  • kkobayashi 未感想管理システムを考える。ローカルのテキストとしょぼいカレンダーを連携させる、またはダイアリーを連携させる。 ... about 14 hours ago from tmitter

最近感想たまりまくってて、今何話目で、どの回の感想を書いて、どの回の感想を書いてなくて・・・という管理が全然できてない。それをいちいち調べるのに1時間くらいかかる始末で、今ひとつ効率が悪い。何より、その1時間でアニメ2本見れるよ・・・。
ということで、考えてみました。一番簡単なのは、ダイアリーとしょぼいカレンダーをリンクさせて、感想を書いてない回をピックアップすると。ダイアリーの作品名としょぼいカレンダーの作品名の対応付けが一番悩んだんだけど、それは「視聴リスト」の表にしょぼいカレンダーのリンクを張ることで対応しようかと。これなら視聴リストをタイムリーにアップデートする必要性も出てきて一石二鳥。
で、実装なのだけど。ここは一つWeb2.0的にAPIを使ってみるかと思ってて、最近公開されたばかりのはてなダイアリーAtomPubでも使ってみるか!と思ったのだけど・・・XML::Atomがうまくインストールできない(こんな感じのエラーが出る)ので、やめることにした。Cygwinはこういうとき困る・・・。ま、WSSE認証とかめんどくさいしね!パスワードとか送りたくないしね!*1
ということで、地道に*2Web::Scraperでもってくることにしました。

use strict;
use Web::Scraper;
use URI;
use Encode;
use Data::Dumper;
use utf8;

my $title_list   = "http://d.hatena.ne.jp/kkobayashi/archive";
my $program_list = "http://d.hatena.ne.jp/kkobayashi/20080831/p1";

## main
my ($names, $programs) = get_program_list($program_list);
my $titles   = get_title_list($title_list);
my %reviewed = ();

foreach my $header (@$titles){
  foreach my $name (keys %$programs){
    if($header =~ /$name.*?(\d+)/){
#      print encode("sjis", $name), " - $1\n";
      $reviewed{$name} = $1 if($reviewed{$name} < $1);
    }
  }
}

my $output ="\n";
foreach my $name (@$names){
  my $subtitles = get_calender($programs->{$name});
  foreach my $number (sort {$a <=> $b} keys %$subtitles){
    $output .= sprintf("%s 第%02d話「%s」\n", $name, $number, $subtitles->{$number}) if($number > $reviewed{$name});
  }
}
print encode("sjis", $output);


## subroutine
sub get_title_list{
  my $uri = URI->new(shift);
  print "scraping $uri ...\n";
  my $scraper = scraper {
    process 'li.archive-section', 'list[]', 'TEXT';
  };
  return $scraper->scrape($uri)->{list};
}

sub get_program_list{
  my $uri = URI->new(shift);
  print "scraping $uri ...\n";
  my $scraper = scraper {
    process '//div[@class="section"]/table/tr/td[3]', 'names[]', 'TEXT';
    process '//div[@class="section"]/table/tr/td[4]/a', 'programs[]', '@href';
  };
  my $result = $scraper->scrape($uri);
  my $table  = {};
  @$table{@{$result->{names}}} = @{$result->{programs}};  # name => URI
  return ($result->{names}, $table);
}

sub get_calender{
  my $uri = shift;
  print "scraping $uri ...\n";
  my $scraper = scraper {
    process '//tr[ @class="past" ]/td[4]', 'number[]', 'TEXT';
    process '//tr[ @class="past" ]/td[5]/text()', 'title[]', 'TEXT';
  };
  my $result = $scraper->scrape($uri);
  my $table  = {};
  @$table{@{$result->{number}}} = @{$result->{title}};  # number => title
  return $table;
}

実行すると、こんな感じに出力が出てきます。

S・A〜スペシャル・エー〜 第21話「敵う・叶う」
ストライクウィッチーズ 第08話「君を忘れない」
恋姫†無双 第08話「関羽黄忠の企みを阻まんとするのこと」
セキレイ 第09話「比礼ト風」
ひだまりスケッチ×365 第03話「5月27日 狛モンスター」
ひだまりスケッチ×365 第04話「3月16日?23日 まろやかツナ風味/10月31日 ガガガガ」
ひだまりスケッチ×365 第05話「3月25日 おめちか」
ひだまりスケッチ×365 第06話「7月30日 さえ太/11月11日 ヒロえもん」
ひだまりスケッチ×365 第07話「4月7日 入学式と歓迎会」
ひだまりスケッチ×365 第08話「10月13日 お山の大将」
ひだまりスケッチ×365 第09話「8月5日 ナツヤスメナーイ/12月3日 裏新宿の狼 PART II」
きらりん☆レボリューション 第124話「ひえひえ! ペンギンさんいらっしゃ?い」
鉄腕バーディー DECODE 第09話「The champion of justice」
マクロスF 第19話「トライアングラーマクロスF 第20話「ダイアモンド・クレバス」
マクロスF 第21話「蒼のエーテルしゅごキャラ! 第45話「がんばれ!誠一郎!」
しゅごキャラ! 第46話「りま降臨!?お笑いの神様!」
しゅごキャラ! 第47話「あたしが歌唄のマネージャー!?」
テレパシー少女 蘭 第10話「蘭と翠と夏休み」
Yes! プリキュア5 第29話「高原でイケメンとテニス!?」
絶対可憐チルドレン 第22話「孟母三遷!皆本、凶弾に散る!?」
隠の王 第13話「眠らない学舎」
隠の王 第14話「夜の底」
隠の王 第15話「別れの朝」
隠の王 第16話「遠来の客」
隠の王 第17話「決壊の時」
隠の王 第18話「呼ぶ声」
隠の王 第19話「死神の横顔」
隠の王 第20話「戸隠へ」
隠の王 第21話「野望」

ふーむ、これは便利。
どうでもいいけど、これ作るのに大体2時間くらい。数億円のシステムを2時間で!みたいな話が少し前に話題になってたけど、俺が2時間で作れるのはせいぜいこんなもんだ。

*1:負け惜しみ

*2:地道に・・・といっても、Web::Scraperが死ぬほど便利なのはコードを見れば分かるかと・・・。なんだこれ便利すぎ