Config::PitとWeb::ScraperとPlaggerで24時間365日のゲーム監視体制

序文

趣味のネットウォッチのために仕方が無く超便利な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.pmlib/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を使うこともできます)、あーでもmechfollow_linkとか使ってるとダメか……

最後に

「PerlはCPANを使うためのインターフェース」が持論のオレ的には、やりたいことをサクっと解決できるCPANという仕組みはとてもすばらしいと思っています。

さて、次はmalaの予定だったけど、連絡付いたのでDan Kogaiさんで。

Back