SweetPotato::Plagger このページをアンテナに追加 RSSフィード

2008-01-07

[][] Web::Scraper for CustomFeed::Script 栗田出版販売コミック刊行予定情報  Web::Scraper for CustomFeed::Script 栗田出版販売コミック刊行予定情報 - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  Web::Scraper for CustomFeed::Script 栗田出版販売コミック刊行予定情報 - SweetPotato::Plagger  Web::Scraper for CustomFeed::Script 栗田出版販売コミック刊行予定情報 - SweetPotato::Plagger のブックマークコメント

このコードの最新版はCodeReposに置いてあります。

まんがの森コミックリストよりも情報が早いと噂の栗田出版販売コミック刊行予定情報をWeb::Scraper+Plaggerでenjoy!

assets/plugins/CustomFeed-Script/bookkurita-comicdb.pl

シンタックスハイライトするとなぜか表示されないので普通のスーパーpreで。見にくくてサーセン。

#!/usr/local/bin/perl
# author: SweetPotato
use strict;
use warnings;
use utf8;

use DateTime;
use Encode qw( decode );
use URI;
use Web::Scraper 0.22;
use YAML;

my $url = 'http://www3.kuradashi-shinkan.com/kanko/comicdb.asp';

my $s = scraper {
    my $publisher;
    process '//body/div/center/table/tr[not(@align)]',
      'entry[]' => scraper {
        process '/tr/td[position()=1 or position()=2]',
          'tags[]' => ['text', \&trim];
        process '/tr/td[1]', publisher => ['text', \&trim];
        process '/tr/td[2]', series => ['text', \&trim];
        process '/tr/td[3]', title  => ['text', \&trim];
        process '/tr/td[4]', author => ['text', \&trim];
        process '/tr/td[5]', price  => ['text', \&trim];
        process '/tr/td[6]',
          date => ['text', \&trim, \&mk_date],
          part_or_day => ['text', \&trim, sub { m!/(.*?)$!; $1 } ];
        process '/tr/td[7]', isbn => ['text', \&trim];
        result qw( tags publisher series title author price date part_or_day isbn );
    };
    result qw( entry );
};
$s->user_agent->env_proxy;

my $res = $s->user_agent->get($url);
unless ($res->is_success) {
    die "GET $url failed: " . $res->status_line;
}

my @entry = @{ $s->scrape(decode('cp932', $res->content)) || [] };
for my $e (@entry) {
    $e->{body} = &mk_body($e);
    delete $e->{$_} for qw( publisher series price part_or_day isbn );
}

binmode STDOUT, ":utf8";
print YAML::Dump +{
    title => '栗田出版販売 コミック刊行予定情報',
    link  => $url,
    entry => \@entry,
};

# guess year
sub mk_date {
    my ($month, $day) = (shift =~ m!(.*)/(.*)!) or return;
    $day = &part_to_day($day);

    my $today = DateTime->now(time_zone => 'Asia/Tokyo')->truncate(to => 'day');
    my $this = $today->clone->set(month => $month, day => $day);
    my $last = $this->clone->subtract(years => 1);
    my $next = $this->clone->add(years => 1);
    my @date = sort { DateTime::Duration->compare($a->[1], $b->[1], $today) }
               map { [$_->[0], $_->[1]->is_positive ? $_->[1] : $_->[1]->inverse ] }
               map { [$_, $today - $_] } ($this, $last, $next);

    $date[0]->[0]->ymd;
}

sub mk_body {
    my $entry = shift;

    $entry->{part_or_day} =~ /^\d+$/
        ? join ', ', map { $entry->{$_} } qw( author publisher series price isbn )
        : join ', ', map { $entry->{$_} } qw( part_or_day author publisher series price isbn );
}

sub part_to_day {
    $_ = shift;
    return $_ if /^\d+$/;
    return 21 if /下/;
    return 11 if /中/;
    return 1;
}

sub trim { s/^\s*|\s*$//g; $_ }

config.yaml

plugins:
  - module: Subscription::Config
    config:
      feed:
        - url: 'script:/path/to/bookkurita-comicdb.pl'

  - module: CustomFeed::Script

  - module: Publish::iCal
    config:
      dir: /path/to/dir
      filename: kurita.ics
トラックバック - http://plagger.g.hatena.ne.jp/SweetPotato/20080107

2007-12-13

[][] Web::Scraper for CustomFeed::Script はてなハイク  Web::Scraper for CustomFeed::Script はてなハイク - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  Web::Scraper for CustomFeed::Script はてなハイク - SweetPotato::Plagger  Web::Scraper for CustomFeed::Script はてなハイク - SweetPotato::Plagger のブックマークコメント

はてなハイクメイン(真ん中のエントリーが並ぶ部分)のHTML構造を変更しました。これまでの、ULタグでの構造から、DIVを使った構造に変更になっています。

ページ上部のメニュー変更について - はてなハイク日記

これに合わせて修正した。

さきほどはてなハイクでRSSフィードの出力を開始しました。

  • キーワード、asin,httpページ
  • ユーザーページ

それぞれで、RSSフィードの配信をしています。RSSファイルのURLは、ヘッダ内のlinkをご覧ください。

RSSフィードの配信開始について - はてなハイク日記

とのこと。ニーズに応じてRSSとPlaggerを使いこなすべし。

  • プロフィールアイコンをセットするようにした。
  • configを修正した。

おまけでPipes版も作った。

新サービス・はてなハイクをスクレイプ。

新着ページキーワードページユーザページFollowingProfileのどれでもうまくいくはず。

まだβだからHTMLの構造が変わってスクレイパが使えなくなるかもしれんので注意。

assets/plugins/CustomFeed-Script/hatena-haiku.pl

#!/usr/local/bin/perl
# author: SweetPotato
use strict;
use warnings;

use DateTime::Format::MySQL;
use DateTime::Format::W3CDTF;
use URI;
use Web::Scraper 0.22;
use YAML;

my $url = shift or die;

my $s = scraper {
    process '//title', title => 'text';
    process '//*[@class="entry" and not(.//div[@class="input"])]',
      'entry[]' => scraper {
        process 'img.profile-image',
          icon => ['@src', sub { +{ url => $_->as_string } } ];
        process '.title', title => 'text';
        process '.body', body => 'html';
        process '.username a', author => 'text';
        process '.timestamp a', link => ['@href', sub { $_->as_string } ];
        process '.timestamp', date => ['text', \&mk_date];
        result qw( icon title body author link date );
    };
    result qw( title entry );
};
$s->user_agent->env_proxy;

my $res = $s->scrape(URI->new($url));

binmode STDOUT, ':utf8';
print YAML::Dump +{
    title => $res->{title},
    link  => $url,
    entry => $res->{entry},
};

sub mk_date {
    my $dt = DateTime::Format::MySQL->parse_datetime(shift);
       $dt->set_time_zone('Asia/Tokyo');
    DateTime::Format::W3CDTF->format_datetime($dt);
}

config.yaml

plugins:
  - module: Subscription::Config
    config:
      feed:
         - url: 'script:/path/to/hatena-haiku.pl http://h.hatena.ne.jp/'

  - module: CustomFeed::Script

  - module: Filter::ResolveRelativeLink
トラックバック - http://plagger.g.hatena.ne.jp/SweetPotato/20071213

2007-10-29

[] tumblrのAPIを叩いてCustomFeed::Script用にフィード化するPerlスクリプト 05:24  tumblrのAPIを叩いてCustomFeed::Script用にフィード化するPerlスクリプト - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  tumblrのAPIを叩いてCustomFeed::Script用にフィード化するPerlスクリプト - SweetPotato::Plagger  tumblrのAPIを叩いてCustomFeed::Script用にフィード化するPerlスクリプト - SweetPotato::Plagger のブックマークコメント

への反応:

API用意されてるのになんでRSSたたくんだろう。

それplaggerで、やることじゃないんじゃいかな。 - sasezakiの日記 - たんぶら部 - Tumblove -

が至極ごもっともだと思ったので,APIを叩く版を作った。

assets/plugins/CustomFeed-Script/tumblr.pl

引数は3つ。

  1. ユーザ名 or URL
    • 例:sweetpotato,http://sweetpotato.tumblr.com/,http://tumblelog.marco.org/
  2. エントリ数(省略可能)
    • 正の整数を指定する。省略時は20が使用される。10単位での指定が望ましい。エントリは10件ずつAPIを通じて取得し,取得した分は無駄なく結果として返すので,指定した数以上のエントリが返される場合もある。
  3. タイプ(省略可能)
    • regular,quote,photo,link,conversation,videoのいずれかを指定することで,そのタイプの投稿のみを取得する。これらはそれぞれtumblrのDashboardの「Add a new...」の左から順に対応している。省略時は全てのタイプの投稿を取得する。
#!/usr/bin/perl
# author: SweetPotato
use strict;
use warnings;

use DateTime::Format::HTTP;
use FindBin;
use File::Spec::Functions qw/catfile/;
use HTML::Template;
use LWP::UserAgent;
use List::MoreUtils qw/any/;
use URI;
use Switch;
use XML::TreePP;
use YAML;

my $user = shift or die "specify user or url";
$user = "http://$user.tumblr.com" unless $user =~ m!^http://!;
die "bad url: $user" unless $user =~ m!^http://[^/]+/?$!;
$user =~ s!/$!!;

my $num = @ARGV ? shift : 20;
die "specify positive number: $num" unless $num > 0;

my $type = @ARGV ? shift : '';
if ($type ne '') {
    any { $type eq $_ } qw/regular quote photo link conversation video/
    or die "type must be one of regular, quote, photo, link, conversation, or video: $type";
}

my $ua = LWP::UserAgent->new;

my ($title, $timezone, @entry);

for (my $start = 0; $start < $num; $start += 10) {
    my $url = URI->new("$user/api/read");
       $url->query_form(start => $start, num => 10, type => $type);
    my $res = $ua->get($url);
    unless ($res->is_success) {
        warn "GET $url failed: " . $res->status_line;
        last;
    }

    my $parser = XML::TreePP->new;
       $parser->set(utf8_flag => 1);
    my $tree = $parser->parse($res->content);

    $title = $tree->{tumblr}->{tumblelog}->{'-title'}
        unless defined $title;
    $timezone = $tree->{tumblr}->{tumblelog}->{'-timezone'}
        unless defined $timezone;

    for (@{ $tree->{tumblr}->{posts}->{post} || [] }) {
        # pre-process for template
        if ($_->{'-type'} eq 'photo') {
            $_->{'photo-url'} = $_->{'photo-url'}->[0]->{'#text'};
        }
        if ($_->{'-type'} eq 'conversation') {
            $_->{'conversation-text'} =~ s!\r\n!<br />!g;
        }

        push @entry, +{
            link  => $_->{'-url'},
            date  => &mk_date($_->{'-date'}, $timezone),
            title => &mk_title($_),
            body  => &mk_body($_),
        };
    }

    # fetch next ?
    last if $tree->{tumblr}->{posts}->{'-start'} < $start;
}

binmode STDOUT, ":utf8";
print YAML::Dump +{
    title => $title,
    link  => "$user/",
    entry => \@entry,
};

sub mk_title {
    my $post = shift;

    switch ($post->{'-type'}) {
        case 'regular' { return $post->{'regular-title'} || 'regular post' }
        case 'quote' { return 'quote post' }
        case 'photo' { return 'photo post' }
        case 'link' { return $post->{'link-text'} || 'link post' }
        case 'conversation' { return $post->{'conversation-title'} || 'conversation post' }
        case 'video' { return 'video post' }
        else { return }
    }
    return;
}

sub mk_date {
    my ($date, $timezone) = @_;

    DateTime::Format::HTTP->format_datetime(
        DateTime::Format::HTTP->parse_datetime($date, $timezone)
    );
}

sub mk_body {
    my $post = shift;

    my $t = HTML::Template->new(
        filename => catfile($FindBin::Bin, 'tumblr.t'),
        die_on_bad_params => 0,
    );
    $t->param($post);
    return $t->output;
}

assets/plugins/CustomFeed-Script/tumblr.t

HTML::Template用テンプレート。

<TMPL_IF NAME="regular-body">
    <TMPL_VAR NAME="regular-body">
</TMPL_IF>

<TMPL_IF NAME="photo-url">
    <img src="<TMPL_VAR ESCAPE=HTML NAME="photo-url">" />
    <TMPL_IF NAME="photo-caption">
        <br /><br /><TMPL_VAR NAME="photo-caption">
    </TMPL_IF>
</TMPL_IF>

<TMPL_IF NAME="quote-text">
    &#8220;<TMPL_VAR NAME="quote-text">&#8221;
    <TMPL_IF NAME="quote-source">
        <br /><br />&mdash; <TMPL_VAR NAME="quote-source">
    </TMPL_IF>
</TMPL_IF>

<TMPL_IF NAME="link-url">
    <a href="<TMPL_VAR ESCAPE=HTML NAME="link-url">">
        <TMPL_IF NAME="link-text">
            <TMPL_VAR ESCAPE=HTML NAME="link-text">
        <TMPL_ELSE>
            <TMPL_VAR ESCAPE=HTML NAME="link-url">
        </TMPL_IF>
    </a>
    <TMPL_IF NAME="link-description">
        <br /><br /><TMPL_VAR NAME="link-description">
    </TMPL_IF>
</TMPL_IF>

<TMPL_IF NAME="conversation-text">
    <TMPL_VAR NAME="conversation-text">
</TMPL_IF>

<TMPL_IF NAME="video-player">
    <TMPL_VAR NAME="video-player">
    <TMPL_IF NAME="video-caption">
        <br /><br /><TMPL_VAR NAME="video-caption">
    </TMPL_IF>
</TMPL_IF>

config.yaml

エロい例。

plugins:
  - module: Subscription::Config
    config:
      feed:
        - url: 'script:/path/to/tumblr.pl moeura 100 photo'
        - url: 'script:/path/to/tumblr.pl detailura 100 photo'
        - url: 'script:/path/to/tumblr.pl detailurac 100 photo'

  - module: CustomFeed::Script

  - module: Filter::FindEnclosures
  - module: Filter::FetchEnclosure
    config:
      dir: /path/to/enclosure
      fake_referer: 1

ぱふぱふ2008/05/19 14:23ntifier って何ですか?

SweetPotatoSweetPotato2008/05/19 20:06ぱふさん,ご指摘ありがとうございます。Perlコードの貼り付けがうまくいっていませんでした。該当箇所を修正しました。
「ntifier」,私も確認しましたが,何でしょうね……。コード中にはそのような部分文字列すら存在しないミステリー……。

ぱふぱふ2008/05/19 22:05早速の対応ありがとうございます。
コードを見て勉強させていただきます。

トラックバック - http://plagger.g.hatena.ne.jp/SweetPotato/20071029

2007-10-28

[][] Web::Scraper and EFT 無表情 01:45  Web::Scraper and EFT 無表情 - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  Web::Scraper and EFT 無表情 - SweetPotato::Plagger  Web::Scraper and EFT 無表情 - SweetPotato::Plagger のブックマークコメント

Web::Scraperを書くのがだるくなったのでCustomFeed::Simple+EFTでシンプルにハックすることにした。

今話題のニュースサイト,漫画(マンガ)情報サイト《無表情》Plaggerでenjoy!

assets/plugins/CustomFeed-Script/muhyojo.pl

#!/usr/bin/perl
# author: SweetPotato
use strict;
use warnings;
use utf8;

use DateTime;
use DateTime::Duration;
use Encode;
use URI;
use Web::Scraper 0.22;
use YAML;

my $url = 'http://muhyojo.web0.jp/';

my $s = scraper {
    process '//table[@width="100%" and not(@bgcolor)]/tbody/tr/td[.//div[@align="right"]]',
      'info[]' => scraper {
        process '//b', 'category' => 'text';
        process '//font[@color="red" or @color="orange"]', 'tag[]' => 'text';
        process '//a[not(@class) and not(./img)]',
          'title[]' => 'text',
          'link[]' => ['@href', sub { URI->new_abs($_, $url)->as_string } ];
        process '//font[@color="#696969"]',
          'date[]' => ['text', sub { &mk_date($_) } ];
        result qw/category tag title link date/;
      };
    result qw/info/;
};

my $res = $s->user_agent->get($url);
unless ($res->is_success) {
    die "GET $url failed: " . $res->status_line;
}

my @entry;
for my $info (@{ $s->scrape(Encode::decode('shiftjis', $res->content)) || [] }) {
    for (0 .. $#{$info->{title}}) {
        push @entry, +{
            title => $info->{title}->[$_],
            link  => $info->{link}->[$_],
            date  => $info->{date}->[$_],
            tags  => [$info->{category}, $info->{tag}->[$_]],
        };
    }
}

binmode STDOUT, ':utf8';
print YAML::Dump +{
    title => '漫画(マンガ)情報サイト《無表情》',
    link => $url,
    entry => \@entry,
};

# guess year
sub mk_date {
    my $md = shift;
    my ($month, $day) = ($md =~ /(\d{2})\/(\d{2})/) or return;

    my $today = DateTime->now->truncate(to => 'day');
    my $this = $today->clone->set(month => $month, day => $day);
    my $last = $this->clone->subtract(years => 1);
    my $next = $this->clone->add(years => 1);
    my @date = sort { DateTime::Duration->compare($a->[1], $b->[1], $today) }
               map { [$_->[0], $_->[1]->is_positive ? $_->[1] : $_->[1]->inverse ] }
               map { [$_, $today - $_] } ($this, $last, $next);
    $date[0]->[0]->strftime('%Y-%m-%d');
}

assets/plugins/Entry-FullText/muhyojo.pl

# author: SweetPotato
sub handle {
    my ($self, $args) = @_;
    $args->{entry}->link =~ qr!^http://muhyojo\.web0\.jp/publisher/[-\w]+/[-\w]+/(index\.html?)?#\d+$!;
}

sub extract {
    my ($self, $args) = @_;

    my ($name) = ($args->{entry}->link =~ /#(\d+)$/) or return;
    my ($data) = ($args->{content} =~ m!(<legend[^>]*><a name="?$name"?>.*?)</fieldset>!s) or return;
    $data;
}

config.yaml

plugins:
  - module: Subscription::Config
    config:
      feed:
        - url: script:/path/to/muhyojo.pl

  - module: CustomFeed::Script

  - module: Filter::EntryFullText
  - module: Filter::ResolveRelativeLink
  - module: Filter::ForceTimeZone
トラックバック - http://plagger.g.hatena.ne.jp/SweetPotato/20071028

2007-10-20

[] Web::Scraper for CF::Script コミックHOLIC  Web::Scraper for CF::Script コミックHOLIC - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  Web::Scraper for CF::Script コミックHOLIC - SweetPotato::Plagger  Web::Scraper for CF::Script コミックHOLIC - SweetPotato::Plagger のブックマークコメント

コミックHOLICの各話を取得。

assets/plugins/CustomFeed-Script/comicholic.pl

#!/usr/bin/perl
use strict;
use warnings;
use utf8;

use URI;
use Web::Scraper;
use YAML;

my $url = 'http://www.toranoana.jp/webcomic/holic/';

my $s = scraper {
    process '//div[./a[@target="_blank"]]', 'comic[]' => scraper {
        process '/div/a',     link  => ['@href', sub { $_->as_string } ];
        process '/div/a/img', icon  => ['@src', sub { +{ url => $_->as_string } } ];
        process '/div',       title => 'text';
        result qw/link icon title/;
    };
    result qw/comic/;
};

binmode STDOUT, ':utf8';
print YAML::Dump +{
    title => 'コミックHOLIC',
    link  => $url,
    entry => $s->scrape(URI->new($url)),
};

config.yaml

plugins:
  - module: Subscription::Config
    config:
      feed:
        - url: 'script:/path/to/comicholic.pl'

  - module: CustomFeed::Script

[] Web::Scraperを使ってFlexComixブラッドのassetsをYahoo!コミック無料マガジン全般対応版に書き直した 00:53  Web::Scraperを使ってFlexComixブラッドのassetsをYahoo!コミック無料マガジン全般対応版に書き直した - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  Web::Scraperを使ってFlexComixブラッドのassetsをYahoo!コミック無料マガジン全般対応版に書き直した - SweetPotato::Plagger  Web::Scraperを使ってFlexComixブラッドのassetsをYahoo!コミック無料マガジン全般対応版に書き直した - SweetPotato::Plagger のブックマークコメント

以前作った↑を,Web::Scraperを使ってYahoo!コミック無料マガジン全般対応版に書き直した。

assets/plugins/CustomFeed-Script/yahoocomic.pl

#!/usr/bin/perl
use strict;
use warnings;
use utf8;

use URI;
use Web::Scraper;
use YAML;

my $mag = shift;
   $mag = "http://comics.yahoo.co.jp/magazine/${mag}_0001.html" unless $mag =~ m!^http://!;

my $s = scraper {
    process '//table[@cellspacing="4"][1]//b', title => 'text';
    process '//table[@cellpadding="4" and .//img and count(.//tr)=4]',
      'comic[]' => scraper {
        process '//tr[1]//img',   icon   => ['@src', sub { +{ url => $_->as_string } } ];
        process '//tr[2]//a',     link   => ['@href', sub { $_->as_string } ];
        process '//tr[2]//a/b',   title  => 'text';
        process '//tr[3]//small', author => ['text', sub { (/(.*?)\s*$/)[0] } ];
        process '//tr[4]//small', body   => 'text';
        result qw/icon link title author body/;
      };
    result qw/title comic/;
};

my $res = $s->scrape(URI->new($mag));

binmode STDOUT, ':utf8';
print YAML::Dump +{
    title => $res->{title},
    link  => $mag,
    entry => $res->{comic},
};

config.yaml

plugins:
  - module: Subscription::Config
    config:
      feed:
        - url: 'script:/path/to/yahoocomic.pl blood'
        - url: 'script:/path/to/yahoocomic.pl flare'
        - url: 'script:/path/to/yahoocomic.pl weeklyyj'
        - url: 'script:/path/to/yahoocomic.pl fang'
        - url: 'script:/path/to/yahoocomic.pl gumbo'
        - url: 'script:/path/to/yahoocomic.pl marjong'
        - url: 'script:/path/to/yahoocomic.pl sangokushi'
        - url: 'script:/path/to/yahoocomic.pl essay'

  - module: CustomFeed::Script

  - module: SmartFeed::All
    config:
      title: Yahoo!コミック > 無料マガジン
      link: http://comics.yahoo.co.jp/magazine/
トラックバック - http://plagger.g.hatena.ne.jp/SweetPotato/20071020