GoogleAnalyticsの流入キーワードをmecabで形態素解析して頻出単語をランキング
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で設定を行う
-
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をワンライナーなんかで使う時に
よく使う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
個人的には一つの言語でワンライナーで書いた方が微妙な仕様の違いに惑わされなくて済むし、
使い捨てコードは後者を選択する事が多いかも。
apacheのmod_proxy_balancerを使ってロードバランサを試してみる
複数の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でschema.rbをsqlファイルとして出力する
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ベースのテーブル定義スクリプトが作成されていれば成功。
リモートのmysqlのselect結果をローカルのcsvまたはtsvに出力
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