Hatena::Groupfragments

甘くておいしいおイモだよー このページをアンテナに追加 RSSフィード

2006-10-21

Plagger::Plugin::CustomFeed::MelonBooks 15:49 Plagger::Plugin::CustomFeed::MelonBooks - 甘くておいしいおイモだよー を含むブックマーク はてなブックマーク - 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

f:id:SweetPotato:20061021160745p:image

謝辞

コードはPlagger::Plugin::Subscription::Toranoana - fubaはてなを多分に参考にしました。d:id:fubaさんに感謝します。

otsuneotsune2006/10/22 02:47定義的にSubscriptionじゃなくてCustomFeedか?

SweetPotatoSweetPotato2006/10/22 03:33> Subscription: Load subscription (list of feeds)
> CustomFeed: Parse non-RSS format
言われてみればそうですね。訂正しました。
あと,はてブコメント欄の
> 野良じゃなくちゃんと作るのならCustomFeed::Configあたりかな
の指摘ですが,取得対象のリストが複数のページに分かれているので,CustomFeed::Configでは難しいと思い,このようにしました。

fubafuba2006/10/22 11:25お、素晴らしいです。
PPS::ToranoanaがSubscriptionなのは、実店舗のフィードも含めて複数フィード生成できるからなので、これなら確かにCustomFeedですね。