Plaggerの最近のブログ記事

ついこの間作ったGoogleWebmasterツールのレポートを定期的に取得するPlaggerプラグインを、ちゃんとデータセンターのホストでcronで動かし、かつレポートの先もサーバー上のDBとか静的ファイルに書き出しておくのが良かろう(将来的にも)、と思って作業してた。

普通に、そのホストにPlaggerを入れて、テキトーなRSSをgmailに送って、というテストに成功したまでは良かったんだけど、いざ動かしてみるとまったく動かない。
あれ、おかしいと思って普通のperlスクリプト版の方で試してみたらこちらも動かない。

follow_link(): Can't call method "url"

こんなエラーが出て、途中で処理が止まる。

「は。何これ?」

仕方がないのでエラーメッセージでググったら、既にバグレポートが上がってたんだけど、

http://code.google.com/p/www-mechanize/issues/detail?id=113

This will be fixed in 1.62.

ちょっwww
まじっすかwwww

WWW::Mechanizeの最新版は1.60。Plagger入れる時に依存関係のあったモジュールもCPANでアップデートしたので、そのまま何の疑いも持たずに1.60をいれたけどダメだったんですね。
ちなみに僕が作業で使っていたマシンにインストールしてあったバージョンは1.34。
なので、とりあえずMechanizeの古めのものを使えばよさげな感じ。


と、ここまで突き止めて今日は時間切れ。
一旦は確実に動作する(はず)の1.34を入れてみて、1.60との差分を見ながら「動くはず」のバージョンを試すってのが良いのかな?

WEBマスターツールのhomeに戻る処理と、dashboardに遷移する部分がおかしかったので、follow_linkのURL正規表現を修正してみた。
Publish::Gmailで送信してみると、メールでこんなようなレポートを受信できる。

20090908.png
.yamlファイルで設定したGoogleアカウントで管理しているサイトについて、全部一括でレポートを作成できるようになったので、実現したいことは大体できるようになった。
文言の表示とか、テーブルそのまま取ってくるとかどうよ?みたいな、細かい部分はそのうち直す。

package Plagger::Plugin::CustomFeed::GoogleWebmasterToolReport;
use strict;
use warnings;
use Plagger::Mechanize;
use base qw (Plagger::Plugin);

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

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

sub aggregate {
    my ($self, $context, $args) = @_;
    my $mech = join('::', __PACKAGE__, "Mechanize")->new($self);
    $mech->login or $context->error('login failed');

    my $feed = Plagger::Feed->new;
    $feed->type('Google Webmaster Tool Report');
    $feed->title('Google Webmaster Tool Report on ' . Plagger::Date->now());
    $feed->link('http://www.google.com/webmasters/tools/?hl=ja');

    my @sites = $mech -> find_targets;
    my $num_sites = @sites;
    $context -> log (debug => "$num_sites");

    for (my $i = 1; $i <= $num_sites; $i++) {
        my $queries_entry = Plagger::Entry->new;
        $mech->go_top();
        $queries_entry->title("上位の検索クエリ site #$i");
        $queries_entry->date( Plagger::Date->now() );
        $mech->go_dashboard($i);
        $queries_entry->body($mech->search_queries_html);
        $feed->add_entry($queries_entry);

        my $external_links_entry = Plagger::Entry->new;
        $mech->go_top();
        $external_links_entry->title("外部リンク site #$i");
        $external_links_entry->date( Plagger::Date->now() );
        $mech->go_dashboard($i);
        $external_links_entry->body( $mech->external_links_html);
        $feed->add_entry($external_links_entry);
    }
    $context->update->add($feed);
}

package Plagger::Plugin::CustomFeed::GoogleWebmasterToolReport::Mechanize;
use strict;
use warnings;
use Plagger::Mechanize;
use base qw(Class::Accessor::Fast);

__PACKAGE__->mk_accessors(qw(mech email password start_url));

sub new {
    my $class = shift;
    my $plugin = shift;
    my $mech = Plagger::Mechanize->new;
    $mech->agent_alias( "Mac Mozilla" );
    return bless {
        mech     => $mech,
        email    => $plugin->conf->{email},
        password   => $plugin->conf->{password},
        start_url => 'https://www.google.com/accounts/ServiceLogin?service=sitemaps&passive=true&nui=1&continue=https%3A%2F%2Fwww.google.com%2Fwebmasters%2Ftools%2F&followup=https%3A%2F%2Fwww.google.com%2Fwebmasters%2Ftools%2F&hl=ja',
    }, $class;
}

sub login {
    my $self = shift;
    my $mech = $self->mech;
    my $res = $mech->get($self->start_url);
    return unless $mech->success;
    $mech->form_number(1);
    $mech->set_fields('Email' => $self -> email,
                      'Passwd' => $self -> password);
    $mech->submit;
    return if ($mech->content =~ m!
return 1; } sub find_targets { my $self = shift; return $self -> mech -> find_all_links(url_regex => qr/dashboard/i); } sub search_queries_html { my $self = shift; my $html; $self->mech->follow_link(url_regex => qr/top-search-queries/i); my $content = $self->mech->content; if ($content =~ m!(.*?
)!is) { $html = "表示回数\n"; $html .= $1; } if ($content =~ m!(.*?
)!is) { $html .= "クリックスルー\n"; $html .= $1; } return $html; } sub external_links_html { my $self = shift; my $html; $self->mech->follow_link(url_regex => qr/external-links/i); my $content = $self->mech->content; if ($content =~ m!(.*?
)!is) { $html = "サイトへのリンク\n"; $html .= $1; } return $html; } sub go_dashboard { my ($self, $number) = @_; $number += 0; $self->mech->follow_link(url_regex => qr/webmasters\/tools\/dashboard/i, n => $number); } sub go_top { my $self = shift; $self -> mech -> follow_link(url_regex => qr/webmasters\/tools\/home/i); } 1; __END__
なんとなくできた。Googleウェブマスターツールにログインして、上位の検索クエリ、サイトへのリンクを自動的に取ってくるPlaggerプラグイン。

package Plagger::Plugin::CustomFeed::GoogleWebmasterToolReport;
use strict;
use warnings;
use Plagger::Mechanize;
use base qw (Plagger::Plugin);

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

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

sub aggregate {
    my ($self, $context, $args) = @_;
    my $mech = join('::', __PACKAGE__, "Mechanize")->new($self);
    $mech->login or $context->error('login failed');

    my $feed = Plagger::Feed->new;
    $feed->type('Google Webmaster Tool Report');
    $feed->title('Google Webmaster Tool Report');
    $feed->link('http://www.google.com/webmasters/tools/?hl=ja');

    my @sites = $mech -> find_targets;
    my $num_sites = @sites;
    $context -> log (debug => "$num_sites");

    for (my $i = 1; $i <= $num_sites; $i++) {
        my $queries_entry = Plagger::Entry->new;
        $queries_entry->title('Search Queries');
        $queries_entry->date( Plagger::Date->now() );
        $mech->go_top();
        $mech->go_dashboard($i);
        $queries_entry->body($mech->search_queries_html);
        $feed->add_entry($queries_entry);

        my $external_links_entry = Plagger::Entry->new;
        $external_links_entry->title('External Links');
        $external_links_entry->date( Plagger::Date->now() );
        $mech->go_top();
        $mech->go_dashboard($i);
        $external_links_entry->body( $mech->external_links_html);
        $feed->add_entry($external_links_entry);
    }
    $context->update->add($feed);
}

package Plagger::Plugin::CustomFeed::GoogleWebmasterToolReport::Mechanize;
use strict;
use warnings;
use Plagger::Mechanize;
use base qw(Class::Accessor::Fast);

__PACKAGE__->mk_accessors(qw(mech email password start_url));

sub new {
    my $class = shift;
    my $plugin = shift;
    my $mech = Plagger::Mechanize->new;
    $mech->agent_alias( "Mac Mozilla" );
    return bless {
        mech     => $mech,
        email    => $plugin->conf->{email},
        password   => $plugin->conf->{password},
        start_url => 'https://www.google.com/accounts/ServiceLogin?service=sitemaps&passive=true&nui=1&continue=https%3A%2F%2Fwww.google.com%2Fwebmasters%2Ftools%2F&followup=https%3A%2F%2Fwww.google.com%2Fwebmasters%2Ftools%2F&hl=ja',
    }, $class;
}

sub login {
    my $self = shift;
    my $mech = $self->mech;
    my $res = $mech->get($self->start_url);
    return unless $mech->success;

    $mech->form_number(1);
    $mech->set_fields('Email' => $self -> email,
                      'Passwd' => $self -> password);
    $mech->submit;
    return if ($mech->content =~ m!
mech -> find_all_links(url_regex => qr/dashboard/i); } sub search_queries_html { my $self = shift; my $html; $self->mech->follow_link(url_regex => qr/top-search-queries/i); my $content = $self->mech->content; if ($content =~ m!(.*?
)!is) { $html = "表示回数\n"; $html .= $1; } if ($content =~ m!(.*?
)!is) { $html .= "クリックスルー\n"; $html .= $1; } return $html; } sub external_links_html { my $self = shift; my $html; $self->mech->follow_link(url_regex => qr/external-links/i); my $content = $self->mech->content; if ($content =~ m!(.*?
)!is) { $html = "サイトへのリンク\n"; $html .= $1; } return $html; } sub go_dashboard { my ($self, $number) = @_; $number += 0; $self->mech->follow_link(url_regex => qr/dashboard/i, n => $number); } sub go_top { my $self = shift; $self -> mech -> follow_link(url_regex => qr/webmasters\/tools\/home\?hl=ja/i); } 1; __END__
それにしてもひどいコードだw
先週の続き

だいぶ前にhackしたEvent.pmを使うの前提にPlagger::Plugin::Filter::TrackEndTimeFromTitleってのを書いてみました。書いたというかyusukebeさんのP::P::Filter::YouTubeFromTitleをほぼそのままパクリ。ありがとうございます。
package Plagger::Plugin::Filter::TrackEndTimeFromTitle;

use strict;
use base qw( Plagger::Plugin );
use Encode;
use HTML::Entities;
use Plagger::Event;

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

sub filter {
    my($self, $context, $args) = @_;
    my $e = $args->{entry};
    my ($title, $artist,$duration);
    my $starttime = $e -> date;
    my $event = $e -> event;

    $title = $e->title;
    $title = encode('UTF-8',$title);

    #Last.FM/audioscrobbler
    if($title =~ /(.*?)\s\342\200\223\s(.*?)\Z/){
        $artist = decode('UTF-8',$1);
        $title = decode('UTF-8',$2);
    }

    $duration = get_duration($self, $context, $artist, $title);

    unless($duration eq '') {
        my $event = Plagger::Event -> new;
        $event -> dtstart($e -> date);
        $event -> dtend($e -> date + DateTime::Duration->new(seconds => ($duration/1000)));
        $e -> add_event($event);
    }
}

sub get_duration {
    my ($self,$context,$artist, $title) = @_;
    my $url  = URI->new('http://ws.audioscrobbler.com/2.0/');
    my $file = $self->cache->path_to('track.getInfo_result.html');

    $artist = encode('UTF-8', $artist) unless $context->conf->{no_decode_utf8};
    $title = encode('UTF-8', $title) unless $context->conf->{no_decode_utf8};
    $context->log( info => 'Getting song duration from ' . $artist . " / " . $title );

    my $ua = Plagger::UserAgent->new;
    $url->query_form(method => 'track.getinfo',
                     artist => $artist,
                     track => $title,
                     api_key => $self->conf->{api_key} || 'b25b959554ed76058ac220b7b2e0a026',);

    my $res = $ua->mirror( $url->as_string => $file );
    if($res->is_error) {
        $context->log( error => $res->status );
        return;
    }

    open my $fh, "<:encoding(utf-8)", $file
        or return $context->log(error => "$file: $!");

    my $duration;
    while (<$fh>) {
        if(m!(.*?)!){
            $duration = $1;
            $context->log(info => "duration: $duration");
            last;
        }
    }
    return $duration;
}

yamlはこんな感じ。
plugins:
  - module: Subscription::Config
    config:
      feed:
        - url: http://ws.audioscrobbler.com/1.0/user/yoozoosato/recenttracks.rss

  - module: Filter::Rule
    rule:
      module: Fresh
      mtime:
        path: ./lastfm.tmp
        autoupdate: 1

  - module: Filter::TrackEndTimeFromTitle
    config:
      api_key: YOUR_API_KEY

しょこたんTVと同じようにGoogle Calendarに放るとこんな感じで自分がiTunesで再生した曲が可視化できる。

20081112.png

明日くらいからもうちょっと突っ込んだことに挑戦する予定。

前回作った時は結構簡単に実装できたもんだから「すぐ行ける!」と思いつつハマった。

関連エントリ:しょこたんが出るTV番組の情報をGoogle Calendarに入れておくことで見逃さないようにするテスト

その時はRule::Deduptedの存在を知らなかったので、dc:timeが「放送開始時刻」じゃぁFreshが使えなくて意味ないな、という結論だった。
で、最近になってGoogle Code Hosting SVN -> TwitterというPlaggerレシピを書く過程で発見したRule::Dedupedって便利じゃん!ってのに今さら気づき、早速使ってみたという訳。

Freshの代わりにDBで管理するDedupedを使えば、ちゃんと

Plagger::Plugin::Filter::Rule [debug] Deleting ????????? - livedoor ??? since it has 0 entries

と表示してくれたよ!!よかった!!!


ということでカレンダー自体を公開設定にしたので、googleのアカウントをお持ちでしたら誰でもこのカレンダーを自分のgoogle calendarにインポートできます。


↓このblogで表示用に少し小さくしてます。まぁ我慢して下さい。

先週くらいに、RBCで用意してもらったgoogle codeプロジェクトホスティングからSVNコミットログを抜き出し、twitterに投げるというのを作った。(see also this entry.)

「Twitterと連動させたよ!」ってMLにポストしたらメンバーからの評判も概ね良かったんですが、自分的にはtwittされる順番がおかしいってのだけがどうしても気に喰わなかった。

CustomFeed::SVNLogでreverse指定して引っ張ってくるだけだと、最新のコミットログがtwitterの一番上に表示されなかったんだよね。


Plagger自体をdream hostで動かしてるからそもそもサーバーのload averageも異常に高い時があるし、google codeにもその都度アクセスするからcronの頻度をあんまり縮める訳にもいかず、さてどうしたものかと思っていたらPlaggerのEntryを逆順にするFilterを見つけた。

参考:(フィルタ本体)[sbm] del.icio.us to hatebu - nirvashの日記
参考:(利用例)cohtan blog: Bloggerの投稿をTwitterやJaikuで通知するPlaggerレシピ
参考:(利用例):嶋の徒然なる日々 with podcast : ●不満点が出てきた[Plaggerを使ってLivedoor BlogからMixiへ自動投稿を行う]


Filter::Reverseはここのページにあるのをそのままコピペで使った。

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

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

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

    $context->log(debug => "reverse");
    my @entries = $args->{feed}->entries;
    @entries = reverse(@entries);
    $args->{feed}->{entries} = ¥@entries;
}

1;

OK、ちゃんとやりたかったことが実現できていることが確認できた。

20080220.png

PlaggerにはCustomFeed::SVNLogというモジュールが付属しているので、ありがたく使わせてもらおうと思ったら、perlモジュールのSVN::Coreが足りない、とエラーになった・・・。

% plagger -c ./svn2twitter.yaml
Can't locate SVN/Core.pm in @INC

なのでCPANからSVN::Coreをインストールしようと思ったが、SVN::CoreはCPANには登録されていないそうなので、インストールは少々やっかい
(参考:ぶらぶら日記: SVN::Core をインストールする

さらに僕がPlaggerを動かしているのはDreamhostだったりするので、SVN::Core dreamhostでぐぐったInstalling SVK on Dreamhost - Dev411: The Code Wikiというドキュメントも発見!(英文です)

参考にさせてもらいながら早速作業をしたので、メモ。


まずは大前提として、DreamhostでちゃんとCPANが使えるようになっていること。これはPlaggerを入れた時点でクリアしているはずなんだけど、今一度コチラのページを確認しておくと良いです。
特にPERL5LIBを正しく設定してexportされていること、また$HOME/local/share/perl/ディレクトリが存在していて、ちゃんとPERL5LIBに含まれていることが超重要です。


以下、SVN::Coreを入れるための方法。

(1-1) swigの取得、configure
Dreamhostにはデフォルトで/usr/bin/swigにswigが入っているが、最初っからインストールされている/usr/bin/perlを使うように設定されているっぽい。DreamhostにPlaggerをインストールする時点で$HOME/local/bin/perlに5.8.8を入れているので、そちらを見てくれるようにswigも新規に$HOME/local/にインストールする。

一応、元々入っているswigとバージョンを合わせる為に1.3.24にしてみた。
(最新版は1.3.33)

% wget http://downloads.sourceforge.net/swig/swig-1.3.24.tar.gz
% tar zxf ./swig-1.3.24.tar.gz
% cd ./SWIG-1.3.24
% ./configure --prefix=$HOME/local \
--with-perl5=$HOME/local/bin/perl


(1-2) configure したらインストール
make && make install でオK
ここは特に問題ないはず。$HOME/local/bin/swigができる。

U.S.のYahoo!天気予報の情報をRSSで配信してくれている。
例えば僕が住んでいる船橋市の天気だとこんな感じ。

http://xml.weather.yahoo.com/forecastrss?p=JAXX0011&u=c

日本のYahoo!でも天気情報をRSS配信はしてくれてるんだけど、
http://rss.weather.yahoo.co.jp/rss/days/4510.xml
こっちは週間予報がメインで、1日分の情報(現在の天気情報)に関してはU.S.のY!の方が圧倒的に情報量が多い。風速とか、その日の日の出・日没時刻まで判るしね。
日本国内を一括取得、ってのはさすがに無理で、エリアコードを毎回渡さないといけないのが難点だけど。(上の例で言うとJAXX0011ってのが船橋のエリアコード)

このあたりの天気情報をPlagger + cronで毎朝取得して、雨が降りそうだったら自分の携帯にHTMLメール形式でそのまま送れないかなぁ、と。

U.S.のY!から返ってくるRSSにはxmlnsでyweatherとgeo(geocode)というネームスペースが拡張されている。

↓クリックで拡大

タイトルのまんま。
前回、しょこたん☆ぶろぐの更新情報をGoogle Calendarに入れるってのができたので、今回はTV番組情報をLivedoor番組表から取得して、Google Calendar APIで更新かけてみる。

アイディアソースは [Plagger] テレビ番組表を Plagger で #2 - Bulknews::Subtech - subtech

結論から言うと、コアの部分はサクッとできた。

けど、今のやりかたじゃぁ色んな所が決定的にダメ。あー、ダメなんじゃなくて、需要を満たせてない、が正しいか。
いずれにしても対応するのには今の僕の知識と技術では結構時間がかかりそう。
すぐできると思ったんだけどなぁ。。。誰かいいアイディア・方法を教えてplease...(突っ込みコメント、TB歓迎!!)

ということで、ここまでのやり方と対応詳細は以下の通り。

コメントで「翻訳して」と言われたので、モジュール書いた。
ファイルをダウンロード


言うまでもなく100%ネタなので、動作確認とか一切してません。ご利用は自己責任で。

オリジナルは↓これ。
http://plagger.org/trac/browser/trunk/plagger/lib/Plagger/Plugin/Filter/Kansai.pm

このアーカイブについて

このページには、過去に書かれた記事のうちPlaggerカテゴリに属しているものが含まれています。

前のカテゴリはPHPです。

次のカテゴリはPokemonです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

月別 アーカイブ

ウェブページ

OpenID対応しています OpenIDについて
Powered by Movable Type 6.1.2