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

2007-02-25

[][] widget for Widget::Simple ウェブ魚拓 18:28  widget for Widget::Simple ウェブ魚拓 - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  widget for Widget::Simple ウェブ魚拓 - SweetPotato::Plagger  widget for Widget::Simple ウェブ魚拓 - SweetPotato::Plagger のブックマークコメント

ウェブ魚拓キャッシュへのリンクを追加する。

assets/plugins/Widget-Simple/megalodon.yaml

link: http://megalodon.jp/
query:
  url: $args->{entry}->permalink
content: ウェブ魚拓

config.yaml

plugins:
  # ...
  module: Widget::Simple
  config:
    widget: megalodon
トラックバック - http://plagger.g.hatena.ne.jp/SweetPotato/20070225

2007-02-21

[] Filter::AbsolutizeEntryLink  Filter::AbsolutizeEntryLink - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  Filter::AbsolutizeEntryLink - SweetPotato::Plagger  Filter::AbsolutizeEntryLink - SweetPotato::Plagger のブックマークコメント

scraperを作る時に,スクレイプ対象のページに相対リンクしか含まれていない時がよくある。そんな時はいつもextract_after_hookで,

extract_after_hook: |
  use URI;
  $data->{link} = URI->new_abs($data->{link}, $args->{feed}->url)->as_string;

ということをやって相対リンクを絶対化するんだけど,scraperを作る度にこのコードを書くのは,たった2行ではあるが面倒だし,同じコードが複数のファイルに書かれているのはどうも気持ち悪い。

Filter::AbsolutizeEntryLinkは,この作業を行うためのFilterである。フィードのリンクを基準URLとして,エントリの相対リンクを絶対化する。既にエントリのリンクが絶対的である場合には,そのリンクに対しては何もしない。

Plagger/Plugin/Filter/AbsolutizeEntryLink.pm

package Plagger::Plugin::Filter::AbsolutizeEntryLink;
use strict;
use base qw( Plagger::Plugin );

use URI;

sub register {
    my ($self, $context) = @_;
    $context->register_hook(
        $self,
        'update.entry.fixup' => \&filter,
    );
}

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

    return if $args->{entry}->link =~ m!^https?://!;

    $args->{entry}->link(URI->new_abs($args->{entry}->link, $args->{feed}->link)->as_string);
}

1;

Filter::EntryFullTextなどを併用する際の注意

Filter::EntryFullTextなど,エントリのリンクに指定されたURLの内容を取得するFilterを併用する場合は,必ずFilter::AbsolutizeEntryLinkを最初に適用すること。つまり,

plugins:
  # ...
  - module: Filter::AbsolutizeEntryLink
  - module: Filter::EntryFullText

はOKだが,

plugins:
  # ...
  - module: Filter::EntryFullText
  - module: Filter::AbsolutizeEntryLink

はNGである。

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

2007-02-16

[] CustomFeed::TaiyoshaComic  CustomFeed::TaiyoshaComic - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  CustomFeed::TaiyoshaComic - SweetPotato::Plagger  CustomFeed::TaiyoshaComic - SweetPotato::Plagger のブックマークコメント

このプラグインはWeb::Scraperを用いて書き直されました。最新版はCodeReposに置いてあります。

太洋社コミック発売予定一覧の「発売日順」表を解析し,コミックごとにエントリ化する。

CustomFeed::Manganomoriとやっていることは似ているので,普段信頼している情報源にあわせてお好きな方をどうぞ。

Plagger/Plugin/CustomFeed/TaiyoshaComic.pm

モジュール内で日本語を使用しているので,UTF-8(BOMなし)で保存すること。

package Plagger::Plugin::CustomFeed::TaiyoshaComic;
use utf8;
use strict;
use base qw( Plagger::Plugin );

use Plagger::Date;
use Plagger::Util;
use Switch;
use URI;

sub register {
    my ($self, $context) = @_;
    $context->register_hook(
        $self,
        'subscription.load' => \&load,
    );
}

sub load {
    my($self, $context) = @_;

    my $ym = $self->conf->{month} || Plagger::Date->now()->strftime("%y%m");

    my $feed = Plagger::Feed->new;
    $feed->aggregator( sub { $self->aggregate($context, $ym); });
    $context->subscription->add($feed);
}

sub aggregate {
    my ($self, $context, $ym) = @_;

    my @urls = map { "http://www.taiyosha.co.jp/comic/comic${ym}_date${_}.html" } (1..2);

    my $feed = Plagger::Feed->new;
    $feed->link($urls[0]);
    $feed->title("大洋社 コミック発売一覧 $ym");

    for my $url (@urls) {
        my $list = $self->parse(Plagger::Util::load_uri(URI->new($url)));

        for my $item (@$list) {
            my $entry = Plagger::Entry->new;
            $entry->title($item->{title});
            $entry->author($item->{author});
            $entry->tags([$item->{publisher}]);
            $entry->body(join ', ', ($item->{author}, $item->{publisher}, $item->{price}));
            $entry->body($entry->body.', '.$item->{shoseki}) if $item->{shoseki};

            if ($item->{day} !~ /^\d+$/) {
                $entry->body($item->{day}.'旬, '.$entry->body);
                $item->{day} = $self->part_to_day($item->{day});
            }
            $entry->date(Plagger::Date->strptime("%y%m%d", $ym.$item->{day}));

            $feed->add_entry($entry);
        }
    }

    $context->update->add($feed);
}

sub parse {
    my ($self, $content) = @_;

    my $list = [];
    while ($content =~ m{
        <TR><TD
        .*?<font.*?>(.*?)/(.*?)</font>
        .*?<font.*?>(.*?)</font>
        .*?<font.*?>(.*?)</font>
        .*?<font.*?>(.*?)</font>
        .*?<font.*?>(.*?)</font>
        .*?<font.*?>(.*?)</font>
    }gsx) {
        push @$list, {
            month => $1, day => $2, publisher => $3, title => $4,
            author => $5, price => $6, shoseki => $7,
        };
    }

    $list;
}

sub part_to_day {
    my ($self, $part) = @_;

    switch ($part) {
        case '上' { return "01"; }
        case '中' { return "11"; }
        case '下' { return "21"; }
    }

    $part;
}

1;

config.taiyosha_comic.yaml

monthには取得したいコミック一覧の年月を %y%m 形式で指定する。指定しない場合は今月のコミック一覧を取得する。

以下の例では出力にPublish::iCalを使用。

plugins:
  - module: CustomFeed::TaiyoshaComic
    config:
      month: 0702
  - module: Publish::iCal
    config:
      dir: .
      filename: taiyosha_comic.ics

関連

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

2007-02-12

[][] scraper for CustomFeed::Config FlexComix ブラッド 02:14  scraper for CustomFeed::Config FlexComix ブラッド - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  scraper for CustomFeed::Config FlexComix ブラッド - SweetPotato::Plagger  scraper for CustomFeed::Config FlexComix ブラッド - SweetPotato::Plagger のブックマークコメント

Web::Scraperを使って書き直した。

ブラッド読みとしてこれは作っておきたかった。

Yahoo!コミック内のFlexComix ブラッドのページを解析し,最新16作品を作品ごとにエントリ化する。

作品のコンテンツそのものが取得されるわけではないが,エントリにセットされたリンク先から作品は読める。

assets/plugins/CustomFeed-Config/flexcomix_blood.yaml

ページのHTMLがひどかった。長い格闘の末に書き上げた芸術的なXPath。

ページ構造が変わっていたようなのでXPathを修正した。

# author: SweetPotato
match: http://comics\.yahoo\.co\.jp/magazine/blood_0001\.html$
extract_xpath:
# updated: //td[@bgcolor='#ffe4ac' and @align='center']/small/text()
  icon: //table[@cellpadding='4' and .//img and count(.//tr)=4]//tr[1]//img/@src
  link: //table[@cellpadding='4' and .//img and count(.//tr)=4]//tr[2]//a/@href
  title: //table[@cellpadding='4' and .//img and count(.//tr)=4]//tr[2]//a/b/text()
  author: //table[@cellpadding='4' and .//img and count(.//tr)=4]//tr[3]//small/text()
  body: //table[@cellpadding='4' and .//img and count(.//tr)=4]//tr[4]//small
extract_after_hook: |
  use URI;
  $data->{link} = URI->new_abs($data->{link}, $args->{feed}->url)->as_string;
  $data->{author} = substr($data->{author}, 3);
  $data->{body} = $data->{updated}.' '.$data->{body};
  $data->{body} =~ s/<.*?>//g;

config.flexcomix_blood.yaml

plugins:
  - module: Subscription::Config
    config:
      feed:
        - url: http://comics.yahoo.co.jp/magazine/blood_0001.html
  - module: CustomFeed::Config

2007-02-10

[] Yahoo! Pipesに求めたい機能ひとつだけ 02:58  Yahoo! Pipesに求めたい機能ひとつだけ - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  Yahoo! Pipesに求めたい機能ひとつだけ - SweetPotato::Plagger  Yahoo! Pipesに求めたい機能ひとつだけ - SweetPotato::Plagger のブックマークコメント

最近話題になっているYahoo! Pipesを使ってみた。

なるほど,ユーザからの手入力を受け付けるモジュールは新しい。でも複数のRSSをまとめるだけならはてなRSSでもできるし。Filterだって元の検索クエリをいじればなんとかなるものが多いし。というわけであまり面白味を感じなかった。

その理由をもっと考えたら,CustomFeed::* に相当する機能が存在しないからだという結論になった。

Plaggerの魅力はあらゆるWebページを処理できるところにある。それはRSSやAtomなどの成形されたフィードだけじゃなくて,Web日記的なページからWeb1.0的なイラストサイトまで,本当にあらゆるページだ。これをエンパワーメントするのが CustomFeed::* なわけだ。

そもそもフィードは機械処理されることを前提としたフォーマットなので,それを扱えるだけじゃ面白くないのよ。俺はつながらないはずのWebページもつなげるようにしたいのよ。

てなわけでそういったスクレイピング機能が今後実装されるまで,まだまだPlaggerにお世話になりそう。

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

2007-02-09

[][] scraper for CustomFeed::Config CG定点観測  scraper for CustomFeed::Config CG定点観測 - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  scraper for CustomFeed::Config CG定点観測 - SweetPotato::Plagger  scraper for CustomFeed::Config CG定点観測 - SweetPotato::Plagger のブックマークコメント

CG定点観測を解析し,日付ごとにエントリ化する。

id:Seacolorさん作のscraperは,正規表現版は最新2日分の記事を絵師サイトごとに,XPath版は最新日の記事をカテゴリごと(今日のネコミミ,今日のぱんちゅ,……)にエントリ化するのだけれど,私にとっては日付ごとにエントリ化された方が便利だったので作成。

assets/plugins/CustomFeed-Config/cgteiten.yaml

元はCSSで指定されている文字の大きさを再現するために extract_after_hook にて手入れ。

# author: SweetPotato
match: http://drag11\.(?:sakura\.ne\.jp|s6\.xrea\.com)/check/$
extract: <!--check\d{8}--><DIV CLASS="cap"><SPAN CLASS="cap-day">(.*?)</SPAN><A NAME="(.*?)">(.*?)</A></DIV>(.*?</DL>)
extract_capture: date link title body
extract_date_format: %Y/%m/%d
extract_date_timezone: Asia/Tokyo
extract_after_hook: |
  $data->{title} = $data->{date}.' '.$data->{title};
  $data->{link} = $feed->link.'#'.$data->{link};
  $data->{body} =~ s!<SPAN CLASS="drag11_big">!<SPAN STYLE="font-size: 200%; font-weight: bold;">!g;

config.cgteiten.yaml

  - module: Subscription::Config
    config:
      feed:
        - url: http://drag11.s6.xrea.com/check/
  - module: CustomFeed::Config
トラックバック - http://plagger.g.hatena.ne.jp/SweetPotato/20070209

2007-02-07

[][][] scraper and upgrader CGIBOY日記帳  scraper and upgrader CGIBOY日記帳 - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  scraper and upgrader CGIBOY日記帳 - SweetPotato::Plagger  scraper and upgrader CGIBOY日記帳 - SweetPotato::Plagger のブックマークコメント

断片部から移動。元の記事は削除し,はてブでフォロー。

CGIBOY日記帳を解析し,日付ごとにエントリ化する。そして本文を取得する。月ごと表示日ごと表示の両方に対応。

assets/plugins/CustomFeed-Config/cgiboy_diary.yaml

元のYAMLをXPath化。iconにはエモティコンをセット。エモティコンは存在しない場合もあるみたいだけど,その場合は何もセットされないっぽい。

# author: SweetPotato
match: http://diary\d*\.cgiboy\.com/[-\w]+/[-\w]+/$
extract_xpath:
  date: //table[not(@width)]//span/font/text()
  icon: //table[not(@width)]//img/@src
  link: //table[not(@width)]//a/@href
  title: //table[not(@width)]//a/font/text()
extract_date_format: %Y,%m,%d
extract_date_timezone: Asia/Tokyo
extract_after_hook: |
  use URI;
  ($data->{link}, $data->{icon}) = map {
    URI->new_abs($_, $args->{feed}->url)->as_string
  } ($data->{link}, $data->{icon});

assets/plugins/Filter-EntryFullText/cgiboy_diary.yaml

# author: SweetPotato
sub handle {
    my($self, $args) = @_;
    $args->{entry}->link =~ qr!^http://diary\d*\.cgiboy\.com/[-\w]+/[-\w]+/index\.cgi\?y=\d{4}&m=\d{1,2}(?:&d=|#)\d{1,2}$!;
}

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

    my $day = ($args->{entry}->link =~ /(?:&d=|#)(\d{1,2})$/)[0];
    if($args->{content} =~ m!<a name="$day">.*?(<TABLE cellSpacing=0 cellPadding=10 width=100% border=0>.*?</TABLE>)!s) {
        $data->{body} = $1;
    }

    return $data;
}

config.cgiboy_diary.yaml

plugins:
  - module: Subscription::Config
    config:
      feed:
        - url: http://diary.cgiboy.com/d01/sample/
        - url: http://diary2.cgiboy.com/0/sample/
  - module: CustomFeed::Config
  - module: Filter::EntryFullText
  - module: Filter::ResolveRelativeLink

[][][] scraper and upgrader FC2絵日記  scraper and upgrader FC2絵日記 - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  scraper and upgrader FC2絵日記 - SweetPotato::Plagger  scraper and upgrader FC2絵日記 - SweetPotato::Plagger のブックマークコメント

断片部から移動。元の記事は削除し,はてブでフォロー。

FC2絵日記の絵日記(例えばサンプル絵日記)を解析し,日付ごとにエントリ化する。そして本文を取得する。

実はFC2絵日記はなんでもRSSが使えるんだけど,折角ということで。

assets/plugins/CustomFeed-Config/fc2diary.yaml

元のYAMLをXPath化。iconにはエモティコンをセット。

# author: SweetPotato
match: http://diary\d*\.fc2\.com/cgi-sys/ed\.cgi/[-\w]+/$
extract_xpath:
  date: //table[@border='0' and @cellpadding='0' and @cellspacing='2' and not(@width)]//font/text()
  icon: //table[@border='0' and @cellpadding='0' and @cellspacing='2' and not(@width)]//img/@src
  link: //table[@border='0' and @cellpadding='0' and @cellspacing='2' and not(@width)]//a/@href
  title: //table[@border='0' and @cellpadding='0' and @cellspacing='2' and not(@width)]//a/text()
extract_date_format: %Y/%m/%d
extract_date_timezone: Asia/Tokyo
extract_after_hook: |
  use URI;
  ($data->{link}, $data->{icon}) = map {
    URI->new_abs($_, $args->{feed}->url)->as_string
  } ($data->{link}, $data->{icon});

assets/plugins/Filter-EntryFullText/fc2diary.yaml

# author: SweetPotato
handle: http://diary\d*\.fc2\.com/cgi-sys/ed\.cgi/[-\w]+/\?Y=\d{4}&M=\d{1,2}&D=\d{1,2}$
extract_xpath:
  body: //table[@cellpadding='6' and @width='100%']

config.fc2diary.yaml

plugins:
  - module: Subscription::Config
    config:
      feed:
        - url: http://diary.fc2.com/cgi-sys/ed.cgi/sample/
  - module: CustomFeed::Config
  - module: Filter::EntryFullText
  - module: Filter::ResolveRelativeLink
トラックバック - http://plagger.g.hatena.ne.jp/SweetPotato/20070207