Pages

2012年9月17日月曜日

Mongodbを使いTwitterデータをRで解析する

「Twitterから取得したデータを解析し、mongodbに格納し、Rで分析する」Node.jsアプリケーションを開発するための実験。プロトタイピング。Pragmatic Programmer流儀であれば、曳光弾プログラムでしょうか。

実験の環境

  • OSX 10.8.1 Mountain Lion
  • node.js 0.8.7
  • R 2.13.2
  • mongodb 2.0.7

内容

  • Twitter開発者登録
  • Streaming APIの利用
  • P.O.S parts of speech 単語解析
  • mongodbへのデータ登録
  • Rからmongodbを利用

twitterの開発者登録

TwitterAPIの利用は、OAuthが推奨されている(Basic認証でも今のところは大丈夫だが、いずれダメになる)ので、OAuth認証を行うため、twitterの開発者登録を行い、アプリを登録しなければならない。

Twitter開発サイト Streaming API パラメータの扱い

Twitter開発者アカウント登録の手順

http://ratememo.blog17.fc2.com/blog-entry-967.html を参考にしました。

  1. Twitterアカウントを用意する (mailアカウントが必要、既に登録済みのものはダメ)
  2. http://dev.twitter.com/ に取得したTwitterアカウントで、ログインする
  3. 「My Applicationの登録」リンクがあるhttps://dev.twitter.com/apps/new ので、その画面に移動しアプリを登録
  4. access level を "read" から "read & write"に変更
  5. Access Token (Access Token key と Access Token Secret)を取得し、控えておく

ntwitterライブラリを使いstreaming APIへのアクセスをテスト

OAuthのサンプルのほとんどが、ユーザサイドから利用するWebアプリの認証に関するもので混乱。(OAuthの構造を良く理解していない自分が悪い)

今にして思へば、Twitterの開発者登録の時に、取得した"Access Token"は、「自分自身(自分のアプリ)がtwitterAPIにアクセスするための暗号キー」で、アプリを利用するユーザ(consumer)が使用するものではないのです。随分と混乱してます。

結局、ntwitterライブラリのサンプルソースを使う。

twitter API利用Node.jsライブラリ ntwitter

エラーイベントが起きる。調査

前回のサンプルを作った時にはエラー発生しなかったのに。なぜだろう。
events.js:68
        throw new Error("Uncaught, unspecified 'error' event.");

https://github.com/AvianFlu/ntwitter/issues/57

とりあえず、エラーイベントを回避するため、サンプルコードに以下を追加。

    scream.on("error", function(error){
        console.log(arguments);
    });

再度、何度か実行してみると、成功する場合とエラーが返ってくる場合(argumentsを表示している)があるが、理由は不明。Twitter側の問題だろうか?

失敗した場合

$ node sample/ntwitter_sample.js 
{ '0': 'http', '1': 401 }

P.O.S moduleを組み込んで実行してみた結果

英語専用の形態要素分析 "part-of-speach POS Tagging"のライブラリpos-jsを使い、単語の分解と品詞のタグ付けを試みる。POS Taggingとpos-jsライブラリについては、以前 記事を書いたので見ていただけますと幸いです。

http://tech-baker.blogspot.jp/2012/06/part-of-speech-tagging.html

ここまでのプログラムソースは、以下の通り。

var twitter = require('ntwitter');    // twitter API用ライブラリ

// Twitter開発者登録の結果
var twit = new twitter({
    consumer_key        : 'xxxxxxxxxxxxxxxxxxxx',
    consumer_secret     : 'xxxxxxxxxxxxxxxxxxxx',
    access_token_key    : 'xxxxxxxxxxxxxxxxxxxx',
    access_token_secret : 'xxxxxxxxxxxxxxxxxxxx'
});

var pos = require('pos');             // POS Tagging用ライブラリ  

// 検索キーは、とりあえず appleで :-p
twit.stream('statuses/filter', {'track':'apple'}, function(stream) {
    stream.on('data', function(data) {
        // この行は日本語を省くため 結構いい加減
        if(data.text.toLowerCase().indexOf('apple') > 0){
            console.log(data.text);                        // オリジナルを表示
            var words = new pos.Lexer().lex(data.text);    // 単語分解
            var taggedWords = new pos.Tagger().tag(words); // 品詞のタグ付け jsonで返す
            for(i in taggedWords){
                var taggedWord = taggedWords[i];
                var word = taggedWord[0];
                var tag  = taggedWord[1];
                console.log(word + " : " + tag);
            }
        }
    });

    stream.on('end', function(response){
        //console.log('');
    });

    stream.on('destroy', function(response){
        //console.log('');
    });

    // エラーイベント対処
    stream.on('error', function(error){
        console.log(arguments);
    });

    // 5000ミリ秒で終了イベントを発生させた。これをしないと何時までのTwitterからのpushを受け続ける
    setTimeout(stream.destroy, 5000);
});

実行してみると

$ node sample/s_api_with_pos.js 
(途中省略)
@WhipeeDip One fifty and Apple will hand you a new phone. Though that thing is probably pushing “catastrophic damage ineligible for swap.”
@WhipeeDip : NN
One : NN
fifty : NN
and : CC
Apple : NNP
will : MD
hand : NN
you : PRP
a : DT
new : JJ
phone : NN
. : .
Though : IN
that : IN
thing : VBG
is : VBZ
probably : RB
pushing : VBG
“catastrophic : NN
damage : NN
ineligible : JJ
for : IN
swap : NN
. : .
” : NN


形態要素分析結果をmongodbに保存する

Node.js用のmongodb接続ライブラリ mongooseをインストールする。 # npm install mongoose 一発。

mongooseの使い方については、いずれまとめたいと思います。以下のサイトを参考にしました。

mongoose 本家 github

node.js + mongoose + mongodbで遊ぶ

上記のプログラムを改造して、mongodbに登録を行うように改造してみる。出来たプログラムを動かしてみると?

  • 終了しない。プログラムが。
  • でも、保存されたもよう。

mongodbのクライアントアプリ mongoで直接データを覗いてみます。

$ mongo
MongoDB shell version: 2.0.7
connecting to: test
> show dbs
local   (empty)
posdb   0.203125GB
test    (empty)
> use posdb
switched to db posdb
> show collections
postags
system.indexes
> db.postags.find()
{ "word" : "@PissWizardCunt", "tag" : "NN", "_id" : ObjectId("50557db5da8a0357b8000001"), "__v" : 0 }
{ "word" : "not", "tag" : "RB", "_id" : ObjectId("50557db5da8a0357b8000006"), "__v" : 0 }
(途中省略)
{ "word" : "5", "tag" : "CD", "_id" : ObjectId("50557db5da8a0357b800005b"), "__v" : 0 }
{ "word" : ".", "tag" : ".", "_id" : ObjectId("50557db5da8a0357b8000060"), "__v" : 0 }
has more
> exit
bye

has more が表示されているけど、残りのデータは、どのように表示させたら良いのかな?

// (途中まで同じ)

var pos = require('pos');            // POS Taggingライブラリを呼び出す
var mongoose = require('mongoose');  // mongodb接続用ライブラリを呼び出す

// スキーマの定義 mongooseは、ORMのように振る舞う。modelを定義する訳ですね。 
var Schema = mongoose.Schema;
var PosSchema = new Schema({
    tag: String,
    word: String
});
mongoose.model('PosTag', PosSchema);
// 接続
mongoose.connect('mongodb://localhost/posdb');
var PosTag = mongoose.model('PosTag');

twit.stream('statuses/filter', {'track':'apple'}, function(stream) {
    // TwitterからTweetを取得し
    stream.on('data', function(data) {
        if(data.text.toLowerCase().indexOf('apple') > 0){   // 日本語は省き
            var words = new pos.Lexer().lex(data.text);     // 単語に分解し
            var taggedWords = new pos.Tagger().tag(words);  // 品詞タグをつけ
            for(i in taggedWords){
                var postag = new PosTag();                  // 新規レコードを用意?
                postag.tag = taggedWord[1];                 // 値をセット
                postag.word = taggedWord[0];                // 値をセット
                postag.save(function(err){                  // 登録エラー処理
                    if(err){ console.log(err);}
                });
            }
        }
    });

// (後半省略)

});

統計解析環境Rからmongodbのデータを呼び出す

OSXはパッケージを本家からダウンロードして使っている。

mongodb接続ライブラリrmongodbをインストールするため、

  1. メニューのパッケージインストーラを起動
  2. ダウンロード先のミラーサイトを指定し
  3. rmongodb を検索し、インストール


 httpdヘルプサーバーを起動...   完了 
 URL 'http://cran.md.tsukuba.ac.jp/bin/macosx/leopard/contrib/2.13/rmongodb_1.0.2.tgz' を試しています 
Content type 'application/x-gzip' length 733855 bytes (716 Kb)
 開かれた URL 
==================================================
downloaded 716 Kb
 
ダウンロードされたパッケージは、以下にあります /var/folders/cw/qznw_j_1313d3stpq5zc34240000gn/T//RtmpAbXBrm/downloaded_packages
console画面から、rmongodbを読み込んでみて、テストしてみる。
> library(rmongodb)
rmongodb package (mongo-r-driver) loaded
Use 'help("mongo")' to get started.

うまく、rmongodbライブラリはインストールされたので、恐る恐るtwitterデータを確認してみる。

保存されているデータの構成(私の現在のdb, collection構成)

  ---------     ------------  
  database      collections
  ---------     ------------

  posdb ------> postags
  test (empty)
  local(empty)


$ r

R version 2.13.2 (2011-09-30)
Copyright (C) 2011 The R Foundation for Statistical Computing
ISBN 3-900051-07-0
Platform: x86_64-apple-darwin9.8.0/x86_64 (64-bit)
(途中省略)

> library(rmongodb)
rmongodb package (mongo-r-driver) loaded
Use 'help("mongo")' to get started.

> db <- mongo.create()
> mongo.find.one(db,'posdb.postags')
    word : 2     @PissWizardCunt
    tag : 2      NN
    _id : 7      50557db5da8a0357b8000001
    __v : 16     0
> mongo.find.one(db, 'posdb.postags', list(tag='NN'))
    word : 2     @PissWizardCunt
    tag : 2      NN
    _id : 7      50557db5da8a0357b8000001
    __v : 16     0
> mongo.find.one(db, 'posdb.postags', list(tag='CD'))
    word : 2     13
    tag : 2      CD
    _id : 7      50557db5da8a0357b800001a
    __v : 16     0
> mongo.find.one(db, 'posdb.postags', list(tag='RB'))
    word : 2     not
    tag : 2      RB
    _id : 7      50557db5da8a0357b8000006
    __v : 16     0
> quit()
Save workspace image? [y/n/c]: n

無事、データをRで処理することが出来るところまで、たどり着きました。今回の実験は終了。

2012年9月16日日曜日

山ライオンのmongodbをアップグレード

OSXを Mountain Lion に入れ替えて、それに対応するmacports のアップデートをやったのは、8月の末。 しかし、いくつかのソフトが動作しない、インストール出来ないなどが被害が続いた。 不具合は、すぐに解決するだろうとたかをくくっていたが、一向に新しいmacportsのバージョンが出ないので、少しづつ対応してみたので以下、まとめ。

  • w3m
  • gaushe
  • mongodb

w3m のアップグレード

原因が不明。以下のサイトを参考にして、とりあえずインストール (Mountain Lionでw3mがコンパイルできない件)[http://d.hatena.ne.jp/kimuraw/20120730/p1]

$ sudo port -f uninstall boehmgc
$ sudo port -s install boehmgc configure.optflags=-O0
$ sudo port clean w3m
$ sudo port install w3m

gaushceのインストール

これは、諦めて本家からソースをダウンロードし、コンパイルした。./configure , make , sudo make install でOK。

mongodb のアップグレード

  • mongodb は、boost に依存している
  • boost が、version 1.5 ではダメらしい。しかし、port インストーラでは
  • 普通にinstallすると boostのversion 1.5 を (これが最新版なので) インストールしようとする

なので、(googleさんにお世話になった結果)

  1. mongodb , boost を uninstall する
  2. boost は、1.49 のソースを取得し、インストールする
  3. mongodbは、依存関係を無視してインストールする -n オプションでインストールする

ことにした。

$ sudo port uninstall mongodb
$ sudo port uninstall boost
$ svn co -r 93341 http://svn.macports.org/repository/macports/trunk/dports/devel/boost/
$ cd boost/
$ sudo port install
(コンパイル完了まで1時間ぐらいかかったかな)

$ port installed | grep boost
  boost @1.49.0_0 (active)

$ sudo port -n install mongodb
--->  Computing dependencies for mongodb
--->  Configuring mongodb
--->  Building mongodb
--->  Staging mongodb into destroot
--->  Creating launchd control script
###########################################################
# A startup item has been generated that will aid in
# starting mongodb with launchd. It is disabled
# by default. Execute the following command to start it,
# and to cause it to launch at startup:
#
# sudo port load mongodb
###########################################################
--->  Installing mongodb @2.0.7_0
--->  Activating mongodb @2.0.7_0
--->  Cleaning mongodb

mongodbを起動する

$ sudo port load mongodb
Password:
Warning: port definitions are more than two weeks old, consider using selfupdate
$ ps ax | grep mongo
46294   ??  Ss     0:00.01 /opt/local/bin/daemondo --label=mongodb --start-cmd sudo -u _mongo /opt/local/bin/mongod --dbpath /opt/local/var/db/mongodb --logpath /opt/local/var/log/mongodb/mongodb.log --logappend ; --pid=exec
46295   ??  S      0:00.00 sudo -u _mongo /opt/local/bin/mongod --dbpath /opt/local/var/db/mongodb --logpath /opt/local/var/log/mongodb/mongodb.log --logappend
46296   ??  S      0:00.10 /opt/local/bin/mongod --dbpath /opt/local/var/db/mongodb --logpath /opt/local/var/log/mongodb/mongodb.log --logappend
46306 s000  S+     0:00.00 grep mongo

とりあえず、これで終了だが。


うっかりアップグレードすると、boostをアップグレードしてしまいそうだ。忘れないようにするには、どうしたらよいのだろうか?


2012年9月5日水曜日

RubyでDB2/AS400に接続

今はAS400とは言わず、”System i” と呼ぶのだそうです。 職場でAS400上のDB2のデータを加工する簡単なバッチ処理プログラムを作成する必要があったので、rubyで作成してみることにしました。

この記事は、RubyでDB2にODBC接続を行う方法についての覚書です。DB2との接続について調査し、いくつか試しましたが、

  • ibm_db と言うデータベースドライバがgemで配布されているが、SQL CLI (call level interface)が必要
  • AS400クライアントパッケージ Client Accessで、ODBCドライバーを提供している
  • 意外とruby + windows + ODBCの記事が少ない
  • gemで配布されているruby-ODBCも、odbcドライバをコンパイルする必要がある(コンパイラ必要)

と言う状態。様は直接DB2に接続するドライバの入手は意外と大変で、ODBC接続が応用が利きそうだが、バイナリのODBCを入手する必要があります。 結局、採用した構成は、以下の図の通り。

ruby DBI (+ DBD::ODBC) -- Client Access ODBC Driver -- DB2/AS400

  DBI : Database Interface 汎用データベースアクセス クラス。SQLをパースし、DBDに渡す。
  DBD : DBIの下位層で各データベースとの接続を行う。DB毎に用意される。

今回使用したWindows PC環境は、以下の通り。

  • OS: WindowsXP
  • Ruby: Active Script Ruby (ruby version 1.8.7 rubygems も同梱されたパッケージ)
  • IBM AS400接続クライアント Client Access インストール済み

ODBCデータソースの設定

Windowsのコントロールパネル内の”ODBCデータソース”(だったかな)で設定します。ポイントだけ記載。

  1. システムDSN に追加
  2. client access odbc driver を使用
  3. serverタグの"SQLデフォルトライブラリ"に、データベースオブジェクトが格納されているライブラリを指定

Ruby DBIモジュールとODBCのDBD(DataBaseDriver)をインストール

DBIは、gem から

gem install dbi
gem install dbd-odbc

ODBCドライバのインストールは、以下を参考にした。 SequelでSQL ServerにODBC接続する

  1. バイナリのDBD::ODBCライブラリを入手。ODBC Binding for Ruby
  2. i386-msvcrt-ruby-odbc.zip を入手し解凍し、所定の位置に配布

入手したsoファイル(odbc.so, odbc_utf8.so)を置いたのは、私の場合 "C:¥ruby¥lib¥ruby¥1.8¥i386-mswin32" (注: 私は ActiveScriptRuby を C:¥rubyにインストールし、パスを通しているので、この位置)


接続テストのサンプルプログラム

以下のサイトにサンプルコードが記載されていたので、一部追加して、転記します。

Ruby DB2 ODBC - how to access a DB2 database on an As/400 or iSeries

require 'odbc'

require 'rubygems' # baker add
require 'dbi'

dbh = DBI.connect('DBI:ODBC:MY_AS400', 'MY_USER', 'MY_PASS')
sth = dbh.prepare('select count(*) from my_table')
sth.execute

# Print out each row
while row=sth.fetch do
  p row
end

sth.finish
dbh.disconnect

実際に irbで試してみる

実際にirbを使って、接続テストをしてみた。

C:\Documents and Settings\baker>irb
irb(main):001:0> require "odbc"
=> true
irb(main):002:0> require "rubygems"
=> true
irb(main):003:0> require "dbi"
=> true
irb(main):004:0> db = DBI.connect('DBI:ODBC:データソース名','ユーザ','パスワード')
=> #<DBI::DatabaseHandle:0x3393f0c @trace_output=nil, @driver_name="odbc", @trace_mode=nil, @convert_types=true, @handle
=#<DBI::DBD::ODBC::Database:0x3393e1c @handle=#<ODBC::Database:0x3393e58>, @attr={}>>

irb(main):005:0> st = db.prepare('select count(*) from テーブル名')
=> #<DBI::StatementHandle:0x338f9e8 @row=nil, @prepared=true, @coltypes=nil, @trace_output=nil, @fetchable=false, @raise
_error=nil, @cols=nil, @trace_mode=nil, @dbh=#<DBI::DatabaseHandle:0x3393f0c @last_statement="select count(*) from G2DNF
", @trace_output=nil, @driver_name="odbc", @trace_mode=nil, @convert_types=true, @handle=#<DBI::DBD::ODBC::Database:0x33
93e1c @handle=#<ODBC::Database:0x3393e58>, @attr={}>>, @executed=false, @convert_types=true, @handle=#<DBI::DBD::ODBC::S
tatement:0x338f920 @arr=[], @statement="select count(*) from xxxxxx", @params=[], @handle=#<ODBC::Statement:0x338f984>>>

irb(main):008:0> st.execute
=> nil

irb(main):009:0> st.fetch
=> [234402]

irb(main):010:0> st.finish
=> nil

irb(main):011:0> db.disconnect
=> nil

st.finish と db.disconnectは、効いてないっぽい。(儀式?)

2012年8月31日金曜日

exports ってなに?

適当に使っていたけど、良く判らなかった exports 。ググって理解したことは、

  • moduleを輸出(export)する時に使う
  • requireを使って、moduleを呼び出すが、exportされたものしか利用できない
  • CommonJSの中の仕様で、当初のjavascriptにはない

と言うことで、早速 CommonJSを見る。

CommonJS Module 仕様 (英語)

相変わらず、英語に弱くて大変。むりやり、訳してみる。 (流石に、酷い訳なので、後で少しづつ手直しします。すみません。)

(ここから訳開始)

Moduleの状況 (使われ方、仕様)

  1. 一つのmoduleの中は、変数 requireがあり、それ(require)は、クラス(Function)だ。 (javascriptでは、クラスは関数の定義として提供される。この文章では、ある場合にはクラスと訳したほうが良いようだ。)
  2. "require"関数は、モジュールの識別子を(パラメータとして)受け取る。
  3. "require"は、外部のmoduleから輸出されたAPIを(戻り値として)返す。
  4. もし、依存の矛盾(cycle)が発生した場合、外部のmoduleは、その通過中の依存の一つから要求された瞬間に実行を完了することができない(APIを返せない)かもしれない。 この場合、"require"によって戻されたオブジェクトは、少なくとも、現在の(実行遅延中の)モジュールの実行前に外部モジュールによって輸出されたものを含んでいなければならない。
  5. もし、要求されたモジュールがなにも返すことができない場合、"require"は、エラーを投げなければならない。
  6. "require"クラスは、"main"プロパティを持つかもしれない。これ(main)は、読み込み専用で、削除されず、プログラムの最上位"module"であることを示します。もし、このプロパティが提供された場合、それ(main)は、メイン・プログラムの"module"オブジェクトに対し参照独立?でなければならない。

    1. "require" 関数は、"paths"属性を持つかもしれない。"Paths"は、path文字列の優先順位をしめす配列であり、(優先順位の)高いものから低いものへの順で、最上位モジュールの配置位置へのパスを格納している。
    2. "paths"プロパティは、sandboxの中に存在してはいけない。(sandboxは、セキュアなモジュール・システムである)
    3. "paths"属性は、全てのモジュールから、参照され独立していなければならない。referentially identical
    4. "paths"オブジェクトを他のオブジェクトで置き換えることは、無効だろう。
    5. "paths"属性が存在する場合、"paths"の内容の直接の修正は、(それが)対応するモジュールの検索の振る舞いにしたがって、反映されなければならない。
    6. "paths"属性が存在する場合、それは検索パスの消耗的なリストである。それは、ローダが内部的に、問われたパスの前後の他の配置を見るようなものだ。"paths"属性が存在する場合、それはローダの特権であり、パスを解決する、標準化する、正規化するために優先的に(使われる)。
  7. 一つのモジュールの中には、"exports"と呼ばれる変数がある。これは、オブジェクトであり、モジュールは、(モジュールが)実行できるように(自身の)APIを(そのオブジェクトに)加える。
  8. モジュールは、輸出(外部へのAPIの提供)の唯一の手段として、"exports"オブジェクトを利用しなければならない。
  9. モジュールには、変数 "moodule"が存在していなければならず、それはオブジェクトである。
  10. "module"オブジェクトは、読み取り専用であり、その"id"プロパティを削除しないこと。(idは)最上位のモジュールの"id"である。"id"プロパティは次の様なものでなければならない、つまり "require(module.id)"は、exports オブジェクトを返しますし、そのオブジェクトはmodule.idに由来するものです。(言い換えると、module.idは、別のモジュールに渡すことが出来、オリジナルのモジュールを返さなければならないことが要求される。)
  11. "module" オブジェクトは、URI文字列を持つかもしれないが、それはリソースを示す完全な形のURIであり、そのURIから、moduleは作られる。"uri"プロパティは、sandbox内に存在してはいけない。(参照できないので)

Module識別子について

  1. モジュール識別子は、/(スラッシュ)で構成された一連の単語である。
  2. 単語とは、CamelCaseスタイルの識別子と"."と".."でなければならない。
  3. モジュール識別子は、".js"のようなファイル識別子を持つかもしれない。
  4. モジュール識別子は、相対的か、トップレベルかも知れない。(ファイルパスのこと?)
  5. もし、最初の単語が、"."とか".."だったら、それは相対位置を示しています。
  6. 最上位のモジュール識別子は、概念的なモジュール・名前空間のルートの位置として解決される。
  7. 相対的なモジュール識別子は、"require"が書かれ、呼び出されたモジュールの識別子に対する相対的なものとして、解決される。

あいまいな点

この仕様は、以下の重要な事項で、相対的にあいまいな点を残している。

  1. モジュールは、データベース、ファイルシステム、ファクトリパターンのオブジェクト(Function)やライブラリにリンクし変換可能になっているかどうか
  2. PATHは、モジュール識別子を解決するためのモジュールローダにサポートされているか

サンプルコード

// math.js
exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
        sum += args[i++];
    }
    return sum;
};
// increment.js
var add = require('math').add;
exports.increment = function(val) {
    return add(val, 1);
};
//program.js
var inc = require('increment').increment;
var a = 1;
inc(a); // 2

module.id == "program";

(ここで訳終了)

参考にしたサイト

以下のサイトは、大変参考になりました。ありがとうございます。

module.exports と exportsの違い

モジュールを輸出する - exports

まず、使われる側は、公開したい関数を exports というグローバル変数(?)のプロパティに代入します。 すると、他のプログラムは、エクスポートされた関数を使用できるようになります。逆に exports にセットしていない関数や変数は他から見えないので、グローバル名前空間が汚れるのを気にする必要がありません。素晴らしいですね。 (中略) モジュールを自身に含める - include モジュールを使う手段に include() 関数というのが用意されています。 require()と同じようにモジュールのファイル名(- .js)を渡して実行するのですが、exports変数にセットされた関数たちを、自分とこの名前空間に展開します。ローカル変数とか、名前がかぶっていたりすると、知らないうちに上書きされたりして危険な感じがします。

2012年8月27日月曜日

JenkinsをApacheの背後で動かす

この記事とタイトルは、その多くを以下のJenkins本家の記事から得ました。

Running Jenkins behind Apache

Jenkinsとは

CI(継続的インテグレーション)のための支援ツールとして有名です。実際に用意されている基本の機能は、Web画面で簡単に操作、設定できる拡張cronと理解しても良いかな。 以下は、日本語サイトからの引用です。

Jenkinsとは

Jenkinsは、ソフトウェアのビルドやcronで起動するジョブなどの繰り返しのジョブの実行を監視します。これらのうち、Jenkinsは現在次の2つのジョブに重点を置いています。

  1. 継続的な、ソフトウェアプロジェクトのビルドとテスト: つまり、CruiseControlやDamageControlが行うこと。 一言で言えば、Jenkinsは、容易ないわゆる「継続インテグレーションシステム」を提供し、開発者が変更をプロジェクトに統合でき、ユーザーがより新しいビルドを容易に取得できるようにします。自動化された継続的なビルドは、生産性を向上させます。
  2. 外部で起動するジョブの実行監視: cronによるジョブやprocmailのジョブで、リモートマシンで動作するものも含みます。例えばcronについて言えば、出力をキャプチャーした定期的なメールだけ受信し、こつこつとそれを見ます。おかしくなっていることに気がつくかどうかは、すべてあなた次第です。Jenkinsは出力を保存し、 いつおかしくなったのか容易に把握することができるようになります。

さくらのVPSにインストール

私の環境では、すでにjava ランタイムはインストールしてあります。本家の指示の通り、実行。

$ sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
$ sudo rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
$ sudo yum install jenkins
$ sudo service jenkins start

インストールが終了すると、起動スクリプトは、既にセットされているので、手動で起動してみます。デフォルトの実行ポートは、8080で起動します。 私の場合、8080ポートは制限しているので、ローカルから、w3m等で実行されていることを確認しました。

Apacheの背後で動かす

さて、80ポートは既にapacheが使っていますので、ディレクトリの一つとしてjenkinsが稼働しているようにみせるため、Apacheのproxy機能を使うことにしました。 具体的には、http://mydomain/jenkins で、jenkinsにアクセスできるようにします。

  1. モジュール mod_proxy, mod_http を有効にする (httpd.conf)
  2. jenkinsをプロキシ機能で利用するための設定を、追加(今回は専用の設定ファイル /etc/httpd/conf.d/jenkins.conf)します。

jenkins.confの内容は、以下の通り。

ProxyPass /jenkins http://localhost:8080/jenkins
ProxyPassReverse /jenkins http://localhost:8080/jenkins
ProxyRequests Off
<Proxy http://localhost:8080/jenkins*>
Order deny,allow
Allow from all
</Proxy>

この状態で、apacheとjenkinsを再起動して http://mydomain/jenkinsに、アクセスするとアクセス出来ることは出来るのですが、画像とかcssファイルとかが、読み込まれていません。Proxy利用したため、正しいパスを指定していないことが原因です。

インストールされている /etc/sysconfig/jenkins (初期設定記述)を変更します。ポートの変更とかも、このファイルで行います。 path調整のための変更箇所は以下の通り。

JENKINS_ARGS="--prefix=/jenkins"

あと、サーバ起動時に自動起動するようにしておきます。

# chkconfig --level 345 jenkins on
# chkconfig --list | grep jenkins
jenkins         0:off   1:off   2:off   3:on    4:on    5:on    6:off

セキュリティ

初期状態のままでは、誰でもアクセス出来てしまいますので、以下のサイトを参考にセキュリティ設定を追加しておきました。

jenkins セキュリティ設定

  1. 一般ユーザの権限の制限
  2. 管理者の追加
  3. ユーザ追加を不可に などでしょうか。

それにしても、このロゴは間抜けな感じで好きじゃない。有能な執事を意味しているらしいのだが ...........

2012年8月19日日曜日

joinコマンドは使えるか

csvデータなどテキストベースのデータの塊の処理を必要とする仕事が時々まわってくる。 大抵は、検索系なので、grepしたあと、少し手作業で加工するだけ。しかし、たまに複数のデータファイルを特定のキーで結びつけたりしなければならなくなる。 今までは、リレーショナル・データベースにぶち込んでSQLで処理していた。が、先日なんでもUnixコマンドでやりましょう的な記事を見つけて、大変可能性を感じたので、やってみることにした。

用意したのは、以下の2つのCSVファイル。

やりたいことは、sample-地域.csvの県名とsample-ナンバー.csvの出身地を結びつけ、両方の情報を表示すること。SQLで言うjoin

$ cat sample-地域.csv 
ID,地域,県名,
1001,九州,宮崎,
1002,九州,鹿児島,
1003,九州,福岡,
1004,中国,山口,
,,,
,,,
,,,

$ cat sample-メンバー.csv
ID,名前,性別,歳,出身地,,,,,,
1001,山田,男,50,福岡,,,,,,
1002,田中,女,32,宮崎,,,,,,
1003,中山,女,18,福岡,,,,,,
1004,佐藤,女,19,大分,,,,,,
1005,佐藤,男,48,山口,,,,,,
,,,,,,,,,,
,,,,,,,,,,
,,,,,,,,,,
(続く)
,,,,,,,,,,
,,,,,,,,,,

sort , awk, sed そして join

joinコマンドを使うことが今回の目的だ。joinコマンドを使う上で重要なことは、対象ファイルがキーでソートされていること。その下準備のために、sort, awk, sedのコマンドを利用するのだ。

実行したコマンド行は、3行。 実行内容だけ解説します。各コマンドの詳細は helpやmanを見てください。

$ sed -e "/^[,I]/d" sample-メンバー.csv | sort -t , -k 5 | awk -F , '{print $1,$5,$2,$3,$4}' > member.txt
$ sed -e "/^[,I]/d" sample-地域.csv | sort -t , -k 3 | awk -F "," '{print  $1,$3,$2}' > area.txt 
$ join -j 2 area.txt member.txt 

1行目のコマンドライン

sedは、stream editorの略。-eは、"コマンド"を指定するオプション、/^[,I]/d のdは削除(delete)のd、正規表現/^[,I]/に一致する行を削除することを意味する。

sortは、名前の通り並び替え。-t ,でデータが ","で仕切られていることを指定し、-k 5で5番目の項目が並べ替えの基準どすることを指示する。

awkは、万能レコード加工ツールかな。色々出来るらしいので、もはや言語と言って良いかも。{}で囲まれたコマンドを実行する。-F ,はデータのデリミタ(仕切り)が","であることを指定し、{}の中で、項目の並べ替えを行っている。$nが項目のIDだ。 以上のパイプ "|"でつなぎ、仕事をするのが、Unix流だ(そうだ)。

3行目のコマンドライン

3行目のjoinコマンドは、今回のポイント。-j 2は、項目の2番目がジョインするためのキーであることを指定している。キーの指定は、各ファイル個別に出来るが、やはり細かいところはmanを見てください。

要約

1行目で、sample-メンバー.csvファイルを 1) 不要な行を削除し、2) キーでソートし、3) 行毎に整形しspace区切りのファイル member.txtを作る。

2行目で、同様に sample-地域.csvから、area.txtをつくる。

3行目で、2つのファイル area.txt member.txt をジョインする。

作成結果、出力結果は、以下の通り。
CodeRider:sample baker$ cat area.txt 
1001 宮崎 九州
1004 山口 中国
1003 福岡 九州
1002 鹿児島 九州

CodeRider:sample baker$ cat member.txt 
1004 大分 佐藤 女 19
1002 宮崎 田中 女 32
1005 山口 佐藤 男 48
1001 福岡 山田 男 50
1003 福岡 中山 女 18

CodeRider:sample baker$ join -j 2 area.txt member.txt 
宮崎 1001 九州 1002 田中 女 32
山口 1004 中国 1005 佐藤 男 48
福岡 1003 九州 1001 山田 男 50
福岡 1003 九州 1003 中山 女 18

rubyでやってみる

これと、ほぼ同様なことをrubyでやってみた。

総当たりでレコードを調べ、キー値が一致していたら、標準出力に出力すると言う結構乱暴なプログラム。

require 'ostruct'
require 'csv'

area = OpenStruct.new
area.filename = "sample-地域.csv"
area.key = 3 - 1

member = OpenStruct.new
member.filename = "sample-メンバー.csv"
member.key = 5 - 1

CSV::open(area.filename,"r") do |a|
    line_a = a.compact
    next if line_a.size == 0
    CSV::open(member.filename,"r") do |b|
        line_b = b.compact
        next if line_b.size == 0
        $stdout.print [line_a + line_b].join(","), "\n" if a[area.key] === b[member.key]
    end
end

実行結果は、
$ ruby another_one.rb 
1001,九州,宮崎,1002,田中,女,32,宮崎
1003,九州,福岡,1001,山田,男,50,福岡
1003,九州,福岡,1003,中山,女,18,福岡
1004,中国,山口,1005,佐藤,男,48,山口

OpenStructを使ったのは、JavaScriptのように自由にプロパティが設定出来て、整理しやすかったから。(使ってみたかったとかではない :-p)

OpenStructマニュアル(英語)

もっと短くできるかもしれないけど、まあサッとつくるプログラムとしてはこんなもんでよいのでは。

Unixコマンドとrubyとどちらが便利だろうか? また、リレーショナル・データベースにぶち込んでSQLで処理すると言う手もあるが。


(余談) Numbersはすごい


余談だが、サンプルは Appleの表計算ソフト Numbersで作成した。Excelと違って、一寸面白い。表計算ソフトではなく、表プレゼンテーションソフトと言ったほうが良い。 あまり使っていなかったのでビックリした。気づいたことは

  1. シートと表は異なる
  2. シートは、ExcelのWorksheetとは全く違う。むしろBookにちかい
  3. 表には、一つの表を描くのが正しい
  4. 表は、シート内で複数配置することが出来、それぞれがExcelのworkseetのような機能をもつ
  5. その構造の御陰で、他の表のレイアウト(例えば幅とか)に影響されず、別の表を作成できる
  6. シートは、表(オブジェクト)を配置する台紙の役割。これがレポート用紙になる

この構造は、慣れると大変便利だ。Excelで文書を書くのは愚の骨頂だと常々思うが、Numbersならば、ページレイアウトを自然に出来るだろう。

ただ、Excelのように使うと問題ある。今回の場合、CSVへの出力だ。

  • 表毎にCSVを出力出来るが、無駄な行や列を作成しておくと、その分 ,, が生成される。(取り除かなければならない)
  • 行末の処理が、ちょっとおかしい(行末項目のソートが出来ないなど)。CSVを作成する場合は、行末に,を付けるように出力するか、後で補完したほうが良いようだ。

2012年7月28日土曜日

山ライオンにハマル

OSX Mountain LionとCommand Line Toolsに関する、雑記事

Svnコマンドがない

Mauntain Lionをインストールした。 なかなか、楽しいOSなのだが、開発者にとっては、やはりOSの更新は鬼門のようだ。

開発中のソフトウエアソースの更新をしようと、svn update コマンドを実行しようとした。svnコマンドがないとエラーがでる。 ない? てっきり、MacPortsでsubversionをインストールしていたのかと勘違いしたが、実は svnコマンドは、Xcode依存だった。

StackOverFlowで既に話題

Snow Leopard以来、Xcodeをアップデートしていなかった。Lionにアップデートした時には、Xcodeが削除されることはなかったのだが、Mountain Lionでは削除されるようだ。 調べてみると、新しい Xcodeのバージョン4.4が、AppleStoreで無償で公開されている。

早速、インストール。住処は、WiMAX 1本立ちなので、2時間ほどかかってしまった。 インストール後、Xcodeを起動し、

  1. menu (Xcode) -> (Preference..) のメニューを選び、
  2. (Download)タグを選び、
  3. コンポーネント(Command Line Tools)をインストール(ボタン)する

そう、Command Line Toolsの中に、svnコマンドが入っているらしい。

MacPortsの更新に失敗する

最初に、Xcodeのインストールの前に、MacPortsを更新しようとして失敗した。 これは、やはり Xcodeの削除とともに、gccコンパイラがないためらしい。

$ sudo port selfupdate

WARNING: Improper use of the sudo command could lead to data loss
or the deletion of important system files. Please double-check your
typing when using sudo. Type "man sudo" for more information.

To proceed, enter your password, or type Ctrl-C to abort.

Password:
--->  Updating MacPorts base sources using rsync
MacPorts base version 2.0.4 installed,
MacPorts base version 2.1.2 downloaded.
--->  Updating the ports tree
--->  MacPorts base is outdated, installing new version 2.1.2
Installing new MacPorts release in /opt/local as root:admin; permissions 0755; Tcl-Package in /Library/Tcl

Error: /opt/local/bin/port: port selfupdate failed: Error installing new MacPorts base: shell command failed (see log for details)

やはり、これも Command Line Toolsをインストールして成功。


Xcodeのインストール場所が変更になった御陰で

Xcodeのインストール場所も、 /Developer から、/Applicationsに変更されたようだ。そのため、警告が出てしまう。

developer_dirのWarning

この場合、/opt/local/etc/macports/macports.conf を少しいじる必要がある。

# 非アクティブのポートは削除します
# sudo port uninstall inactive
# 非アクティブのポートを削除した後、調べてみると、警告がでる!
$ port installed inactive
Warning: Your developer_dir setting in macports.conf points to a non-existing directory. Since this is known to cause problems, please correct the setting or comment it and let macports auto-discover the correct path.
None of the specified ports are installed.
# ----------------------------------------------
# readonlyになっているmacports.conf を修正
# (修正前)
# # Directory containing Xcode Tools
# developer_dir       /Developer
# (修正後) コメントアウトしただけ
# # Directory containing Xcode Tools
# # developer_dir       /Developer
# ----------------------------------------------
$ sudo chmod 777 /opt/local/etc/macports/macports.conf
$ sudo vim /opt/local/etc/macports/macports.conf
$ sudo chmod 444 /opt/local/etc/macports/macports.conf
# 再度、実行すると警告は消えています。
$ port installed inactive
None of the specified ports are installed.

2012年6月17日日曜日

[Twitter Streaming API] Twitterの中心でappleを叫ぶ

Twitterのグローバルタイムラインから情報を収集することを検討してみる。 調べた結果では、Twitter Streaming APIを使うのが一番と言うことで、早速、試してみた。

Twitter Streaming API ドキュメント

Twitter Streaming APIは、WebアプリケーションAPIで利用するRESTな動きではなく、一度Requestを投げて、Twitterとつながるとだらだらとtweetを返してくる。 クライアントは、それを待ち受けて処理し続ける。つまりpushされてくるAPIだ。

twitterの中心で"apple"を叫ぶ人達

Node.JSを使う。

試作では、エンドポイント filterを使い、キーワード_apple_を含むグローバルタイプラインを取得している。出力は、標準出力に流している。終了させるためには、Ctl-Cだ(芸がない)。

// ファイル名は app.js にした
var https = require('https'); // httpsモジュールでつなぎます。
var host  = 'stream.twitter.com'; // Streaming APIを提供するサイト。

var find_one = 'apple';

// getメソッドに渡すオプションをまとめ
var options = {
    host : host,
    port : 443,
    path : '/1/statuses/filter.json?track=' + find_one, 
    // filterエンドポイントを利用。この当たりは一寸怪しい
    auth : 'TWITTERID:PASSWORD' // 自分のtwitterアカウントのユーザID,パスワードをセット
};

// リクエストオブジェクトを取得します
var req = https.get(options, 
  function(response){
    console.log('Response: ' + response.statusCode);
  }
);

// リクエストオブジェクトが、イベント"response"を受け取った場合
req.on('response', function(response){
    // レスポンスがデータだった場合(?)
    response.on('data', function(chunk){
        // 受け取ったデータをJSON形式に変換します
        var tweet = JSON.parse(chunk);
        // tweetがuserプロパティ、nameプロパティが存在する場合
        if('user' in tweet && 'name' in tweet.user){
            // 漢字、日本語などはfilterされず流されてくるため、ここで除いておいた
            // ちょっといい加減(今後の課題)
            if(tweet.text.toLowerCase().indexOf(find_one) > 0){
            console.log('[' + tweet.user['name'] + ']\n' + tweet.text);
        }
    }
  });
});

// リクエストオブジェクトが、イベント"error"を受け取った場合、
req.on('error', function(e){
  console.log(e);
});

// 異常例外でプログラムがストップする場合を避けるため
process.on('uncaughtException', function(err){
  console.log('uncaughtException: ' + err);
});

実行してみます。
$ node app.js 
Response: 200
[Q U E]
so my Screen just went black!! I don't feel like going downtown to the Apple store today
[Yoel Wonsey]
@JmSamuel1415 Just go eat a rocky mountain apple
[jose antonio rivera ]
RT @ComediaChistes: — Oye, me enteré que Apple demandó a tu novia por violación de patentes. —¿Si? ¿Por qué? —Porqué está tan plana como ...
[iPhone iPad Actu]
#iphone #geek Post-Jobs, Apple unleashes new iPhone http://t.co/nYJFdAYA #ipod #ipad #apple
[iPhone iPad Actu]
#iphone #geek Steel Battalion Heavy Armor Gameplay Partie 2HD (fr) http://t.co/kqwcvnBt #ipod #ipad #apple
[danielle Bergman]
RT @AcneSkinSite: An apple a day keeps the acne away! Honey helps restore a youthful look! Try an Apple Honey Mask for endless benefits& ...
[Dol Noppadol]
RT @JAlLBREAK: Apple Now Throws in a MagSafe 2 Converter with Every Thunderbolt Display http://t.co/Jdi486kK
[Alniedawn F E]
RT @AcneSkinSite: An apple a day keeps the acne away! Honey helps restore a youthful look! Try an Apple Honey Mask for endless benefits& ...
[TAYLOR Ann'Marie]
Shorty jus gave me sum fye shxt..apple bacardi sprite tini! Fye

(だらだらと続く)


これからの課題

とりあえず、いろいろな人のblogを見ながら、試作してみたが、いろいろと気になる。

  • Twitter Streaming APIのドキュメントによれば、filter エンドポイントを利用するのは、POST メソッドだが、GETで動作した。
  • 漢字は、filter対象にならず、送り込まれるようだ。英語だけのTweetだと判断する必要があるかな。
  • 開発者アカウントを使う必要があるな。
  • とにかく、Twitter streaming APIのドキュメントを良く読む必要がある。
  • Node.JSのhttp,httpsモジュール、processオブジェクトは再勉強する必要あり。

今後使ってみようと思うのは、このモジュール ntwitter

ntwitter:Node.JSで、twitter streaming APIを扱う便利なモジュール


processオブジェクトについて

Node.JSドキュメント process から Event: 'uncaughtException' function (err) { } 発生した例外がイベントループまでたどり着いた場合に生成されます。 もしこの例外に対するリスナーが加えられていれば、 デフォルトの動作 (それはスタックトレースをプリントして終了します) は起こりません。

Twitter Streaming API ドキュメントの一部

Twitter APIのドキュメントから、Streaming API関連の一部を引用してみた。後で訳すことにしよう


Overview

The set of streaming APIs offered by Twitter give developers low latency access to Twitter's global stream of Tweet data. A proper implementation of a streaming client will be pushed messages indicating Tweets and other events have occurred, without any of the overhead associated with polling a REST endpoint.

Twitter offers several streaming endpoints, each customized to certain use cases.

  • Public streams: Streams of the public data flowing through Twitter. Suitable for following specific users or topics, and data mining.
  • User streams: Single-user streams, containing roughly all of the data corresponding with a single user's view of Twitter.
  • Site streams: The multi-user version of user streams. Site streams are intended for servers which must connect to Twitter on behalf of many users.

An app which connects to the Streaming APIs will not be able to establish a connection in response to a user request, as shown in the above example. Instead, the code for maintaining the Streaming connection is typically run in a process separate from the process which handles HTTP requests:

FAQ から

How do I use the Twitter platform?

Twitter offers a platform with a number of different ways to interact with it.

Web Intents, Tweet Button and Follow Button is the simplest way to bring basic Twitter functionality to your site. It provides features like the ability to tweet, retweet, or follow using basic HTML and Javascript. You can also embed individual tweets.

More complex integrations can utilize our REST, Search, and Streaming APIs. The Streaming API allows you to stream tweets in real time as they happen. The Search API provides relevant results to ad-hoc user queries from a limited corpus of recent tweets. The REST API allows access to the nouns and verbs of Twitter such as reading timelines, tweeting, and following.

To use the REST and Streaming API, you should register an application and get to know the ways of OAuth and explore Twitter Libraries.

What is the version of the REST API?

In the API documentation there is a version place marker in the example request URL. Currently only one version of the API exists, that version is 1. This means any REST API queries will be of the format: https://api.twitter.com/1/statuses/user_timeline.json. The Streaming API is currently on version 1 as well, while the Search API is unversioned.

How are rate limits determined on the Streaming API?

At any one moment of time there are X amount of tweets in the public firehose. You're allowed to be served up to 1% of whatever X is per a "streaming second."

If you're streaming from the sample hose at https://stream.twitter.com/1/statuses/sample.json, you'll receive a steady stream of tweets, never exceeding 1% X tweets in the public firehose per "streaming second."

If you're using the filter feature of the Streaming API, you'll be streamed Y tweets per "streaming second" that match your criteria, where Y tweets can never exceed 1% of X public tweets in the firehose during that same "streaming second." If there are more tweets that would match your criteria, you'll be streamed a rate limit message indicating how many tweets fell outside of 1%.

How do I keep from running into the rate limit?

Caching. We recommend that you cache API responses in your application or on your site if you expect high-volume usage. For example, don't try to call the Twitter API on every page load of your hugely popular website. Instead, call our API once a minute and save the response to your local server, displaying your cached version on your site. Refer to the Terms of Service for specific information about caching limitations. Rate limiting by active user. If your site keeps track of many Twitter users (for example, fetching their current status or statistics about their Twitter usage), please consider only requesting data for users who have recently signed in to your site. Scale your use of the API with the number of users you have. When using OAuth to authenticate requests with the API, the rate limit applied is specific to that user_token. This means, every user who authorizes your application to act on their behalf, has their own bucket of API requests for you to use. Request only what you need, and only when you need it. For example, polling the REST API looking for new data is inefficient for both your application, and the Twitter API. Instead consider using one of the Streaming APIs as a signal of when to make REST API requests. Consider using a combination of the APIs to achieve your goal. You can't do everything with one API, but by combining them you can do most things. For example, instead of using the Search API for all your querying, use the Streaming API to track keywords and follow users Tweets, and save the Search API for the more complex queries. These are just some example strategies. To work out different solutions for you to achieve your goals, search through discussions on the topic or start your own.

How do password resets effect application authorization?

When using OAuth, application connectivity and permissions do not change when a user resets their password on twitter.com. The relationship between Twitter, a user, and a third-party application do not involve a username and password combination. When a Twitter user changes their password, we'll now ask the user whether they would also like to revoke any of their application authorizations, but any revocations are manually executed by the end user.

As of March 12, 2012 it is still possible to connect to the The Streaming APIs via Basic Auth credentials. If the password belonging to a user account that connects to the Streaming API via basic auth is changed, the new password will need to be used to regain that connection.

I don't want to require users to authenticate but 150 requests per hour is not enough for my app, what should I do?

Rethink not wanting to require authentication. It's the primary means to grow your application's capabilities. We recommend requiring authentication to make use of potentially 350 requests per hour per access token/user. Consider also investigating whether the Streaming API's follow filter will work for you.

How do I count favorites?

Favorite counts aren't available as part of tweet objects in the REST, Streaming or Search APIs at this time. User streams and Site streams both stream events when an authenticated user favorites tweets or has their tweets favorited. Using these authenticated streaming APIs, you can count favorites in real-time as they happen. This is currently the only scalable means to count favorite activity.

How do I count retweets?

Tweets in the REST and Streaming APIs contain a field called retweet_count that provides the number of times that tweet has been retweeted. You can obtain the retweet count for any arbitrary tweet by using GET statuses/show/:id.

You can count retweets as they happen by using a The Streaming APIs. In particular, User streams and Site streams allow you to be streamed retweet events about/around an authenticated user in real time.

I keep hitting the rate limit. How do I get more requests per hour?

REST & Search API Whitelisting is not provided. Resourceful use of more efficient REST API requests, authentication, and Streaming APIs will allow you to build your integration successfully without requiring whitelisting. Learn more about rate limits or see the rate limiting FAQ for more information.

2012年6月11日月曜日

Part-of-Speech Tagging (品詞タグ付け) 事始め

英文の中から名詞のみを抽出することを検討している。 日本語と違い、英語はスペースで分かち書きしているので、「の、は、が」とかを分析する形態要素解析は簡単なようだ。 したがって、自然言語処理(NLP Natural Language Processing)の技術のうち、品詞判断を実現するツールを探してみた。

この手のツールは、POS Tagging(Part-of-Speech Tagging: 品詞タグ付け)と呼ぶそうだ。 いろいろと探してみた結果、Eric Bill氏が1993年に開発した "Bill Tagger"が有名で、かなりのツールはこのエンジンを利用している。

http://en.wikipedia.org/wiki/Brill_tagger

Bill氏は、今マイクロソフト社に勤務しているとのこと。彼のオリジナルツールは、"Error-driven"であり、"tranformation-based"なんだそうで、辞書ベースと文脈解析で最初の解析を終えたあと、利用者が解析を修正することで辞書を成長させていくものらしい。

今回の目的は、自然言語処理の勉強でなく、名詞のみが判断されれば良いし、また出来ればjavascriptのツールが欲しかったので、pos-jsを試してみた。

https://github.com/fortnightlabs/pos-js
http://code.google.com/p/jspos/

pos-jsのインストール

作業ディレクトリに、ライブラリをインストールしてみる。

pos-js (js pos:javascript parts-of-speech tagger)も、Eric Bill氏のエンジンを利用している。

    [baker@www tagger-js]$ npm install pos
    npm http GET https://registry.npmjs.org/pos
    npm http 200 https://registry.npmjs.org/pos
    npm http GET https://registry.npmjs.org/pos/-/pos-0.1.1.tgz
    npm http 200 https://registry.npmjs.org/pos/-/pos-0.1.1.tgz
    pos@0.1.1 ./node_modules/pos

サンプルプログラムの実行

まずは、サイトのサンプルプログラムを試してみた。

作業ディレクトリで作成したサンプルコード sample.js
var pos = require('pos');
// 分かち書き解析モジュールで単語分解する
// クラス Lexer()を用意し、lexメソッドで単語分解を実行し、wordsに配列として保管。
var words = new pos.Lexer().lex("This is some sample text. This text can contain multiple sentences.");
// クラス Tagger()を用意し、tagづけ(品詞判断)を行う。アウトプットは[単語、タグ]の配列の配列。
var taggedWords = new pos.Tagger().tag(words);
for(i in taggedWords){
    var taggedWord = taggedWords[i];
    var word = taggedWord[0];        // 単語を抽出
    var tag = taggedWord[1];         // タグを抽出   
    console.log(word + " /" + tag);
}

実行結果です。
$ node sample.js 
This /DT     # 前置詞
is /VBZ      # 動詞, 現在形
some /DT     # 前置詞
sample /NN   # 名詞
text /NN     # 名詞
. /.         # ピリオド
This /DT     # 前置詞
text /NN     # 名詞
can /MD      # 助詞
contain /VB  # 動詞
multiple /JJ # 形容詞
sentences /NNS  # 名詞、複数形
. /.         # ピリオド

タグの一覧を引用しておきます。NNPは(たぶん名詞)てな意味ですね。

    --- ----------------------- -------------
    TAG sense                   sample
    --- ----------------------- -------------
    CC Coord Conjuncn           and,but,or
    CD Cardinal number          one,two
    DT Determiner               the,some
    EX Existential there        there
    FW Foreign Word             mon dieu
    IN Preposition              of,in,by
    JJ Adjective                big
    JJR Adj., comparative       bigger
    JJS Adj., superlative       biggest
    LS List item marker         1,One
    MD Modal                    can,should
    NN Noun, sing. or mass      dog
    NNP Proper noun, sing.      Edinburgh
    NNPS Proper noun, plural    Smiths
    NNS Noun, plural            dogs
    POS Possessive ending       Õs
    PDT Predeterminer           all, both
    PP$ Possessive pronoun      my,oneÕs
    PRP Personal pronoun         I,you,she
    RB Adverb                   quickly
    RBR Adverb, comparative     faster
    RBS Adverb, superlative     fastest
    RP Particle                 up,off
    SYM Symbol                  +,%,&
    TO ÒtoÓ                     to
    UH Interjection             oh, oops
    URL url                     http://www.google.com/
    VB verb, base form          eat
    VBD verb, past tense        ate
    VBG verb, gerund            eating
    VBN verb, past part         eaten
    VBP Verb, present           eat
    VBZ Verb, present           eats
    WDT Wh-determiner           which,that
    WP Wh pronoun               who,what
    WP$ Possessive-Wh           whose
    WRB Wh-adverb               how,where
    , Comma                     ,
    . Sent-final punct          . ! ?
    : Mid-sent punct.           : ; Ñ
    $ Dollar sign               $
    # Pound sign                #
    " quote                     "
    ( Left paren                (
    ) Right paren               )

2012年6月3日日曜日

Webな統計分析環境 RStudio とMongodb

さくらVPS に、NoSQLデータベース Mongodbをデータの取得・保存先に使い、統計分析を行う為の環境を用意した。
  • 統計解析ツール R
  • データベース Mongodb
  • Rとmongodbを連携させるためのRライブラリ rmongodb
  • R利用のためのユーザインターフェース R Studio
最終的に目指しているのは、データを自動で定期的にmongodbにため込み、RStudioで適宜分析する環境だ。
インターネットのデータ -- (Spidering Tool) -- mongodb -- rmongodb -- R -- R Studio

R & R Studioのインストール

既に拡張パッケージ EPEL(Extra Packages for Enterprise Linux)を利用して入れば、Rのインストールはyumを使うだめの簡単作業だ。
http://fedoraproject.org/wiki/EPEL
依存しているパッケージが20ぐらいあったような。
R Studioは、統計解析ソフトRのためのIDE(統合開発環境)だ。Rの為のEclipseやVisualStudioと言ったところか。サーバ版をインストールした。
http://rstudio.org/
# Rをインストールする
# EPELを使っていることが前提  
  $ sudo yum install R
# RStudioのrpmパッケージを取得し、インストール
  $ wget http://download2.rstudio.org/rstudio-server-0.96.228-x86_64.rpm
  $ sudo rpm -Uvh rstudio-server-0.96.228-x86_64.rpm 
インストールすると、サーバが起動し、起動スクリプトもセットされる。RStudioは、初期設定ポートが8787なので、8787ポートは開けてあげること。 http://yoursite.com:8787/ にアクセスするとログイン画面が表示されるはず。ユーザ情報は、Linuxユーザ情報をそのまま利用している。




rmongodb

Mongodbのインストールは、随分前にインストールしていたので、省略。yumでインストール出来たはず。コンパイルとか、随分とインストールに時間が掛かったような記憶がある。
rmongodbは、Rのライブラリで、Rに、mongodbへの接続機能を追加するものだ。R本体はデータの取得先に対する機能は充実しておらず、その当たりはライブラリで頑張ってくださいと言う姿勢らしい。
githubで公開しているrmongodbは、上手くコンパイル出来なかった。CRAN(the Comprehensive R Archive Network)から取得すると上手くらしい。
# mongodbとRを接続するためのライブラリ rmongodbをインストールする
# githubに配布されているものは、なぜかインストールに失敗するので、CRANから取得しインストール
 $ wget http://cran.r-project.org/src/contrib/rmongodb_1.0.3.tar.gz 
 $ sudo R CMD INSTALL rmongodb_1.0.3.tar.gz 

R Studioを実行してみる

まず、コマンドラインからrmongodbが動作するか確認してみた。
> R                 # コマンドラインからRを起動
(途中省略)
> library(rmongodb) # rmongodbをロードする
rmongodb package (mongo-r-driver) loaded
Use 'help("mongo")' to get started.

R Studioから、同じ事をやってみる。
  1. http://yoursite.com:8787/ にアクセス
  2. linuxユーザでログイン
  3. 右下のペインのPackages タブを選択
  4. rmongodb にチェックを入れる
すると、右のコンソールに上記のコマンドラインと同じメッセージが表示される。(当たり前だけど)
なかなか、使い勝手がよさそう。




Mongodbへのデータ登録と取得

サンプルソースをみて、Rからmongodbを利用する方法など確認してみた。
# insert
mongo <- mongo.create()                            # 接続
if (mongo.is.connected(mongo)) {                   # 接続確認
    buf <- mongo.bson.buffer.create()              # bson用の1レコードバッファをR内に用意
    mongo.bson.buffer.append(buf, "name", "baker") # レコードバッファに属性と値をセット
    mongo.bson.buffer.append(buf, "age", 50L)
    b <- mongo.bson.from.buffer(buf)               # レコードバッファをbson形式に変更
    mongo.insert(mongo, "test.people", b)          # db:test, collection:peopleに追加
}

# select
mongo <- mongo.create()
if (mongo.is.connected(mongo)) {
    buf <- mongo.bson.buffer.create()
    mongo.bson.buffer.append(buf, "age", 18L)
    query <- mongo.bson.from.buffer(buf)

    # Find the first 100 records
    #    in collection people of database test where age == 18
    # queryの内容は、{age:18L}
    # レコードコレクションに対するカーソルが提供される
    cursor <- mongo.find(mongo, "test.people", query, limit=100L)
    # Step though the matching records and display them
    # nextメソッドで、カーソルを順に動かしていく
    while (mongo.cursor.next(cursor))
        print(mongo.cursor.value(cursor))
    mongo.cursor.destroy(cursor)                  # カーソルの開放
    # 現在、100件も入っていないので表示されませんが :-p
}
実際にMongodbにデータが格納されていることを、mongodbのクライアントソフトmongoで確認してみる。
# mongo (クライアントソフトから実行結果を確認してみる)
> show dbs     # データベース一覧をみる
admin
error_logger
local
test
> use test     # データベース test に移動
switched to db test
> show collections   # 現在のデータベース内のコレクションをみる
foo
people
system.indexes
users
> db.people.find()   # test.people を全件検索してみる
{ "_id" : ObjectId("4fcb042f3eee4d39039e1b87"), "name" : "baker", "age" : 50 }
> 

2012年5月13日日曜日

Markdown記法を使いこなしたい

最近、公私ともにプロジェクト管理にredmineを使うことが増えてきた。
redmineは、便利なツールだが気になるのが、記述に使っているtextile記法。以前は、tracやpukiWikiを使っていたこともあり、なにかツールを替える都度に新しい記法を覚えるは面倒くさい。ましてや、最近はプログラムでhtmlタグを書くのも色々と簡易的な記法が増えてきて、それはそれで便利だがちょっと、もう少し統一してもらっても良いような気がする。
また、昔からExcelやdocでドキュメントを作ることも嫌いだった。やむを得ず作る時もあるのだが、普段使いではない。textで書いて置けば検索が楽だし、ポータビリティも良いではないか。
なにが、言いたいのか。つまり、一つの記法で全てに対応出来ないだろうかと言うことだ。
表:アプリケーション毎に異なる記法
| case     | style     |
|----------|-----------|
| redmine  | textile   |
| sphinx   | markdown  |
| trac     | trac      |
| pukiWiki | hatena    |
方針として できる限りmarkdownで書く。pandoc コマンドで様々な形式に変換する。 とした。
  • テキストファイル(生)を見た時に、普通の人が一番判りやすそうなのは、markdown形式だと感じたので、これを基準にする。
  • pandocコマンドは、各OS向けにインストーラがそろっているし、変換のバリエーションが多く、導入しやすそう。
pandocの本家サイトはここ
pandocをインストールしてみた。早速、テストしてみる。

Markdown記法で書かれたサンプル

各書式の記述は、いろいろと方言があるようだ。本家の説明が一番良いだろう。

本家の各書式説明
markdownの文法
Markdown記法は結構、書き方が乱暴でもなんとかなりそうな感じ。h1タグはh1.や#でも良いし。
ex)samle.txt
今日行うべきテスト
===============

準備すること
----------
#. テスト要件のヒアリング
#. テストデータの作成
#. テストケースの作成

テスト環境
---------
* ユニットテスト
* 統合テスト
* 自動化スクリプト

### 気になること ###

テスト  備考
------ ----- 
テスト1 テストサンプル
テスト2 テストサンプル
テスト3 テストサンプル

<pre>

    | file | ----- lan ------- | server | ------ (wifi) -------- | client | 

</pre>

html, textile, rst 形式に変換する

markdown形式のファイルをhtmlに変更する。
$ pandoc sample.txt -s -t html -o samle.html
// -s standalone オプション header情報とかつけてくれる。
// -f (from)元の形式 defaultは、markdown形式か?あるいは自動判別か?
// -t (to)変換先の形式
// -o (out)出力先ファイル
redmineとかで利用するためにtextile形式に変換したり、sphinxで利用するためにrst (reStructured Text)に変換したりする。
$ pandoc sample.txt -t textile -o sample.textile
$ pandoc sample.txt -t rst -o sample.rst

おまけ:: S5形式でプレゼンテーションスライドを作成する

S5とは、A Simple Standards-Based Slide Show System で、要するに htmlとjavascript,cssで作成されたhtmlドキュメントだ
S5の本家サイト
$ pandoc sample.txt -s -i -t s5 slide.html
// -i list itemを順番に表示させてくれる
プレゼンテーションファイルは上記の通りで変換出来るが、補助スクリプト(javascriptとかcssとかテンプレート)を入手しなくてはならない。
  1. 上記のs5本家からS5本体をダウンロード
  2. 解凍して、最初の階層にあるuiフォルダを上記のslideファイルと同じディレクトリにコピーする
  3. ディレクトリ名は、s5に変える (あるいはslide.htmlの中の指定フォルダをいじる)
これで、プレゼンテーションが完成。上記のファイルをプレゼンテーション
なんか、最後の図とか表示されないし、使い方をもう少し研究しなければならないが

課題が残った

ここまで来ると、何でもやりたかったのだが、色々制約や勉強しなければならないことが多い。
  • pdfツールも同梱(markdown2pdf)が、日本語は通らないらしい
  • texも使えるが、TeXはすっかり忘れてしまった
  • epubも出来るらしい (いや、これは形式の問題より内容を充実させないと哀しい電子書籍になってしまう)
  • 画像の扱いについて研究しなければ
  • markdown形式では、inline htmlでのtable形式がダメのようだが....
  • サンプルの記述がおかしいかったので、修正 (2012/5/27)

2012年4月15日日曜日

Dance! Dance! Dance! Perfume

GitHubにPerfume降臨

GitHubにperfumeが登場して話題となったのは、確か3月。Perfumeの世界戦略用のWebサイトで公開されたコンピュータグラフィックスによるダンス動画に使用されたモーション・ピクチャー・データと中田ヤスタカの音楽データをGitHubで提供されたのだ。サンプル・プログラムと共に。
忙しくてダウンロードしたままだった。やっと遊んでみることにした。

Perfume Global site
http://www.perfume-global.com/

GitHub perfume-dev
https://github.com/perfume-dev/

ダウンロードしたデータ

ダンスのMotion Picture Data (BVH形式)
  • あーちゃん用 aachan.bvh
  • かしゆか用 kashiyuka.bvh
  • ノッチ用 nocchi.bvh
音楽 nakata yasutaka 様謹製
  • perfume_globalsite_sound.wav
サンプルプログラムは、Processing用のソースをダウンロード。 とりあえず動かしてみる。3人が準備体操しているだけ。なるほど orz

プログラムソースファイル構成
  • p5f_sample.pde -- メインルーチン
  • PBvh.pde -- pvhデータを扱うモデル
  • PvhParser.pde -- PBvhで利用。bvhデータを解析。
  • BvhBone.pde -- PBvhで利用。フレームの位置情報を扱っている?

サンプルプログラムを修正

プログラムソースをみると、dataフォルダに A_test.bvh, B_test.bvh, C_test.bvhのデータがあり、これを読み込んで動かしているようなので早速、ダウンロードしたbvhファイルに入れ替えて再度実行。
おお!動きました。でも音がない。

  1. 入手したbvhファイル、wavファイルをプログラムソース直下のdataフォルダにコピー
  2. p5f_sample.pdeの中のファイル読み込み部分のファイル名を変更

以下の方のブログを参考に minimライブラリを利用してみました。
http://r-dimension.xsrv.jp/classes_j/minim/

サンプルのままでも、面白いのですが、せっかくサンプルソースが公開されているのですから、ちょっとだけ手を入れてみます。
  • 三人のフレームは色を変えてみる
  • フレームの形状をsphere (球)からbox(立方体)に変更
  • フレームの末端、つまり頭、足首、手首を大きくした
// p5f_sample 修正
import ddf.minim.*; // サウンドライブラリ minimをインポート
Minim minim;        // 音源クラス用の変数を宣言
AudioPlayer player; // 演奏クラス用の変数を宣言

BvhParser parserA = new BvhParser();
PBvh bvh1, bvh2, bvh3;
public void setup()
{
  size( 1280, 720, P3D );
  background( 0 );
  noStroke();
  frameRate( 30 );
  
  // dataフォルダからダンスデータを読み込む
  bvh1 = new PBvh( loadStrings( "aachan.bvh" ) );
  bvh2 = new PBvh( loadStrings( "kashiyuka.bvh" ) );
  bvh3 = new PBvh( loadStrings( "nocchi.bvh" ) );

  // sound add この当たりがサウンド操作
  // 一回鳴らしたきりになっているがどうしたら良いか不明
  minim = new Minim(this);
  player = minim.loadFile("Perfume_globalsite_sound.wav");
  player.play();

  loop();
}

public void draw()
{
  background( 0 );
  camera((float) mouseX, (float) mouseY, 100.f, (float) (width/2.f), (float) (height/2.f), 0.f, 0.f, 1.f, 0.f);
  pushMatrix();
  translate( width/2, height/2 + 100, 0);
  scale(-1, -1, -1);
  bvh1.draw( millis() , 1); //aachan 識別子として Integer値を渡すことにした
  bvh2.draw( millis() , 2); //kashiyuka
  bvh3.draw( millis() , 3); //nochi
  popMatrix();
}

void stop()
{
  player.close();
  minim.stop();
  super.stop();
}
PBvh.pde ソースだけ、手を入れてみました。
// PBvh.pde の一部
  public void draw( int ms , int co) // 識別子用のパラメータ co を追加
  {
    parser.moveMsTo( ms, 3000 ); 
    BvhBone root = parser.getBones().get(0);
    update(root);
    draw(co); // protected な drawに間接的に識別子を渡す
  }
//
// 途中 省略
//
  protected void draw(int co)
  {
    //fill(color(100));
    // 識別子 co によって、塗りつぶし色を変更している。
    if(co == 1){
      fill(127,0,0);
    }else if(co == 2){
      fill(0,127,0);
    }else if(co == 3){
      fill(0,0,127);
    }else{
      fill(color(100));
    }
    
    for( BvhBone b : parser.getBones())
    {
      pushMatrix();
      translate( b.absPos.x, b.absPos.y, b.absPos.z);
      //sphere(2);
      box(5); // 立方体に変更
      popMatrix();
      if (!b.hasChildren()) // 子を持たない。終端のことか。
      {
        pushMatrix();
        translate( b.absEndPos.x, b.absEndPos.y, b.absEndPos.z);
        //sphere(2);
        box(25); // でかい立方体に変更
        popMatrix();
      }    
    }
  }
実際はもっと格好良いのだが!
修正したプログラムは、ここでみることが出来ます。appletなので起動まで時間が掛かかりますね。
Perfume Dance sample applet
気になること
  • 音がループしないし、動きと同期していない。
  • もっと、大胆にカスタマイズしたい。youTubeでは既に多くの人が楽しんで凄いことになっている

右手首をくるくると回しながら、三人が立ち位置を入れ替えているところは、何度観てもかわいいなあ

2012年1月31日火曜日

jQuery Mobile 最初の一歩か

Railsがらみで、jQueryを少しだけ使っていたら、世の中はjQueryだらけ、そしてモバイルもjQueryが最有力という。
すこし、まとめて jQueryと jQuery Mobileについての記事を読んでみた。
最初の一歩なので、基本の"き"を羅列してみる。

jQueryのポイント

  • java Scriptの言語拡張は、おこなっていない
  • 繰り返し記述がない
  • 返値は、jQueryオブジェクト
  • ドキュメントへのアクセスは $()で
  • CSSへのアクセスは css() で
  • ドキュメント読み込み完了イベントは、$(function(){処理})
  • エレメント読み込み完了イベントは、$(エレメント).ready(function(){処理})
なんか、おまじないみたいになってきた。

jQuery Mobileの初歩

  • ひとつのhtmlに記述する
  • html5のカスタム属性 data-* を使う
  • ページ遷移はページ内リンク
jQueryのcss, ajax機能を基礎にあらゆるモバイル端末向けのドキュメントをSPIで記載するのだな。

サンプル・ソース

以下のサイトから、サンプルソースを拝借しました。
http://www.amitpatil.me/getting-started-with-jquery-mobile-twitter-app/

jQeury Mobile + java scriptで作成したシンプルなTwitter閲覧ソフトだ。
jQueryMobileで書かれたhtml (index.html)と、実際の動作を受け持つ javascript (app.js)の2本のファイルで構成されている。
ユーザIDを検索すると、最近の10ツイートを表示する。
サンプル

index.html


<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.css" />
 <script src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
 <script src="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.js"></script>
 <script src="js/app.js"></script>
</head>
<body>  
    <div data-role="header" data-position="inline">
        <h1>jQueryMobile Twitter</h1>
    </div>
    <div style="padding-left:1%;">
        <input type="search" name="tuser" id="tuser" value="" width="3"/>
  <a data-inline="true" data-role="button" href="#" data-theme="c" class="ui-btn ui-btn-inline ui-btn-corner-all ui-shadow ui-btn-up-c" onClick="javascript:getTweets()">
   <span aria-hidden="true" class="ui-btn-inner ui-btn-corner-all">
    <span class="ui-btn-text">Get Tweets</span>
   </span>
        </a>
    </div>
    <div style="padding-left:1%;">
        <div class="content-primary">
            <ul data-role="listview" data-inset="true" id="tweets">
            </ul>
        </div>
    </div>
    <br>
    <div data-role="header" data-position="inline">
        <h1></h1>
    </div>
</body>
</html>

javascript部

//ドキュメント読み込み完了時に
$(function(){
 //検索する関数
 getTweets = function(){
 //ID "tuser"の値を取得
 var user = $("#tuser").val();
 if(user != ""){
  //リスト表示部を空白、初期化
  $("#tweets").html('');
  //検索中を示すため、読み込み中メッセージを表示
  $.mobile.showPageLoadingMsg();
  //Twitter APIを使って結果を取得し、dataパラメータに渡す。
  $.getJSON('http://twitter.com/status/user_timeline/'+user+'.json?count=10&callback=?', 
  function(data){
   var template = "";
   var screenname = "";
   var realname = "";
   var tweet = "";
   var avataar = "";
   //抽出結果を一つづつ取り出して、index.htmlのitemにセット
   $.each(data, function(index, item){
    screenname  = item.user.screen_name;
    realname = item.user.name;
    tweet  = item.text;
    created_at = item.created_at;
    avataar  = item.user.profile_image_url;
    created_at  = created_at.split(" ");
    
    // item内のドキュメント(テンプレート)を作成
    $("#tweets").append('<li><a href="#"><img style="margin:1%;" src="'+avataar+'" /><h3>'+screenname+'</h3><p>'+tweet+'</p><p class="light-text">'+created_at[1]+' '+created_at[2]+'</p></a></li>');
    
    // list内を読み込みして再表示
    $("#tweets").listview();
    $("#tweets").listview("refresh");
   });
   //読み込み中のダイアログを消す
   $.mobile.hidePageLoadingMsg();
  });
   }
 }
});

コードの解説が全くない。後で、書き足すことにしよう。とりいそぎ、ここまで。

2012年1月22日日曜日

MacPortsをアップデート

OSXで、オープンソースを利用する場合、パッケージシステムはいくつか選択肢があるが、私は、MacPortsを使っています。
http://guide.macports.org/

# 昔は、時々自分が何を使っている忘れて、複数のパッケージシステムを入れて混乱したりしました。

MacPortsは、バイナリを配布するのではなく、ソースをダウンロードして、コンパイルするタイプのパッケージ管理システムです。そのため、コンパイラが必要です。OSXの場合は、Xcodeが必要になります。そのため、Xcodeをインストールしておくことが必須です。

さて、Lionにしてからパッケージのアップデートしてませんでしたので、やってみました。以下の手順です。
  1. リポジトリ情報を最新にする
  2. インストール済みのport(パッケージ)を最新にする

リポジトリ情報の更新

CodeRider:~ baker$ sudo port selfupdate
Password:
--->  Updating MacPorts base sources using rsync
MacPorts base version 2.0.3 installed,
MacPorts base version 2.0.3 downloaded.
--->  Updating the ports tree
--->  MacPorts base is already the latest version

The ports tree has been updated. To upgrade your installed ports, you should run
  port upgrade outdated

selfupdateコマンドは、公開されているMacPortsのリポジトリで、ローカルのportsを更新するために使われます。結果、ローカルのportsは、最新のヴァージョンになります。このコマンドは、またMacPorts基盤 ( MacPorts base)の最新バージョンをチェックし、必要であれば最新のものに、アップグレードします。(macports.org コマンド解説 3.1.2. selfupdate)

つまり、selfupdateでは、リポジトリの情報のみがアップデートされて、既にインストールされているパッケージが更新されるわけではないと言うことです。ただし、ローカルで利用するportとか、MacPortsのコマンド群(MacPorts Base)は、このタイミングで最新版に更新されます。

パッケージの更新

インストール済みのパッケージを全て最新版にします。upgradeコマンドを使うのですが、

upgradeコマンドは、デフォルトの使用では、更新されたパッケージを削除しません。非アクティブにするだけです。(非アクティブについては、Package Image, Destroot, Activate Phasesの項を参照してください)もし、古いportを削除したいのであれば、uオプションを使用しましょう。
(macports.org コマンド解説 3.1.15. upgrade Note)

なので、uオプションを使用して、古いportは削除し、新しいものに入れ替えました。もちろんアップグレード可能なもののみの話ですが。

CodeRider:~ baker$ sudo port -u upgrade outdated
--->  Computing dependencies for gdbm
--->  Fetching archive for gdbm
--->  Attempting to fetch gdbm-1.10_1.darwin_11.x86_64.tgz from http://packages.macports.org/gdbm
--->  Fetching gdbm
--->  Attempting to fetch gdbm-1.10.tar.gz from ftp://ftp.dti.ad.jp/pub/GNU/gdbm
--->  Attempting to fetch gdbm-1.10.tar.gz from http://distfiles.macports.org/gdbm
--->  Verifying checksum(s) for gdbm
--->  Extracting gdbm
--->  Configuring gdbm
--->  Building gdbm
--->  Staging gdbm into destroot
--->  Installing gdbm @1.10_1
--->  Cleaning gdbm
--->  Computing dependencies for gdbm
--->  Deactivating gdbm @1.9.1_0
--->  Cleaning gdbm
--->  Activating gdbm @1.10_1
--->  Cleaning gdbm
--->  Uninstalling gdbm @1.9.1_0
--->  Cleaning gdbm
(途中、たくさん省略)

古いportがクリーンされてますね。
コンパイルするので、少し時間が掛かりましたが、無事 終了です。

2012年1月15日日曜日

WebSocketのRFCを一寸だけ訳す

HTML5と共に気になる技術があります。WebSocket。TCPソケット通信をHTTPプロトコルの下で実現する技術であり、試みだと想像しています。XMLHttpRequestを利用するAjaxは、ドキュメント全体の更新で高負荷となるHTTPと異なり、初めて観た時に革新的だった。でもプログラムレベルでは、やっぱりなじまない。もう少し、他の部分とは独立的にTCP送信を実装したいもんだと、WebSocketに期待しています。
そのWebSocketですが、RFCが公開されていたので、さわりの部分だけ訳してみました。

原文は、ここです。
http://tools.ietf.org/html/rfc6455

いやあ、酷い和訳です。あくまで自分の勉強のためなので、決して訳を鵜呑みにしないように。そもそも、日本語になっていないような気もするし、概要と導入部の最初の節だけなので、具体的な内容には乏しい。

ウェブソケット プロトコル

概要
ウェブソケット・プロトコルは、管理された環境で委託されたコードが動いているクライアントと、そのコードが通信を受け取っているホストとの双方向通信を可能にします。
セキュリティモデルは、ウェブ・ブラウザに一般に使われている原初的なセキュリティモデルです。プロトコルは、T通信を開始時の通信確定手順と、それに続くTCP層の基本的なメッセージ・フレーム群で構成されています。
この技術の狙いは、サーバとの双方向通信を必要とするブラウザベースのアプリケーションに、XMLHttpRequest、iframe、ロング・ポーリングのような複合的なHTTP接続に頼らない、機構を提供することです。

1. 導入(イントロダクション)
1.1. 背景
歴史的に、クライアントとサーバ間の二方向の通信を必要とするウェブアプリ(チャット、ゲームとか)は、更新確認のため、上位への通知を送信する間、(ドキュメントの表示のためのHTTPではない)別のHTTPコール(RFC6202)を使ったサーバへのポーリングを多用してきた。

この結果は、様々な問題を生むことになる。
* サーバは、それぞれのクライアントと、クライアント数だけのTCP通信を強いられる。ある通信は、クライアントに情報を送り、またある通信は送られてくるメッセージを受け取るために。
* 通信の成立は、非常に負荷が高い。それぞれのクライアントーサーバ間メッセージは、(そのための)HTTP headerをメッセージ毎に持っている。
* クライアントサイドスクリプト(Ajaxのような)は、送信結果を結びつけるために、送信と受信の管理(マッピング)を強いられる。

より単純な解決は、双方向の流れをまとめた一つのTCP接続を使うことであるべきだ。これが、ウェブソケットプロトコルの提供するものだ。WebSocket APIを組み込むことで、ウェブプロトコルは、ウェブページから、遠隔地のサーバへの双方向通信を実現するため、HTTPポーリングに代わる別の手段を提供する。
同様の技術は、様々なウェブ・アプリケーションで使われることが可能だ。ゲーム、リアルな証券情報ニュース、複数人による同時編集アプリ、リアルタイムな情報発信機能を提供するユーザインターフェースなどなど。

ウェブソケット プロトコルは、現存する基盤(プロキシ、フィルタリング、認証)からの利便性を得るために、トランスポート層でHTTPを利用している現存の双方向通信技術を捨て去ることができるように設計されました。

== このあたり、全くお手上げ ==
そのような(現存の)技術は、効率と信頼性のトレードオフのもとで実装されている。なぜなら、HTTPは、本来、双方向通信に利用されることを意図していなかったからである。
ウェブソケットは、その目的を、既存のHTTP基盤のもとで、既存のHTTP双方向通信技術に割り当てている(?)。それは、80ポートや443ポートで動作することで、HTTPプロキシや媒体(それが、現在の環境への特殊な使用を意味することであっても)をサポートすることだ。
「 The WebSocket Protocol attempts to address the
goals of existing bidirectional HTTP technologies in the context of
the existing HTTP infrastructure; as such, it is designed to work
over HTTP ports 80 and 443 as well as to support HTTP proxies and
intermediaries, even if this implies some complexity specific to the
current environment.」
== ここまで ==

しかし、設計では、ウェブソケットをHTTPのみに制限していないし、将来の実装では、全体のプロトコルを再構成する事無く、専用のポートのもとで、より単純な通信確定を使うことが出来る。
最後の点は、重要である。なぜなら、双方向のメッセージ通信の際の交信パターンは標準的なHTTP交信にぴったりとはあっていないし、いくつかのコンポーネントの一寸変わった取込を実現できるからだ。