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は、効いてないっぽい。(儀式?)