Perlとバーコードリーダーで本棚整理をするよ! #vgadvent2012


こんにちはこんにちは、VOYAGE GROUPエンジニアblog Advent Calendar 17日めを担当するmonmonといいます!
どんなネタにしようか土曜日まで迷ったあげく、せっかくの会社のAdvent Calendarなので会社に関係する話にしました!

どんな話をするの?


会社の1FにOASISという図書室がありまして、その本の整理をしたいなぁと思いました(あくまで今回のネタ用なので実際はやらないです)。
なので、今回はその話をしようと思います。
ちなみに上の展開写真がOASISね。

何でこの話にしたの?


理由は3つありまして、

という感じです。


仕事でもそうだけど、何かを作り出すときにこういう部品をつなぎ合わせることで形になっていくってのは楽しいものですね。
あぁ!そういえばジョブズも「役に立つ可能性があるとは思わなかった点と点が、10年後に繋がって素晴らしいものができた」みたいな良い話をしてましたね!それだ!

処理の流れ


さぁ!ということで処理の流れだ!最終的に以下のような感じになったよ!

  1. バーコードリーダーでバーコードを読み取り、ISBN13をqueueに入れる
  2. queueからISBN13を取り出しAmazon Product Advertising APIで検索
  3. あればさらにY!の日本語形態素解析API形態素解析
  4. それらをSQLiteに突っ込む
  5. あとは全文検索でうまー

これをひとつずつ見ていこう!

バーコードリーダーでバーコードを読み取る


バーコードリーダーはこれです。
USBで繋ぐと「ぴっ!」って鳴って、バーコードを読み込むと上の部分が緑に光り、ISBNを入力してくれます。便利!

ジョブキューを使う


ということで次。
ISBNを読み込んだ後、その検索結果を待たずに次のISBNを読み込みたいためジョブキューを使います。
で、今年のYAPC::AsiaでもQudoの話があったし、なのでQudoを使ってみようかなと調べたんですが、
調べているうちにこの辺の記事で今回の用途ならJonkでいいかもと思い変更しました。


Jonkの使い方は
https://metacpan.org/module/Jonk

https://metacpan.org/module/Jonk::Cookbook::Basic
に書いてあり、これをまるっとコピーしてきたらサクッと動くくらい楽で、ほぼそのままコピペです。ありがたいありがたい。
2年前のPerl Advent Calendarでも取り上げられているのでもっと難しいことやるときはこっちも参考にしようかなと思ってました(が、時間なくてここまでいかず)。


具体的なコードはこんな感じ。while 1で無限ループ作ってひたすらISBNをキューに突っ込むだけです。

#!perl
# script/jonk-client.pl
use strict;
use warnings;
use FindBin;
use DBI;
use Jonk::Client;

use lib "$FindBin::Bin/../lib";
use MyApp;

my $setting = MyApp->new(appdir => "$FindBin::Bin/../")->setting;

my $dbh = DBI->connect("dbi:SQLite:$setting->{jonk}->{db}",'','');
my $jonk = Jonk::Client->new($dbh);

while (1) {
    print 'input ISBN: ';
    next if <STDIN> !~ m/(\d+)/xms;

    my $isbn = $1;
    my $job_id = $jonk->enqueue('Book', $isbn);
    print "enqueued job_id: $job_id\n";
}


で、取り出して仕事する方はこんな感じ。実際のロジックはMyApp::Worker::Bookにあるので、そっちを呼び出しているだけですね。

#!perl
# script/jonk-worker.pl
use strict;
use warnings;
use FindBin;
use DBI;
use Jonk::Worker;

use lib "$FindBin::Bin/../lib";
use MyApp;
use MyApp::Worker::Book;

my $setting = MyApp->new(appdir => "$FindBin::Bin/../")->setting;

my $dbh = DBI->connect("dbi:SQLite:$setting->{jonk}->{db}",'','');
my $jonk = Jonk::Worker->new($dbh => {functions => [qw/Book/]});
my $worker = MyApp::Worker::Book->new(
    appdir => "$FindBin::Bin/../",
    setting => $setting,
);

while (1) {
    if (my $job = $jonk->dequeue) {
        warn "work job: $job->{arg}\n";
        $worker->work($job);
    } else {
        sleep(3); # wait for 3 sec.
    }
}


ここで呼び出されたWorkerが

の3つの処理を行います

Amazon Product Advertising APIを使う


これは超簡単。Amazonのサイトで自分のkeyとsecret、それにassociate_tagの3つを取得した後、
CPANにあるURI::Amazon::APAを使えば良いです。
弾さんのブログに使い方の記事もあるのでハマる所はないですね。
唯一のハマりどころは日本のURLである「http://ecs.amazonaws.jp/onca/xml」の方を使うということかしら。


コピペするだけで本の検索ができちゃうなんて素敵ですね!

形態素解析APIを使う


さて、つづいて形態素解析です。
これは何で必要になったかと言うと、FTSを使うものの、タイトルなどを一綴りのまま入れてしまうとヒットしないパターンがあるのでそれをどうにかしたかったから。
以下のブログも参考にしました。
http://blog.mwsoft.jp/article/34911345.html


なので、「MeCabを入れて…」と考えたんですが、「今回そんなにガッツリやる必要ないのでもっと楽なのがいいなぁ」と方向修正。
結果、Y!の日本語形態素解析APIです。
で、「これもCPANにあるんじゃないかなぁ」と思ったらやっぱりあったよ!
https://metacpan.org/module/WebService::YahooJapan::WebMA


ただしURLが古いので以下のように変更して使います。

$WebService::YahooJapan::WebMA::APIBase = 'http://jlp.yahooapis.jp/MAService/V1/parse';


具体的なコードはこんな感じ。ほぼSYNOPSISのままですが、word_listの要素が1つの場合に配列になっていなかったのでそこだけ対応しました。

    my $api = WebService::YahooJapan::WebMA->new(
        appid => $self->appid,
    );
     
    my $res = $api->parse(
        sentence => $sentence,
        ress => 'ma',
        response => 'surface,reading,base form'
    ) or croak $api->error;
    my @word_list = (ref $res->{ma_result}->{word_list} eq 'ARRAY')
        ? @{$res->{ma_result}->{word_list}}
        : ($res->{ma_result}->{word_list});
     
    $self->_to_string($self->_flatten(@word_list));

コレを使って、「タイトル」「作者」「出版社」の3つを形態素解析し、それらをspase区切りの文字にしてDBに突っ込むようにしてます。


具体例をあげるとPerl CPANモジュールガイドの場合には、
Perl CPAN もじゅーる モジュール ガイド がいど つみた 冨田 尚樹 なおき わーくす こーぽれーしょん ワークス コーポレーション」のような文字列がDBに突っ込まれるわけです。


ふむ!すごいですね!これで「perl」でも「cpan」でも「モジュール」でも「冨田」でも検索にかかるようになりました!それだけじゃなく「もじゅーる」みたいな読みでもかかるようになりました!そして、なんと「つみた」でもかかります!すごい!

SQLite FTSを使う


さてさて、最後にDBについて。
今回の本データをためるDBですが、今まで話に出て来た通りSQLiteのFTS4を使いました
(ファイルだから開発も楽だしね!)


FTSの使い方は簡単で、いつものcreate tableの文を以下のようにするだけです。

CREATE VIRTUAL TABLE book USING fts4(
    isbn,
    asin,
    title,
    author,
    manufacturer,
    words
);

「CREATE VIRTUAL TABLE」になっているところと、「USING fts4」になっているところが違う所で、他は特に変わりません。
ただ、UNIQUE INDEX利かななくなっちゃうため、アプリ側でユニーク確認をするようにしました。
(もしかしたら何かやり方あるのかもだけど調べきれず。Stack Overflowにはtriggerとかでどうにかする例があったけどそこまでの話ではなかったので深く見てないです)


SQLの発行方法は「match」というキーワードを使うだけ。
また、SQLite FTS3 and FTS4 Extensionsにあるように「where table名 match 'hogehoge'」というようにテーブル名を指定すればカラムをまたいで探してくれます。


具体的なqueryは以下のような感じ。

SELECT * FROM book WHERE book MATCH 'perl cpan';

これで全てのカラムでperlcpanに引っかかるデータを取り出せます。

DEMO的なやつ


以上でできあがった仕組みがこちらです。
https://github.com/monmon/vgadvent2012-app


それでは、写真で使っている様子を見て行きます。


1. まずjonk-client.plを起動するとISBNの入力待ちになります。

2. あとはひたすらバーコードリーダーでISBNを読みまくります。入力するとすぐに「早く次のISBNくれよぉ!」状態になるのでひたすら入れまくります。

3. で、一方違うターミナルでjson-worker.plを起動させておけば、以下のようにキューに入ったISBNを取り出し、一連の処理を始めます。

4. 結果、SQLiteに本データがたまるので、あとは全文検索で検索してうまーです。

5. 「タイトル」と「作者」の両方にあるような「単語」も検索できて便利ですね!!!!

まとめ


初めは「iPhoneでガンガンscanしてブクログに突っ込んで終わり」みたいなネタを考えたんですが、いざバーコードリーダーでやってみるとISBN読み込んでqueueに突っ込むだけだからよりガンガンscanできて楽しかったです。
(ちょうど大そうじの季節だったし、このネタにして良かった!)
まぁエラー処理とか全然考えてないし、既に「原作」と「漫画」のように2人の作者がいる場合にはauthorカラムに「ARRAY(0x7f8f6ac2fe30)」のように突っ込まれちゃうbugが判明したけど気にしません。
作ってみないとわからないことあるしね!リーン何ちゃらだ!


ということで、「コピペばっかりでも簡単にここまで作れるよ、でもコピペばっかりだと全然考慮が足りないから仕事ではちゃんと理解して物を作っていこう」という話でした!
明日は誕生日が終わったばかりの@tadasyさんです!今からでも間に合うのでみんなmention飛ばして祝ってみてください!

いろんな会社のいろんな人の話を聞けるのはやっぱり楽しい!YAPC::Asia Tokyo 2012に参加して来た!

こんにちは!こんにちは!monmonといいます!



2009年に初めて参加して、一昨年去年に引き続き今年もスタッフとして参加させてもらいました。



3年続けてスタッフ参加だったためメインホールのリーダー担当を任されたんですが、今回は初めての場所ということもあって失敗しないか心配で仕方なかったです。



と、失敗してない風なこと言いましたが、2日目におもいきりやらかしました。
「一番長いのが40分と思い込んでた」+「東大の学食に行ったら長蛇の列で帰って来たのが始まる5分前だった」の結果、
Perl 今昔物語のときに勘違いして10分前カンペ出してしまい、馮さんが941さんに「あれ?60分でしたよね?」と聞かなくてはいけないはめに。

すみません、僕のカンペのせいですみません。
慣れない学食なんて行くもんじゃないなってのと、ちゃんと時間は確認しないとダメだと心に刻みました。



ただ、こういう失敗はあったものの、スタッフ全体の動きはスムーズでした。
何かあればLINEでやり取りをして解決していたため、以前のようにバタバタすることがなかったなぁと思います。
# 去年はfacebookメッセージでやり取りをしたんですが、LINEの方がみんなに合っていたからなのか、去年よりもやり取りが活発でした。



会場の準備や片付けについても、牧さん941さんをはじめとする実行委員の人たちのノウハウとスタッフの数の多さのおかげでホントにビックリするくらいな順調具合。最後も「本当にもう終わっちゃうの?!」という感じであっという間に終わってしまい「でかいイベントなのに手際良くてすごいなぁすごいなぁ」という第三者目線で感動してました。

準備のワンシーンとして恒例となったZIGOROuメソッド(いわゆるバケツリレー)でのノベルティ詰めも貼っておきます。

この流れ作業、近くの人とワイワイ話すきっかけにもなってスタッフ間の交流に一役かっているんじゃないだろうかと感じてます。
スタッフ間でのぼっち対策ですね。



そうそう、話が前後しますが、今回僕は少し早めに着いたので他の人が来る間あんちぽさんのSQALEプロマイドを一人で堪能してました。

これはその夜の風景。

あんちぽさんに「どれが当たりですか?」とみんなで聞いているところです。
# ちなみに当たりは草原でかわいいポーズ取っているやつ。
# Ricoさんがコンプさせてたので参考:https://twitter.com/RicoImazu/status/251875701408550912



話がそれた。



去年も一昨年も思ったんですが、941さんのイベントを回す動きはすごいですね。準備の仕方、指示の出し方、問題回避ととっさの対応、業者さんへの直接ダメ出し、と、イベントをやるような人にとっては勉強になることたくさんあるだろうなぁと改めて思いました。



さて、スタッフの話はこの辺にして。
今回はメインホール担当だったのでトークもいくつか聞くことができました。



僕の印象に残っているトークはtypesterさんの「Perlハッカーは息をするようにCPANモジュールを書く。」です。
既にスライドは上がっていますが、
「そのモジュールを作った熱い想いをドキュメントに書こう!」
の流れはスライドではなく動画で観た方がいいので動画が上がったら観てみてください。



このトークの中に出てくる「ドキュメントは熱い想いをぶつけるところ」って表現がとても好きです。
僕は仕事をしているときに「こういうやり方もできたんだけど、〜って理由があったからこのやり方でやったんだ」というのを知るとだいぶ仕事がやり易くなります。
作った人のポリシーみたいなものが見えていると、それだけで「なんでここはこうなの?」的な面倒なやり取りがだいぶ減ってコストが下がるんです。
そういう相手の場合、その人が作る物がどういうものなのかがだんだんわかってきて、ますます仕事がやりやすくなり、そしてもう何て言うかとても楽なんです。なんでですかね、癖みたいな物がわかるってことなんですかね。
なので「ドキュメントに熱い想い」ってのはいい表現だなぁと。
とてもいい表現なので会社でも使いたいなと思っています。



もう一つ印象に残っているトークは最後のmizzyさんの話。

話を聞きながら

ってtweetもしたし、社内の日報にも「新人の子たちは動画観るといいよ」って感想を書きました。



去年のhidekさんの話もそうだけど、YAPCに来るといろんな会社のいろんな人の思いが聞けて楽しいです。
飲みに行ったりすれば聞けるんだろうけど、こんなにいっぺんにいろんな人の話が聞ける機会ってやっぱりそうそうないから、「どういう考え方でコードを書いている」「どういう考え方でサービスを作っている」「どういう考え方で行動している」みたいな話が聞けるのは貴重だなと感じています。
しかも一方通行で聞くだけではなく、質問すれば聞きたいことがもっと聞ける。とても楽しいです。



スタッフも楽しいけど話す側も楽しそうだから次回はそっちもやろうと思います。
次回のYAPC::Asiaも楽しみです!



最後に、今回も牧さん941さん長い期間かけての準備おつかれさまでした!ありがとうございました!

Perl の内部表現の一つはたまたま UTF-8 です。内部表現は何か分からないエンコーディングで、常に明示的にエンコードと デコードが必要ということにしておいた方がよいです。

追記

ajiyoshiさんの昔の記事がとてもわかりやすかったので参考に。
VOYAGE GROUP エンジニアブログ : あなたにも今日こそPerlの文字化けが理解できるたった一つの原則

元記事

記事タイトルはperlunifaqの「UTF8 フラグ」って何?からそのまま引用したもの。
昔は「UTF8フラグってよくわからない」ってなってたけど、この1文読むだけで「内部表現がたまたまUTF-8だったからUTF8フラグって言葉になったのか」とか、「そもそも内部表現が何かってことは気にする必要ないのか」って思えていい。


で、その辺の話を説明してくれている人がいたので引用。

Perl上級者コーナーPart01

503 :nobodyさん [↓] :2011/08/26(金) 02:37:40.52 ID:???
>>501
難しく考えすぎ。難しいと勘違いしているのなら、その原因はutf8(フラグ)という名称についてだけ

Perlには ”Perl文字列”(命名俺)と "バイナリ" の二つしか無い。(数値やオブジェクトはまあとりあえず忘れて)
"Perl文字列"は、その名の通りPerl言語専用の文字列。だからこの文字列をPerlの世界の外に存在しないし、外に出すことはない。
それはつまり、ネットワーク通信やファイル読み書きで外部に流してはいけないということを意味する。

"バイナリ"はただのバイトの並び。ネットワークやファイルから読み取るものは全部"バイナリ"(PerlIOという自動変換機能を使わない限り)
テキストファイルだろうがなんだろうが、それはPerlから見ればただのバイトの並びでしか無い。

Perlにおいて文字列を扱う関数や正規表現は すべて"Perl文字列"を引数に取る。
"バイナリ"も引数にとれるがそれは古いPerlとの互換性用の機能と考える。

じゃあその"Perl文字列"はどうやって作るかというとそれがEncodeモジュール。
ネットワークやファイルから与えられた"バイナリ"をEncodeモジュールを使って"Perl文字列"に変換する。

(utf8::*もあるが忘れていい。utf8.podにはことごとくEncodeモジュールを
使ってくださいと書いてある http://perldoc.jp/docs/perl/5.8.1/utf8.pod)

ちなみにソースコードに書いてある文字列。これもデフォルトでは "バイナリ" 。
互換性のことを考えると理由はわかると思う。use utf8;をするとソースコードの文字は"Perl文字列"になる。

文字コードはたくさんあるが、"Perl文字列"は一種類しかない。Encodeモジュールを使って
いろんな"バイナリ"を"Perl文字列"に変換できる。もちろんその逆も出来る。

US-ASCIIだろうが、Latin-1 だろうが、UTF-8だろうが、Shift-JISだろうが、EUC-JPだろうが
外部にあるものはみんな"バイナリ"。それをEncodeモジュールは"Perl文字列"に変換してくれる
504+1 :nobodyさん [↓] :2011/08/26(金) 02:38:03.50 ID:???
"Perl文字列"の内部の実装がUTF8だとしても、そのことを意識する必要はない。
UTF8フラグという名前が良くないが、これはPerl文字列フラグと読み替えればいい。
Perl文字列フラグがついていればPerl文字列(繰り返すが中身がUTF8なのは知らなくていい)

http://perldoc.jp/docs/perl/5.10.0/perlunifaq.pod
> 内部表現は何か分からないエンコーディングで、常に明示的にエンコードと
> デコードが必要ということにしておいた方がよいです。


だからPerl文字列フラグ付きのUTF8やLatin-1などというものはない。
Perl文字列フラグがついていれば、それはすべて同じPerl文字列。


あとは自分のプログラムは文字列を扱うとき、"Perl文字列"を基本として使うのか(統一された文字のモダンなやり方)
"バイナリ"を基本として使うのか(互換性重視の古いやり方)を決める。"Perl文字列"なら文字と1文字として扱える。
"バイナリ"だとそうはいかない。

Perl文字列を基本として使うなら、Perl外部のネットワーク、ファイルの読み書き時にすぐに変換して
Perl文字列メインでコードを書くことになる。

もし古いモジュールがPerl文字列対応になっていなければ、バイナリに変換して呼び出すラッパーを書くことになるだろう。


反対にバイナリを基本として使うのなら、バイト数ではなく文字数を知りたい時など、
文字として扱いたいところでPerl文字列に変換して関数を呼び出す。
そういうラッパーを書くことになるだろう。


どちらを基本にするかをしっかりと自覚しているのなら、あとはラッパーが変換し、関数を呼び出し、ラッパーがもとに戻す。
ラッパーが違いを吸収して、Perl文字列だろうがバイナリだろうが、自分で決めた基本に統一できるのだから状態を確認する手間はない。
505 :nobodyさん [↓] :2011/08/26(金) 02:38:14.84 ID:???
君が望んでいる通り、いろんな組み合わせや状態なんかを考えたくないだろう?
多言語対応で作るとなったとき、プログラムで使う文字はひとつに統一したほうがいいだろう?

それがPerlでは、"Perl文字列"になる。本当はPerl外も巻き込んで一つに統一されたほうが
いいんだけど、統一という言葉に一番近いUnicodeでさえ、文字集合は同じでも、文字を表すビット列には
UTF8、UTF16、UTF32などがある。近いうちに一つに統一されることは無いだろう。

だからPerl外部からPerl内部に渡って来る時の変換機能は絶対になくせない。
(もちろんこのアプリはこの文字コードだけを使いますと決めれば変換機能を
なくせはするだろうけど、それはPerlという言語の開発者が決めることではない)

古い互換性を保つコード・機能を忘れれば、Perl文字列はPerl文字列という物一つに統一されている。実にシンプル
逆にPerlの外ではいろんな文字コードが存在し変換の必要があるが、それはPerlのせいではない。

NKFやJcodeといったPerl文字列が存在しなかった時代に作られた「"バイナリ"を無理やり
文字列とみなして操作する」古いモジュールのことを忘れればこんなにシンプルなんだ。


長々と書いたが、結論はこれだけ。
Perl外部に存在する"バイナリ" と Perl内部に存在する"Perl文字列"
"バイナリ"を○○コードとみなして、"Perl文字列"に変換するのがEncodeモジュール
506 :nobodyさん [↓] :2011/08/26(金) 02:40:07.58 ID:???
おまけ >>501の図の修正版

US-ASCII
Latin-1
UTF-8       いくつものエンコードがある
Shift-JIS     混沌とした世界
EUC-JP
その他

↑Perl外の世界の文字 (UTF-8フラグ 全部OFF)

----------------------Encodeモジュール等で変換

↓Perl内の世界の文字 (UTF-8フラグ ON)

Perl文字列 (ないしょだけど実装はUTF-8フラグ付きのUTF-8エンコード)

           たった一つのシンプルな世界


 Encodeモジュールで変換しなかった文字はどうなるかって?
 それは文字ではない。ただのバイナリ、バイトの配列だ。

YAPC::Asia Tokyo 2011でtakeばかりだったのでgiveしていく

あっという間に終わってしまいましたYAPC::Asia Tokyo 2011
YAPC::Asia 2011だと思っていたんですが、下のつぶやきを見て「なるほどそうだったのか」とTokyoを入れ直しました。)

去年のボランティア参加が超楽しくて、なので今年も全部手伝いたいと思い、
941さんの

のつぶやきを見て速攻で「ボランティアやりたいです」メールをしたり、

を見て速攻で個人スポンサー参加しました。
普段の仕事でPerlを使わない僕ですが、YAPCは僕の中で楽しいお祭りなので「行動できることがあればすぐに」の結果です。
おかげで941さんから「ボランティア参加メールがすぐ来て嬉しかった」と嬉しい言葉を貰ったり、
個人スポンサーのページに表示されるという名誉を貰ったりしました。
個人スポンサーは最終的にかわいいTシャツまで貰えてお得感が満載でした。


ボランティア

ということでまずボランティアの話。
今年は会社勤めを始めたために3日間のうち2日間は通常参加、最終日だけボランティア参加をすることに。
「最終日だけのボランティアだと最後の片付けくらいしか手伝えることないんじゃないかな」
と思っていたら、その片付けさえもあっという間に終わってしまい「人が多い+みんな仕事理解しているってスゴい!」という驚きでいっぱいでした。
去年は個人的にかなりテンパったのでなおさらです。
みなさまおつかれさまでした。




個人的に安心して仕事できた理由は

  • ボランティア参加の人がたくさんいたこと
  • 去年に引き続いてボランティア参加している人がいること
  • そういう人がチームリーダーになっていて941さんと牧さんが安心して仕事を委譲できてるっぽかったこと
  • 牧さんがYAPC始まる前からdotCloud++って言うほどにシステム準備できたようなので、去年の「え?おかしい挙動する?ちょっとシステム確認してみる→やばい!問題あった!」なことがなさそうだったこと

などがあります。
「上にいる人があっぷあっぷになっておらず、ずっと座ってリラックスしている状態」だと下にいる人も安心して動けると思っているので、941さんと牧さんがあっぷあっぷになっていないというのはそれだけで心強かったです。



ボランティア人数が多かったことについては先にも書いた通り「仕事ないんじゃないか…」という不安がありました。
ただそれについても、交代要員のおかげでボランティアもセッションを見に行ける余裕ができて良かったように思います。
他にも、片付けの時に「ガムテープで床の芝生を取る」という去年とは違った仕事を貰えたのもボランティア人数が多かったおかげでしょう。



そして今回嬉しかったのは941さんの打ち上げ参加です。
去年は「赤帽の助手席に乗ってライブドアに戻る941さんをみんなで見送る」という、いかにもBGMがドナドナのようなシーンがあったんですが、
今回はSAGAWAさんに全部お任せできるということで941さんとの初打ち上げが実現しました。
企業スポンサーが多かったおかげでしょうか。ありがとうございますありがとうございます。


一般参加

次に一方参加者として。
PrePANの話が好きでした。



僕がPerlを好きになった理由は
「何だよ、この$とか@とか%とか!わけわかんないよ!」

「え、$calar?@rray?え、そんな意味があるの?なにそれかわいい!(%はよくわかんけど)」
と変わってきたところからで、そこから
cpanってのを使うと簡単にいろんなことできるぞ」
「わからなくてブログで質問したら教えてくれた」
みたいな流れでどんどん好きになっていきました。



未だにPerlをガリガリと書けないでいます。
でも、僕はPerlPerlのコミュニティが好きです。
ボランティア参加もPrePANの中で出てきた「コミュニティへの恩返し、コミュニティへの貢献意識」が動機で、
「発表できるものがないならせめて恩返し」なところから始まりました。



他の勉強会でも色んな人に会うわけだけど、YAPC::Asiaにはたくさんの人が居て、たくさんの話が聞けます。
Perlの話も聞けるし、Perl以外のこと、開発手法だったりインフラの話だったり他の言語の話なんかも聞ける。
それはとても貴重なことで、普段仕事をしていて「自分しか悩んでないんだろうなぁ」と思って試行錯誤してることも、
ここで聞くことで案外他の人も同じ悩みを持っていることを知ったり、またその解決方法自体があることを教えてもらえたりします。



Perlの知識足りなくて声かけられない」という思いは今もあるのですが、
「自分の会社のシステムを良くしていくために話を聞きたい」という気持ちがあって、
「どういうテストやってるんですか?」とか「どういう開発方法ですか?」みたいな話を聞いたりしました。
転職していった先輩たちや前職の先輩にも会えたので今の会社の話を聞いたり、
今までの仕事とどこが同じでどこが違うという話も聞きました。



give-and-takeで言うと、僕は色んなセッションや話を聞いてtakeばかりです。
これをPerlの方へすぐにgiveできる自信もまだありません。
ただ、今回得た知識を会社に持っていき、会社のシステムを良くしていくことはできると思っています。
そういう知識が積み重なって会社が良くなって、その結果でPerlコミュニティへgiveしたいと思います。



ということで、「あっという間に終わってしまった」とか言ったけど会社に還元するまでがYAPCだと思っているので「俺たちのYAPCはまだ始まったばかりだ!」な意識でこれからを過ごします。
キッズ・リターン的に言うと

YAPCもう終わっちゃったのかな。
馬鹿野郎!まだ始まっちゃいねえよ!

ですね。これからこれから。


おまけ

そういえば、去年同様、来年のYAPCページを探そうとポチポチやってたら既に作成されていて、レスポンスを見てみると

Last-Modified:Mon, 15 Aug 2011 04:05:48 GMT

ってなってて「アレ?そんな前なの?」とか思ってたら、その後

なつぶやきを見て「おぉ、すごい」と思いました。



あと、YAPC::Asiaのいろんな写真見てるとカメラ欲しくなりますね。一緒に仕事をした方々ありがとうございました。

正規表現のデバッグ方法

re=debugってやると詳細出してくれるの知らなかったのでコピペ。

Perlコーディング初心者質問スレ Part 62

502+1 :nobodyさん [] :2011/05/02(月) 02:42:13.10 ID: fV7VGj7j (1/2)
「文字列A        文字列B」
という 2 つの文字列の間に複数の空白がある行において
/\s*/ でマッチさせると、変数 $` (=$PREMATCH) には何も入りませんでした
なぜでしょうか?

入門書は「パターンマッチが複数解釈出来る場合には一番長い解釈にマッチする」とあります。
よって自分は \s* という「0 回以上のホワイトスペースの繰り返し」は
最も長い解釈では複数の空白にマッチし
$` には文字列A が代入されるはずだと考えたのですが

503+1 :1/2 [↓] :2011/05/02(月) 03:50:24.42 ID:???
>>502
その本の説明が曖昧なのか読み違えているのだろう。

http://perldoc.jp/docs/perl/5.10.1/perlre.pod#Quantifiers
> (始めた地点から)可能な限り多くを先にあるパターンでマッチングさせます。

/\s*/ は0文字以上の空白文字なので、先頭の0文字 = 文字列Aの前でマッチし、
そこで全てのマッチングが成功して終わる。更に長くマッチできる他の場所を
探すような事はしてくれない。

http://perldoc.jp/docs/perl/5.10.0/perlretut.pod

504+1 :2/2 [↓] :2011/05/02(月) 03:51:10.81 ID:???

> perl -Mre=debug -e "qq/abc def/ =~ /\s*/"
Compiling REx "\s*"
Final program:
  1: STAR (3)
  2:  SPACE (0)
  3: END (0)
minlen 0
Matching REx "\s*" against "abc def"
  0 <> <abc def>      | 1:STAR(3)
                 SPACE can match 0 times out of 2147483647...
  0 <> <abc def>      | 3: END(0)
Match successful!
Freeing REx: "\s*"

> perl -Mre=debug -e "qq/abc def/ =~ /\s+/"
Compiling REx "\s+"
Final program:
  1: PLUS (3)
  2:  SPACE (0)
  3: END (0)
stclass SPACE plus minlen 1
Matching REx "\s+" against "abc def"
Matching stclass SPACE against "abc def" (7 chars)
  3 <abc> < def>      | 1:PLUS(3)
                 SPACE can match 1 times out of 2147483647...
  4 <abc > <def>      | 3: END(0)
Match successful!
Freeing REx: "\s+"

Web::Queryの使い方練習のためにApple Storeのnew製品を取得する

この種のライブラリとして Web::Scraper があるが、Web::Scraper の DSL をおもいだすまでにどうしても時間がかかりがちだったので、こういう風なのもいいかなとおもった。jQuery は日常的につかってるので、わすれないし。

わーぃ。いつもWeb::Scraper使う時は昔書いたスクリプトを見直して書き方を思い出していたのでjQueryっぽい書き方ができると楽でいいです。
使い方忘れてもjQueryっぽいから思い出すのも早そうだし。



さっそく使い方を練習するために確認確認。
「何やろうかなぁ。ログインするようなページに対して使ってみようかなぁ」
とか色々考えてみたんですけど、とりあえず今は欲しいものがなかったので、新しいMacBook Proが出た時に、
「あ、出たんだ」
って思えるようなスクリプトを書くことにしました。


まずはページを確認。

http://store.apple.com/jp
Apple TVのところを見てみると「NEW」っていう画像が付いているので、きっと新製品にはこれが付くんだろうなと予測。



imgタグを確認してみるとclass="new-icon"が付いてるみたい。
これを元に製品名を取得してみる。


jQueryで取得

とりあえずjQueryでいつも通り書いてみる。
製品一覧は<div id="aos_family">内に全部詰まってるようなので、
そのdivの中にあるimg.new-iconを取得し、
さらにそれと同階層にあるテキストが欲しいので一旦上に上がってtextを取得。
ということでこんな感じ。

var product = jQuery('#aos_family img.new-icon')
  .parent()
    .text();

console.log(product);

Web::Queryを使って取得

Web::Queryを使って同じようにやってみようと思ったものの、
きっとjQueryのメソッドがそのまま実装されているはずはないと思うのでメソッドの確認。

[4:16]% perldoc -m Web::Query | ack '^sub ' 
sub wq { Web::Query->new(@_) }
sub __ua {
sub new {
sub new_from_url {
sub new_from_file {
sub new_from_html {
sub new_from_element {
sub end {
sub find {
sub html {
sub text {
sub attr {
sub each {
sub DESTROY {

多分、find,end,html,text,attr,eachを使って操作するみたい。
(これ見て気付いたのだけどuser_agent変更するインタフェースはないみたい)



parentがないので親に向かう操作はないとすると、さっきjQueryでやったやり方は捨てて違うアプローチで。
class="product"のliタグに製品一つずつが入っているので、その中にNEW画像があれば製品名を出力してみる。
ということでこんな感じにしました。

use strict;
use warnings;
use Web::Query;

wq('http://store.apple.com/jp')->find('#aos_family li.product')
    ->each(sub {
        $_[1]->each(sub {
            my(undef, $wq) = @_;
            return unless $wq->find('img.new-icon')->attr('class');

            warn $wq->text();
        })
    });

imgタグがあるかないかの確認はattr('class')で値返すかどうかで判断してます。



実際に実行実行。

[4:32]% perl sample.pl
   Apple TV ¥8,800   at sample.pl line 15.

でたーでたー。やりました。これはいじりやすいなぁ。

Coro::LWPで/etc/hostsを使う

本番環境と同じドメインでアクセスできるストレージ環境があって、
/etc/hostsのIPアドレスを書き換えて開発のチェックをしてたりします。
で、Coro::LWP使ったscriptでアクセスしてみたら本番の方に行ってしまいハマったのでメモ書き。



まずMacOSXだから/etc/hostsを読んでくれてないのかと思いだいぶハマりました。
Linuxでやっても同じように本番の方に行ってしまったので「あれ?おかしいな」と。
で、試しに/etc/hostsに

127.0.0.1	 www.google.co.jp

と書き、LWP::UserAgent使って

perl -MLWP::UserAgent -e 'warn LWP::UserAgent->new->get("http://www.google.jp")->code'

を実行してみると

403 at -e line 1.

ちゃんとエラーになる。



次にCoro::LWPを使って

perl -MLWP::UserAgent -MCoro::LWP -e 'warn LWP::UserAgent->new->get("http://www.google.jp")->code'

を実行してみると

200 at -e line 1.

200正常が返って来て/etc/hostsを見てくれてないことがわかった。



こっから色々ググって何か方法ないかと探してみたのだけどなかなか見当たらず。
で、Coro::LWPの中を覗いてみて

Coro::Util::inet_aton

ってのが使われてて、Coro::Utilを覗いてみると

AnyEvent::Socket::inet_aton

ってのが使われてて、さらにAnyEvent::Socketを覗いてみると

      require AnyEvent::DNS;

ってなってて、「コレっぽいかな?」と当てを付けてみたのだけど、そこから先に進めず。



「わからないことがあれば#perl-casualで聞くといいよ!」
ってのを色んな発表で聞いていたので良い機会だと思い試しに聞いてみました。

monmon
Coro::LWPについて分かる方いたら教えて欲しいのですが、
monmon
Coro::LWPで/etc/hostsの内容を読んでくれる方法ってありますか?
monmon
例えば、
monmon
[13:50@www5206u]% grep google /etc/hosts                                         [/tmp]127.0.0.1	 www.google.co.jp
monmon
と、hostsに書いておいて、
monmon
普通にLWPを使った場合は
monmon
[13:50@www5206u]% perl -MLWP::UserAgent -e 'warn LWP::UserAgent->new->get("http://www.google.jp")->code'
monmon
403 at -e line 1.
mobitaro
Google [text/html; charset=UTF-8]
monmon
hosts見てくれるのですけど、
monmon
Coro::LWP使うと
monmon
[13:51@www5206u]% perl -MLWP::UserAgent -MCoro::LWP -e 'warn LWP::UserAgent->new->get("http://www.google.jp")->code'
monmon
200 at -e line 1.
mobitaro
Google [text/html; charset=UTF-8]
monmon
DNSを見ちゃう
kazuho
AnyEvent::DNS? なのでそうですね
monmon
Coro::LWPの中で、Coro::Util::inet_atonを使ってて、Coro::Utilが
monmon
あ、そです
tokuhirom4
うん
monmon
AnyEvent::DNS使ってるようなので
monmon
どうにかやる方法がないのかなと
tokuhirom4
AnyEvent::DNS に hosts を見る機能がないから
monmon
思い。
monmon
そうすると/etc/hosts見るようにするには自分でCoro::LWPに似たようなものを作るしかないですかね?
tokuhirom4
自分だったら、どっかをフックして、/etc/hosts をよんでよしなにするけど
kazuho
とりあえずなおせりゃいいなら Coro::Util::inet_aton をフックするのがいいかなと
kazuho
/etc/hosts をちゃんと読むのめんどくさそう
tokuhirom4
そういう CPAN module とか
tokuhirom4
ありそうだけどw
tokuhirom4
つーかまあ
tokuhirom4
シリアスな用途じゃないなら
tokuhirom4
Coro::LWP を use する前に
tokuhirom4
Socket::inet_aton を保存しておいて
tokuhirom4
use Coro::LWP してから restore するとかでも
tokuhirom4
いいとおもうけど。
tokuhirom4
DNS が block するのが問題なければ。
tokuhirom4
カジュアル用途ならそれで十分とおもう。
tokuhirom4
なにかいてるのかしらんけど!
monmon
なるほどなるほど。ドメインを同じにしてあるステージング環境のステータスコードチェックしたいだけなので
monmon
その方法調べてみます。
monmon
ありがとございます

ということで説明する前に理解してもらって速攻で道筋を教えてもらえました。



出来上がったのが以下のような感じ。
Socket::inet_atonを上書きしてみたのだけどCoro::Utilの中でも使っていたので
Coro::Util::inet_atonごと上書きしました。

#!/usr/bin/env perl
use strict;
use warnings;
use LWP::UserAgent;

use Socket;
use Data::Dumper;

BEGIN {
    *inet_aton = \&Socket::inet_aton;
}
use Coro::LWP;

#*Socket::inet_aton = \&inet_aton;
*Coro::Util::inet_aton = \&inet_aton;

my $url = shift || "http://www.google.co.jp";
print LWP::UserAgent->new->get($url)->code;

https://gist.github.com/751157
「上手くいったーやったー。」と思って#perl-casualにありがとうありがとうの報告をしようと見てみたら

mash_
dnsmasqって/etc/hostsを見てくれるDNSサーバをローカルにたてたりしてます

という続きがあったのでそっちもやってみました。


dnsmasqのインストールと設定と起動

homebrewにあったのでそのままinstall

sudo brew install dnsmasq

名前解決で自分を見るように/etc/resolv.confに以下を追加

nameserver 127.0.0.1

あとは起動

/usr/local/sbin/dnsmasq

/etc/hostsを見ているか確認

% nslookup www.google.co.jp
Server:		127.0.0.1
Address:	127.0.0.1#53

Name:	www.google.co.jp
Address: 127.0.0.1

おぉぉ!上手くいってる様子。
超楽だ。これは便利。



IRC使ったことなくて聞くの躊躇してたんだけど今日聞いておいて良かった。
ありがとうありがとう。