趣味のネットウォッチのために仕方が無く超便利な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/perluse 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/以下に保存できます。
# loginmy $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,);# accessmy $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メソッドでページ移動します。
# scrapemy $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';ここでは固定されているフィードのリンクとサムネイルイメージを直接指定してます。
# outputbinmode 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さんで。