2006-10-21
■ Plagger::Plugin::CustomFeed::MelonBooks

【追記(11/14 13:26)】このエントリの最新版は CustomFeed::MelonBooks - SweetPotato::Plagger - Plaggerグループに移動しました。以後,こちらのエントリは更新しません。
【追々記(10/22 17:02)】同人誌,同人ソフト,同人グッズのリストに対応した。
【追記(10/22 3:28)】id:otsuneさんの指摘を受け,Subscription→CustomFeedとした。
メロンブックス通信販売のページの入荷日別アイテムリストを取得し,アイテム毎にエントリを作成するP::P::SubscriptionCustomFeedを作った。
現在は同人誌のリストしか取得していない。新入荷の同人誌が無い場合はdieになる点に注意。同人ソフトなどの他のリストの取得については時間があれば対応予定。同人誌,同人ソフト,同人グッズのリストに対応した。
Plagger/Plugin/CustomFeed/MelonBooks.pm
package Plagger::Plugin::CustomFeed::MelonBooks; use strict; use warnings; use base qw( Plagger::Plugin ); use Plagger::Util; use Plagger::Feed; use Plagger::Entry; use URI; use HTML::TreeBuilder::XPath; sub register { my ($self, $context) = @_; $context->register_hook( $self, 'subscription.load' => \&load, ); } sub load { my($self, $context) = @_; my $day = $self->conf->{day} || _today(time); my $feed = Plagger::Feed->new; $feed->aggregator( sub { $self->aggregate($context, $day); }); $context->subscription->add($feed); } sub aggregate { my($self, $context, $day) = @_; my $feed = Plagger::Feed->new; $feed->title("MelonBooks $day"); $day =~ s!/!%2F!g; $self->_parse_items($context, $feed, $day, "%C6%B1%BF%CD%BB%EF", 1); # Dojinshi $self->_parse_items($context, $feed, $day, "%C6%B1%BF%CD%A5%BD%A5%D5%A5%C8", 0); # Dojin Soft $self->_parse_items($context, $feed, $day, "%C6%B1%BF%CD%A5%B0%A5%C3%A5%BA", 0); # Dojin Goods $context->update->add($feed); } sub _parse_items { my($self, $context, $feed, $arrival, $genre, $is_dojinshi) = @_; my $dispstart = 0; while(1) { my $uri = URI->new("http://shop.melonbooks.co.jp/tsuhan/system/list.php?RATED=18&DISPACTION=disppage&ARRIVAL=$arrival&DISPSTART=$dispstart&GENRE=$genre&DISPEMPTY=&CHGRATE=&DISPSTARTCHG=0&DISPORDER=maker&DISPPAGE=10&DISPSTYLE=desc"); my $data = Plagger::Util::load_uri($uri); if($data =~ /<OPTION VALUE="(\d+)" SELECTED >/) { last if $dispstart != $1; } else { last }; my $tree = HTML::TreeBuilder::XPath->new; $tree->parse($data); my $path = '//body//table[@bordercolor="white"]'; my $booknodes; eval { $booknodes = $tree->findnodes($path); } or die $@; for my $item (@$booknodes) { $self->_add_item($context, $feed, $item, $is_dojinshi); } $dispstart += 10; } } sub _add_item { my($self, $context, $feed, $item, $is_dojinshi) = @_; my $html = $item->as_HTML('<>&'); my ($id, $title, $circle, $tags); if($is_dojinshi && $html =~ m!<td bgcolor="whitesmoke" valign="middle"><.*?>(.*?)</.*?>.*?<td bgcolor="whitesmoke" valign="middle"><.*?><.*?>(.*?)</.*?>.*?<td bgcolor="whitesmoke" valign="middle"><.*?>(.*?)</.*?>.*?<td bgcolor="whitesmoke" valign="middle"><.*?><.*?>(.*?)</.*?>.*?!) { ($title, $circle, $id, $tags) = ($1, $2, $3, [$4]); } elsif($html =~ m!<td bgcolor="whitesmoke" valign="middle"><.*?>(.*?)</.*?>.*?<td bgcolor="whitesmoke" valign="middle"><.*?><.*?>(.*?)</.*?>.*?<td bgcolor="whitesmoke" valign="middle"><.*?>(.*?)</.*?>.*?<td bgcolor="whitesmoke" valign="middle"><.*?><.*?>(.*?)</.*?>.*?<td bgcolor="whitesmoke" valign="middle"><.*?><.*?>(.*?)</.*?>.*?!) { ($title, $circle, $id, $tags) = ($1, $2, $3, [$4, $5]); } else { return }; my $link = "http://shop.melonbooks.co.jp/tsuhan/system/detail.php?RATED=18&ITEM_ID_FULL=$id"; my $body = join '', map { qq!<img src="http://shop.melonbooks.co.jp/img/$id$_.gif" />! } ("", "a", "b"); my $entry = Plagger::Entry->new; $entry->title($title); $entry->link($link); $entry->body($body); $entry->author($circle); $entry->tags($tags); $feed->add_entry($entry); } sub _today { my @t = localtime(shift); my $yyyymmdd = sprintf("%04d/%02d/%02d", $t[5] + 1900, $t[4] + 1, $t[3]); return $yyyymmdd; } 1;
config.melonbooks.yaml
- module: CustomFeed::MelonBooks config: day: 2006/10/19
dayのフォーマットは%Y/%m/%dで。省略した場合は今日入荷したアイテムの情報を取得する。
スクリーンショット(Publish::Gmail)
謝辞
コードはPlagger::Plugin::Subscription::Toranoana - fubaはてなを多分に参考にしました。d:id:fubaさんに感謝します。
トラックバック - http://fragments.g.hatena.ne.jp/SweetPotato/20061021

> CustomFeed: Parse non-RSS format
言われてみればそうですね。訂正しました。
あと,はてブコメント欄の
> 野良じゃなくちゃんと作るのならCustomFeed::Configあたりかな
の指摘ですが,取得対象のリストが複数のページに分かれているので,CustomFeed::Configでは難しいと思い,このようにしました。
PPS::ToranoanaがSubscriptionなのは、実店舗のフィードも含めて複数フィード生成できるからなので、これなら確かにCustomFeedですね。