Googleのアナリティクスで検索エンジン等からのサイト流入キーワードが見れるけど
キーワードが一塊になっているので例えば

キーワード 訪問数
macbook 10
macbook air 5
macbook air ノートパソコン 3
mac ノート 5
mac pro 1
windows8 pc 5
windows8 ノートpc 10
windows ノートパソコン 5

みたいな感じになってたとして「mac」とか「ノート」みたいな単語の出現数も
取りたいと思ってmecabとgoogle analytics apiを使って試してみました。
処理はrubyで書いてます。
もしかしたらGAで普通に出来るのかもしれない、
けどmecab使ってなんかやりたかったのでよしとします。

手順

  • MacにMecabをインストールする

    ここから以下のファイルをダウンロードする

    • mecab-0.996.tar.gz (mecab本体)
    • mecab-ipadic-2.7.0-20070801.tar.gz(IPA辞書:200Mぐらいある)
    • mecab-ruby-0.996.tar.gz

    DLしたファイルを任意のディレクトリに移動して以下のようにしてインストール

    # mecab本体のインストール
    tar xvzf mecab-0.996.tar.gz
    cd mecab-0.996
    ./configure
    make
    make install
    
    # 辞書のインストール
    tar xvzf mecab-ipadic-2.7.0-20070801.tar
    cd mecab-ipadic-2.7.0-20070801
    ./configure --with-charset=utf8
    make
    make install
    
  • mecabの実行

    mecab <textfile>
    

    もしくは以下のようにコマンドのみ入力し、その後にテキストを入力してもOK

    mecab
    

    分かち書きで出力(品詞の情報とか出さないで形態素で分割した結果のみ出力)

    mecab -O wakati <textfile>
    

    とりあえず動作確認できたので先ほどのキーワードリストをテキストファイルにして
    mecabに読み込ませてみました。

    $ mecab -O wakati sample.txt 
    macbook 
    macbook air 
    macbook air ノート パソコン 
    mac ノート 
    mac pro 
    windows 8 pc 
    windows 8 ノート pc 
    windows ノート パソコン 
    morikubonaoki-no-MacBook-Air:mecab_work morikubonaoki$ mecab sample.txt 
    macbook 名詞,固有名詞,組織,*,*,*,*
    EOS
    macbook 名詞,固有名詞,組織,*,*,*,*
    air 名詞,一般,*,*,*,*,*
    EOS
    macbook 名詞,固有名詞,組織,*,*,*,*
    air 名詞,一般,*,*,*,*,*
    ノート 名詞,一般,*,*,*,*,ノート,ノート,ノート
    パソコン  名詞,一般,*,*,*,*,パソコン,パソコン,パソコン
    EOS
    mac 名詞,固有名詞,組織,*,*,*,*
    ノート 名詞,一般,*,*,*,*,ノート,ノート,ノート
    EOS
    mac 名詞,固有名詞,組織,*,*,*,*
    pro 名詞,一般,*,*,*,*,*
    EOS
    windows 名詞,一般,*,*,*,*,*
    8 名詞,数,*,*,*,*,*
    pc  名詞,固有名詞,組織,*,*,*,*
    EOS
    windows 名詞,一般,*,*,*,*,*
    8 名詞,数,*,*,*,*,*
    ノート 名詞,一般,*,*,*,*,ノート,ノート,ノート
    pc  名詞,固有名詞,組織,*,*,*,*
    EOS
    windows 名詞,固有名詞,組織,*,*,*,*
    ノート 名詞,一般,*,*,*,*,ノート,ノート,ノート
    パソコン  名詞,一般,*,*,*,*,パソコン,パソコン,パソコン
    EOS
    

    分かち書きで出力するとこうなる

    $ mecab -O wakati sample.txt 
    macbook 
    macbook air 
    macbook air ノート パソコン 
    mac ノート 
    mac pro 
    windows 8 pc 
    windows 8 ノート pc 
    windows ノート パソコン 
    

    それぞれの出現数をソートして出力してみる

    mecab sample.txt  | cut -f 1 | sort | uniq -c | sort -r -n
    
    8 EOS
    4 ノート
    3 windows
    3 macbook
    2 パソコン
    2 pc
    2 mac
    2 air
    2 8
    1 pro
    

    とりあえず、ソレらしい感じで結果が取れそう。

  • Google Analytics API使用準備

    OAuthの設定等必要なのでGoogle APIs Consoleで設定を行う

    • ログインして、プロジェクトが未作成の場合はプロジェクトを作成
    • APIs&authタブの中のAPIsを選択、analytics APIをONにする

    • APIsの下のCredentialsで[CREATE NEW CLIENT ID]を選択、必要な項目を入れていく

      • Application type : 今回はrubyから叩くだけなので[installed application]を選択
      • Installed application typeは[Other]のままでOK
      • [Download JSON]からIDとsecretのjsonをダウンロードしておく
  • Google Analytics APIをrubyから使う

    gemをインストールする

    gem install google-api-client
    
  • mecab-rubyを導入する

    上の方でDLした[mecab-ruby-0.996.tar.gz]を解凍してインストールする

    tar xvzf mecab-ruby-0.996.tar.gz
    cd mecab-ruby-0.996
    ruby extconf.rb
    make
    make install
    

    インストールが出来たので同じディレクトリ内にあるtest.rbを実行してみる

    ruby test.rb
    

    エラーが出た

    dyld: lazy symbol binding failed: Symbol ....
    xxxxx
    Expected in: flat namespace
    

    extconf.rbを以下のように修正

    $CFLAGS += ' ' + `#{mecab_config} --cflags`.chomp
    $LDFLAGS = '-L/usr/local/lib'                       # この行を追加
    

    再インストール

    make clean
    ruby extconf.rb
    make
    make install
    

    もう一度test.rbしたら成功した

    公式のサンプルを参考に、こんな感じで流入キーワードを取得、
    結果をmecabで形態素解析して出力

    # coding: utf-8
    
    require 'google/api_client'
    require 'google/api_client/client_secrets'
    require 'google/api_client/auth/installed_app'
    require 'json'
    require 'MeCab'
    
    # 適当に名前とバージョン入れる(未設定だとエラーになる)
    client = Google::APIClient.new(
      :application_name    => 'test',
      :application_version => '0.01'
    )
    
    # ダウンロードした認証jsonファイルを[client_secrets.json]にリネームしておく(この関数で読み込まれる)
    client_secrets = Google::APIClient::ClientSecrets.load
    
    # ref https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtAuthorization?hl=ja
    flow = Google::APIClient::InstalledAppFlow.new(
      :client_id     => client_secrets.client_id,
      :client_secret => client_secrets.client_secret,
      :scope         => 'https://www.googleapis.com/auth/analytics.readonly'
    )
    
    client.authorization = flow.authorize
    analytics = client.discovered_api('analytics', 'v3')
    
    # 流入キーワードと各キーワードの流入数を取得する
    # ref https://developers.google.com/apis-explorer/#s/analytics/v3/analytics.data.ga.get
    result = client.execute(
      :api_method => analytics.data.ga.get,
      :parameters => {
        'ids'        => 'ga:xxxx',    
        'start-date' => '2013-01-01',    # 分析したい開始年月日を指定
        'end-date'   => '2012-01-31',      # 分析したい開始年月日を指定
        'metrics'    => 'ga:visits',
        'dimensions' => 'ga:keyword',
        'sort'       => '-ga:visits'
      }
    )
    
    # analytics apiの結果をjsonパース
    body = JSON.parse(result.response.body)
    
    opt  = "-O wakati"
    data = body['rows'].map{ |r| r[0] }.join(" ")
    
    # mecabで形態素解析
    c = MeCab::Tagger.new(opt)
    keywords = c.parse(data).split(/\s/).group_by{ |e| e }
    
    # 形態素解析の結果を{文字列:カウント}のハッシュにしてソートして出力
    word_counts = keywords.inject({}){|a,(k, v)| a[k] = v.count; a}
    word_counts.sort{|(k1, v1), (k2, v2)| v2 <=> v1}.each do |r|
      p "#{r[0]} : #{r[1]}"
    end
    

    他の値を取りたい場合はディメンションとメトリクスを変える必要があるので
    その場合はこちらを参考に

まとめ

正直、今回の結果だけだとそんなに目新しい発見は無かったけど、
色々使いどころはありそうなのでもうちょっと別の素材でも試してみたい。


rubyをワンライナーなんかで使う時に
よく使うrubyコマンドのオプションとかグローバル変数等を中心にまとめました

グローバル変数

コンフィギュレーション系

  • $LOAD_PATH, $: : require時の検索対象パスの配列

IOストリーム、文字列処理系

  • $_ : 最後にgetsまたはreadlineで読み込んだ文字列
  • $< : コマンド行で指定されたファイルまたは標準入力へのIOオブジェクト、Kernel.gets等の読み出しはこれが使われる
  • $stdin : 標準入力ストリーム
  • $stdout, $> : 標準出力ストリーム、Kernel.puts、print、printf等の表示メソッドの出力先
  • $stderr : 標準エラー出力ストリーム
  • $FILENAME : 現在ARGFから読み出されているファイル名
  • $. : 現在の入力ファイルの最後に読み込んだ行番号
  • $/ : 入力レコードセパレータで、デフォルトは改行
  • $\ : 出力レコードセパレータで、デフォルトはnul、-lオプション時は$/と同じになる
  • $, : Array.joinのセパレータで、デフォルトはnil
  • $; : splitが使うセパレータで、デフォルトはnil、-Fオプション時は値を指定できる
  • $F : nまたはpオプションとaオプションを指定した時にsplitが返す値

正規表現系

  • $& : 現在のスコープで最後に成功した正規表現のパターンマッチ文字列
  • $~ : 現在のスコープで最後に成功した正規表現マッチオブジェクト
  • $` : 現在のスコープで最後に成功した正規表現マッチ文字列よりも前の文字列
  • $' : 現在のスコープで最後に成功した正規表現マッチ文字列よりも後の文字列
  • $+ : 現在のスコープで最後に成功した正規表現マッチのグループに対応する文字列

例外処理系

  • $? : 最後に終了したプロセスの終了ステータス
  • $! : 最後に発生した例外のオブジェクト
  • $@ : 最後に発生した例外のスタックとレース、$!.backtraceと同義

コマンドラインオプション

-e : [-e 'command']の形式でcommand文字列を実行する

    $ ruby -e 'p "hello!"'
    # -eオプション使用時はif等の条件式に単独の正規表現リテラルを
    # 記述した場合、暗黙的に$_との比較となる
    # 下の例はlsの結果のうち先頭がMの行を抽出
    $ ls | ruby -ne 'print if /^M/'    # printは引数無しの時は$_を出力する

-r : プログラムの実行前にrequire 'hogehoge'を行う

    $ ruby -r 'date' -e 'p DateTime.now'

-n :
awkっぽい感じ。
標準入力を1行ずつ取得し[$_]にセット。
その後、eオプションで設定した処理を実行する

    # ドットから始まるファイル、ディレクトリ名を抽出
    $ ls -a | ruby -ne 'print if /^\.\w+$/'

-p : 処理の最後に$_の値を出力する

$ echo "test abc" | ruby -pe '$_.upcase!'

-a : nまたはpと組み合わせて使うと各行がsplitされて$F変数に格納されてくる

# lsの結果のうち<bash>が含まれるレコードを抽出し、splitされた配列の
# 1番目と最後の列(権限とファイル名)を表示する
$ ls -al | ruby -ane 'p "#{$F[0]} : #{$F[-1]}" if /bash/'

使用例

例えばapacheのログをちょっと確認してみるケース。
ログの中から400系か500系のステータスのレコードを抽出し、
日付部分の不要な文字列を削除してhttpステータス URL アクセス日時を表示する
bashのコマンドでやるとこんな感じ

cat access_log | awk '{print $9,$7,$4}' | grep -P '^[45]\d{2}' | sed -e "s/\[//g"

これをrubyで置き換えるとこんな感じ

ruby -nae 'p "#{$F[8]} #{$F[6]} #{$F[3].gsub("[","")}" if $F[8] =~ /^[45]\d{2}/' access_log

個人的には一つの言語でワンライナーで書いた方が微妙な仕様の違いに惑わされなくて済むし、
使い捨てコードは後者を選択する事が多いかも。


複数のwebサーバの簡単な負荷分散の仕組みを作るのに、
apacheのmod_proxy_balancerを使えば簡単なロードバランシングが行える。
今回はVirtualBoxを3台作って導入手順をまとめてみた。

手順

virtual boxで仮想マシンを3つ用意する

  • 192.168.56.101(LB)
  • 192.168.56.102(WEB1)
  • 192.168.56.103(WEB2)

仮想マシンのネットワーク設定はこちら等を参考に

以下の作業まではすべての仮想マシンで同じ作業なので
一つの仮想マシンで作業をして、その仮想マシンをコピーするか
tmux等でまとめて作業すると楽。

  • それぞれにapacheをインストールする
  • vhostsを設定する
  • iptabesの設定を変更して80番ポートをあける
  • iptablesを再起動する
  • サンプルページを設置
  • apacheを起動する

以下、コマンド例

# apacheインストール
$ yum install -y httpd

# vhosts設定ファイルを作成
$ vi /etc/httpd/conf.d/vhosts.conf

NameVirtualHost *:80
<VirtualHost *:80>
  ServerName sample
  DocumentRoot /var/www/html/sample
</VirtualHost>

# iptablesの設定を追加tuika(80番ポートを解放)
$ vi /etc/sysconfig/iptables

-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT

# iptablesを再起動
$ /etc/init.d/iptables restart

# サンプルページを設置(h1タグの中身をそれぞれ[LB|WEB1|WEB2]としておく)
$ mkdir sample
$ cd sample/
$ vi test.html

<html>
<head><title>test</title></head>
<body>
<h1>LB</h1>
</body>
</html>

# apacheを起動
$ /etc/init.d/httpd start

これでそれぞれの仮想マシンにホストのブラウザからアクセスして
以下のようにページが表示されればここまでの作業はOK

http://192.168.56.101/test.html → LB
http://192.168.56.102/test.html → WEB1
http://192.168.56.103/test.html → WEB2

LB用のVMにmod_proxy_balancerが入っているか確認

httpd -M | grep 'proxy'

mod_proxy_balancerの設定を追加する

$vi /etc/httpd/conf.d/balancer.conf
ProxyPass / balancer://mycluster/
ProxyPassReverse / balancer://mycluster/
<Proxy balancer://mycluster/>
  BalancerMember http://192.168.56.102 loadfactor=10
  BalancerMember http://192.168.56.103 loadfactor=10
</Proxy>

LBのapacheを再起動

/etc/init.d/httpd restart

http://192.168.56.101/test.htmlにアクセスしてみる
表示されるページの内容が[WEB1|WEB2]と交互に表示されれば成功

mod_proxy_balancerのconfの解説

  • ProxyPass

    バランシング対象とするパス(以下のパス全てが対象範囲)、
    対応するProxy balancerディレクティブと同じ名前を記述する

  • ProxyPassReverse

    リダイレクト時のURLを置換する為の設定
    ProxyPassと同じ設定にしておけばOK
    (動作未検証なので要確認)

  • Proxy

    スキームを balancer:// とすることで、バランサワーカーを生成
    BalancerMemberでメンバーを追加する

  • loadfactor

    各BalancerMemberの重み付けを設定する(1から100までの値)

ヘルスチェックについて

細かい設定はそんなに出来ないようだけどretry値がデフォルトで60秒で設定されており、
バックエンドサーバのapacheプロセスが死んでいる等している場合は60秒間は振り分けしない
(60秒後にretryして、復活していれば振り分けを再開する)設定になっている

プロセスレベルではなくhttpステータスコードレベルでハンドルしたい場合は
こちらが参考になります


railsでid以外のint型ではないカラムをprimary keyに鳴っているテーブルを扱う時、
schema.rbのdumpが正しく作成されない。
他にも、RDB固有のスキーマ定義を行っている場合は同様の現象が発生する。

以下のように、application.rbのconfig.active_record.schema_formatをsqlにすると
schema.rbでは無くstructure.sqlというsqlファイルを出力するようになる。
sqlファイルなのでschema.rbの時のような問題は起きなくなる。

# config/application.rb

config.active_record.schema_format = :sql

structure.sqlを作成してみる。

$ rake db:structure:dump RAILS_ENV=development

無事sqlベースのテーブル定義スクリプトが作成されていれば成功。


outfileオプションはファイル出力先がサーバになってしまうので
リモートDBのデータcsv等を取得する時にちょっと面倒なので、
ローカルに直接csvなりtsvを出力する。

TSVファイルとして出力する場合
sql結果をそのままファイルに出力するとtsv形式となる為こんな感じでOK

mysql -u [username] -p -h [host] [dbname] -e "select foo,bar from hoge where id < 10" > test.tsv

csvにしたい場合は間に加工処理を入れる

mysql -u [username] -p -h [host] [dbname] -e "select foo,bar from hoge where id < 10" | sed -e "s/"$'\t'"/,/" > test.csv