2008/01/31


Journal of miyagawa (1653)
TEXTや@srcといったショートカット結果に対して任意のフィルタをかませる事が出来るようになったようです。
これまでのように process "span.entry-content", comment => 'TEXT';
と指定していた部分を process "span.entry-content", comment => [ 'TEXT', 'MyFilter' ];
と記述出来るようになったのです。
MyFilterは「Web::Scraper::Filter::MyFilter」というパッケージで定義され、filterプロシージャが呼び出されます。

さっそく、twitterの発言では70%近くが英語のmiyagawaさんの発言をスクレイピングし、エキサイト翻訳で日本語にフィルタするサンプルを作って見ました。
package Web::Scraper::Filter::EnglishToJapanese;
use base qw( Web::Scraper::Filter );
use warnings;
use LWP::UserAgent;
use HTTP::Request::Common qw(POST);

sub filter {
    my($self, $value) = @_;
    my $req = POST( 'http://www.excite.co.jp/world/english/',
        [before => $value, wb_lp => 'ENJA'] );
    my $data = $Web::Scraper::UserAgent->request($req)->content;
    $data =~ s!\x0D|\x0A!!g;
    $data =~ s/^.*?<textarea[^>]*name="after"[^>]*>(.*?)<\/textarea>.*?$/$1/;
    return $data;
}

1;

use URI;
use Web::Scraper;

my $twitter = scraper {
    process 'td.content',
        'comments[]' => scraper {
            process "span.entry-content", comment => [ 'TEXT', 'EnglishToJapanese' ];
        };
    result 'comments';
};
my $comments = $twitter->scrape( URI->new("http://twitter.com/miyagawa/") );
use YAML;
warn Dump $comments;
で、結果
---
- comment: ' ウェブログを作られた http://tinyurl.com/2xldch '
- comment: ' ウェブを出荷します:、:フィルタサポートがある削り器0.21_01。 バージョン番号が言うようにこれがdevリリースであるのに注意してください。'
- comment: ' 見ます。'
- comment: ' ダッシュボード懺悔室、Yellowcard、少年は少女が好きです: 多くの誘惑が今月のSF warfieldで http://www.ticketmaster.com.. を見せます。 ... '
- comment: ' 100のコメント. diggの上のトップページングのためのtakesako、おめでとう、ワオ490、diggs、 http://tinyurl.com/255ht7 '
- comment: ' スクリーンからStreoパート2までNFGを聞くのがあります。 輝かしいアルバム'
- comment: ' @hanekomu、うん、それはしゃぶられます。 そこでは、日本で同じです。 請求先の住所が米国にある状態で、運よく私はcreditcardsを持っています。'
- comment: ' Dashboard Confessionalの新しいアルバム http://www.amazon.com/gp/pr.. を購入した、アマゾン'
- comment: ' より古いリンクの取り逃がすことは一時的であるように思えました。私がfriendfeedされることへのスイッチかJaikuに好きでないので、 http://tinyurl.com/2gq4uj は少し救いました。'
- comment: ' したがって、さえずりの丁付けはいつまでも、行きましたか? そうだとすれば、私は、確実にさえずりを使用するのを止めるつもりです。'
- comment: ' http://subtech.g.hatena.ne... のウェブログを作りました。 Yappo++typester++Plagger++'
- comment: ' @Yappo++'
- comment: ' IT Crowd s02e05を見ます。'
- comment: ' ep1を見ます。'
- comment: ' 12ドルでmonoprice.comからの外でコンポーネントケーブルと結合器を私のPSPビデオに購入しました。 すさまじい値'
- comment: ' 作成されて、playstationのための削り器は、給送 http://tinyurl.com/yqbtjb plagger++ウェブを格納して、発行しています:、:削り器++'
- comment: ' http://feeds.feedburner.com.. に加入しました。'
- comment: ' 私の2週間のabsenseでは、私はWaMu、Master、およびCapitalOneから10の+クレジットカード申し出を受けました。 ため息をついてください。'
- comment: ' ビールを飲みます。'
エキサイト翻訳の部分をスクレイピングするんが筋ちゃうんかいな!というツッコミは無しでお願いします。
Posted at by




先日書いた「個人的ソーシャルブックマークサービスの歩き方」という記事にもある通り、私は個人的な資料をdel.icio.us、ソーシャルなものをはてなブックマークに...と使い分けています。
ただし、携帯からはdel.icio.usが使えない為、はてなブックマークを使ってお気に入りユーザのブクマから必要な物だけを自分のブックマークとしてエントリしています。その後、資料として必要な物をdel.icio.usに手作業で転送しています。ただし量が多い場合にはPlaggerを使うこともあります。
ただし、ここで一つ問題が発生していました。
はてなブックマークのフィードにはブクマコメントがitem/descriptionフィールドに格納されています。ただしPublish::Delicousを含むほぼ全てのSBM系プラグインではsummaryではなくbody(body_text)をコメント部として扱う仕様になっています。ですので
http://b.hatena.ne.jp/mattn/rss <description>おぉ。thx>miyagawa</description>
とdescriptionフィールドに格納されている文字列そのままが欲しいにも関わらず <content:encoded>
  &lt;blockquote cite="http://www.ac.cyberhome.ne.jp/~mattn/cgi-bin/blosxom.cgi/software/lang/perl/20071015162834.htm" title="Big Sky :: Publish::Wassrをでっちあげた"&gt;
    
    &lt;cite&gt;&lt;a href="http://mattn.kaoriya.net/software/lang/perl/20071015162834.htm"&gt;Big Sky :: Publish::Wassrをでっちあげた&lt;/a&gt; &lt;a href="http://b.hatena.ne.jp/entry/http://www.ac.cyberhome.ne.jp/~mattn/cgi-bin/blosxom.cgi/software/lang/perl/20071015162834.htm"&gt;&lt;img src="http://b.hatena.ne.jp/images/entry.gif" title="このエントリーを含むブックマーク" alt="このエントリーを含むブックマーク" border="0"&gt;&lt;/a&gt;&lt;/cite&gt;

  &lt;/blockquote&gt;
  &lt;p&gt;おぉ。thx>miyagawa&lt;/p&gt;
</content:encoded>
という元記事の引用文が含まれたbodyで配信されてしまいます。はじめはPublish::XXXでpost_bodyしているSBM系のプラグインを全て直そうかと(use_summaryみたいなオプションで)思いましたが面倒。いっそAggregator::SimpleのXML::Feed::RSSを操作している部分にオプション付けて強制的にcontentでなくsummaryを使わせるように修正しようかとも思いました。ただ、よく考えたらsummaryをbodyに上書きしてやるプラグインを書いた方が便利だし汎用的だと思い以下のプラグインを作りました。
Plagger/Plugin/Filter/SummaryToBody.pm
package Plagger::Plugin::Filter::SummaryToBody;
use strict;
use base qw( Plagger::Plugin );

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

sub filter {
    my($self, $context, $args) = @_;
    $args->{entry}->body($args->{entry}->summary);
}

1;

__END__

=head1 NAME

Plagger::Plugin::Filter::SummaryToBody - copy summary field to body field.

=head1 SYNOPSIS

  - module: Filter::SummaryToBody

=head1 DESCRIPTION

This plugin copy summary field to body field. This is helpful to sanitize
description field. ex) Hatena bookmark field include <blockquote> tag for
quote.

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 SEE ALSO

L<Plagger>, L<Plagger::Plugin::Filter::SummaryToBody>

=cut
使い方はmodule定義だけ。以下は私がはてブからdel.icio.usの転送につかっているYAML
hatebu2delicous.yaml
global:
  assets_path: /home/user/plagger/assets/
  timezone: Asia/Tokyo
  log:
    level: info

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://b.hatena.ne.jp/[hatena user]/rss

  - module: Filter::SummaryToBody

  - module: Filter::Rule
    rule:
      module: Deduped
      path: /tmp/hatebu2delicious.db

  - module: Publish::Delicious
    config:
      username: [delicious username]
      password: [delicious password]
      interval: 2
      post_body: 1
どっちかっていうとBreakXXX系のプラグインで、しかも個人用途でしかありませんが一応公開しておきます。
後でCodeReposにも置いておきます。

追記
もしかしたら空繰再繰さんの「Plagger::Plugin::Filter::ExtractBody」を使ってXPathで「p」とする事でも同じ結果になるかもしれませんね。
こちらは後日試します。
Posted at by




Publish::Twitterコピって、Publish::Wassrをでっちあげた。
一応動いてる。TwitterからWassrへポストした結果
※テストでは1件だけポストした。

twitter2wassr.yaml
global:
  assets_path: /home/user/plagger/assets
  timezone: Asia/Tokyo
  log:
    level: info

plugins:
  - module: Subscription::Config
    config:
      feed:
        - http://twitter.com/statuses/user_timeline/[twitter user].rss

  - module: Filter::BreakEntriesToFeeds
    config:
      use_entry_title: 1

  - module: Publish::Wassr
    config:
      username: [user name]
      password: [pass word]

Plagger/Plugin/Publish/Wassr.pm
package Plagger::Plugin::Publish::Wassr;
use strict;
use base qw( Plagger::Plugin );

use Encode;
use Net::Wassr;
use Time::HiRes qw(sleep);

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'publish.entry' => \&publish_entry,
        'plugin.init'   => \&initialize,
    );
}

sub initialize {
    my($self, $context) = @_;
    my %opt = (
        user => $self->conf->{username},
        passwd => $self->conf->{password},
    );
    for my $key (qw/ apihost apiurl apirealm/) {
        $opt{$key} = $self->conf->{$key} if $self->conf->{$key};
    }
    $self->{wassr} = Net::Wassr->new(%opt);
}

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

    my $body = $self->templatize('wassr.tt', $args);
    # TODO: FIX when Summary configurable.
    if ( length($body) > 159 ) {
        $body = substr($body, 0, 159);
    }
    $context->log(info => "Updating Wassr status to '$body'");
    $self->{wassr}->update( {status => encode_utf8($body)} ) or $context->error("Can't update wassr status");

    my $sleeping_time = $self->conf->{interval} || 15;
    $context->log(info => "sleep $sleeping_time.");
    sleep( $sleeping_time );
}

1;
__END__

=head1 NAME

Plagger::Plugin::Publish::Wassr - Update your status with feeds

=head1 SYNOPSIS

  - module: Publish::Wassr
    config:
      username: wassr-id
      password: wassr-password

=head1 DESCRIPTION

This plugin sends feed entries summary to your Wassr account status.

=head1 CONFIG

=over 4

=item username

Wassr username. Required.

=item password

Wassr password. Required.

=item interval

Optional.

=item apiurl

OPTIONAL. The URL of the API for wassr.jp. This defaults to "http://wassr.jp/user/xxx/statuses" if not set.

=item apihost

=item apirealm

Optional.
If you do point to a different URL, you will also need to set "apihost" and "apirealm" so that the internal LWP can authenticate.

    "apihost" defaults to "api.wassr.jp:80".
    "apirealm" defaults to "API Authentication".

=back

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 SEE ALSO

L<Plagger>, L<Net::Wassr>

=cut
assets/plugins/Publish-Wassr/wassr.tt
[% IF entry.body %][% entry.body_text %][% ELSE %][% entry.title_text %][% END %] [% entry.permalink %]
deps/Publish-Wassr.yaml
name: Publish::Wassr
author: Yasuhiro Matsumoto
depends:
  Net::Wassr: 0

Net::Wassrは[Perl]Net::Wassr - Hatena::Diary::Neko::kak 500 Internal Server Errorを使用。
Posted at by