序文
趣味のネットウォッチのために仕方が無く超便利なPerlを覚えようという感じの otsune です。そんなわけでコーディングの深い話はよくわからんので、今回はPerlとCPANを使ってネットウォッチを支援する手法について書きます。
ウォッチしたいWebページを機械的に監視できれば、あとはPlaggerなどの便利ツールを使って「メールを出す」「im.kayac.comでメッセンジャーにアラートを出す」「ピザを注文する」など好きな処理をすることが出来ます。
RSSフィードやAPIなどがあるWebサイトであれば特に苦労はしないのですが、今回取り上げるOgame.jpはウェブブラウザーゲームなので、フィードなど便利な機能はまったく存在しません。
そこでウォッチしたいWebページに対してWeb::Scraperを使ってYAMLを出力する短いスクリプトを書いてしまいます。
メールを出すなどのこまごまとした処理は既存のPlagger等のスクリプトに任せられるので、最小限のコードを書くだけでやりたいことが出来るようになります。
概要
ざっと流れを説明します。まずブラウザーゲームのOgame.jpはユーザー名とパスワードによりログインが必要なサイトなので、WWW::Mechanizeでログインします。
そのログイン時にConfig::Pitを使うことでパスワードをスクリプト内に書かないようにします。
そして、Web::ScraperでHTMLページから監視したい「敵が攻めてくる警告文」の部分を切り抜きします。
最後にYAMLで、Plaggerであつかえるフィードとエントリーの形で出力します。
解説
早速コードを見ながら解説しましょう。(コードはCodeReposにcommitしてあります)
#!/usr/bin/perl
use strict;
use warnings;
use URI;
use WWW::Mechanize;
use Config::Pit;
use Web::Scraper;
use DateTime;
use YAML;
この辺は定番という感じで。今回使う主なCPANモジュールは、WWW::Mechanize, Config::Pit, Web::Scraperです。
my $uni = shift || 'uni4';
# get password
#Config::Pit::switch('ogame');
my $config = pit_get("$uni.ogame.jp", require => {
"username" => "otsune",
"password" => "your password on ogame.jp"
});
Config::Pitのpit_get
を使うことでスクリプトにIDやパスワードをハードコードする必要が無くなります。ぜひ使いましょう。パスワードの設定はppit set uni4.ogame.jp
などと入力して$EDITORで編集することで~/.pit/
以下に保存できます。またperl -MConfig::Pit -e'Config::Pit::set("uni4.ogame.jp", data=>{ username=>"dankogai", password=>"kogaidan" })'
というワンライナーでも~/.pit/
以下に保存できます。
# login
my $ogame_login = "http://$uni.ogame.jp/game/reg/login2.php";
my $uri = URI->new($ogame_login);
$uri->query_form(
login => $config->{username},
pass => $config->{password},
v => 2,
);
# access
my $mech = WWW::Mechanize->new(cookie_jar => {});
my $response = $mech->get( $uri );
if (!$response->is_success || $response->content =~ /errormessage/){
warn $mech->status();
return;
};
$mech->follow_link(url_regex => qr{index\.php}i);
URIモジュールでIDやパスワードのクエリー付きURLを組み立てて、WWW::Mechanizeでブラウザーゲームにログインをします。Ogame.jpはログイン後に<meta http-equiv='refresh' ...>
ヘッダーでindex.php
にリダイレクトしているので、follow_link
メソッドでページ移動します。
# scrape
my $feed = scraper {
process 'title', 'title' => 'TEXT';
process 'tr.flight', 'entry[]' => scraper {
process 'span.attack', title => 'TEXT',
process '//a[@class="attack"]/following-sibling::a', body => '@title';
process '//div[starts-with(@id, "bxx")]', date => sub {
my $dt = DateTime->now(time_zone=>'local');
$dt->add( seconds=>$_->attr('title') );
return $dt->iso8601();
}
}
}->scrape($mech->content, $mech->uri);
Web::Scraperのscraper
メソッドで、取得したWebページ(content
)から切り抜きたい箇所をXPathかCSSセレクターで指定して読み込みます。(content
を渡してスクレイピングするときは、第二引数に$mech->uri
を渡すと、Web::Scraperが相対URLを自動的に絶対URLにしてくれるのでオススメ)
ここでポイントなのが後処理をするPlaggerにあわせて「ひとつのFeedに複数のEntry」という構造で項目名を決めることです。具体的な例では
---
link: フィードのURL<http://example.com/hoge/>
title: 'フィードタイトル'
image: フィードのサムネイル画像URL<http://img.example.com/hoge/logo.png>
entry:
- title: 'エントリー1のタイトル'
link: エントリー1のURL<http://example.com/hoge/path/to/entry1-link>
date: エントリー1の日付(2008-12-21T01:23:45Z)
body: 'エントリー1内容'
- title: 'エントリー2のタイトル'
link: エントリー2のURL<http://example.com/hoge/path/to/entry2-link>
date: エントリー2の日付(2008-12-21T06:54:32Z)
body: 'エントリー2内容'
という感じの構造のYAMLを出力します。(何が使えるかはPlaggerのlib/Plagger/Feed.pm
やlib/Plagger/Entry.pm
のアクセッサ名を参照すると良いでしょう)。
Web::Scraperのprocess
では、ダブルクォートを使うときにXPath属性選択省略記号の@
などを\
でエスケープする必要があります。たとえば'//hoge[@attr="fuga"]'
と"//hoge[\@attr='fuga']"
は同じです。XPath文と一致させておきたいときはシングルクォートを使うと良いでしょう。
また、Web::Scraperでsub
でコールバックを使うと、$_にprocess
で切り抜かれたHTML::Elementsが渡されます。
敵が到達するまでの残り時間は、div
タグのtitle
という属性に整数値で書かれているので、sub
のコールバック内でDateTimeモジュールを使って加算して(具体的には$dt->add( seconds=><数値> )
の部分)、日付文字列にして返しています。
$feed->{link} = $ogame_login;
$feed->{image} = 'http://board.ogame.jp/ogame_logo/jp.gif';
ここでは固定されているフィードのリンクとサムネイルイメージを直接指定してます。
# output
binmode STDOUT, ":utf8";
print YAML::Dump $feed;
最後にWeb::Scraperによって$feedに読み込まれたデータをYAMLとして出力します。
おまけ
Plaggerで使うにはplagger/assets/plugins/CustomFeed-Script/
以下にogame_check.pl
という名前などでスクリプトを配置して
plugins:
- module: Subscription::Config
config:
feed:
- url: script:/path/to/plagger/assets/plugins/CustomFeed-Script/ogame_check.pl
- module: CustomFeed::Script
というconfig.yaml
で読み込みます。
このスクリプトをPlaggerだけで使うつもりなら、use Plagger;
とuse Plagger::UserAgent;
をスクリプトに追記することでWWW::MechanizeはPlagger::UserAgentを使うことも出来ます。(おなじ理由で、DateTimeの代わりにPlagger::Dateを使うこともできます)、あーでもmech
でfollow_link
とか使ってるとダメか……
最後に
「PerlはCPANを使うためのインターフェース」が持論のオレ的には、やりたいことをサクっと解決できるCPANという仕組みはとてもすばらしいと思っています。
さて、次はmalaの予定だったけど、連絡付いたのでDan Kogaiさんで。