2015年9月27日
squidログから検索クエリーのテキスト解析
1.squidログのログ・クレンジング
ログのクレンジングにはいろいろな方法がある。試行錯誤で次のようなログのクレンジングを行った。
1.1 squidログの読み込み
ログの項目数が正しいこと、転送バイト数がゼロでないこと、リクエストメソッドが所定のものに合致していること(基本はGETとPOST)、ポート443の通信でないこと、ステータスコードが200であること、コンテンツタイプが”text/xxxx”または”-”であることを条件に読み込みを行った。
1.2 QueryStringのデコード
GET文字列の中で、エンコードタイプが明示されているものもあれば、エンコードタイプがないものもある。検索クエリーでエンコードがきっちりできなければ、何を検索しようしているか、全く分からなくなる。いろいろ挑戦するが中々うまくエンコードできない中、”エンコード時の文字コードが不明なURLをJavaでデコード”(http://blog.grush.jp/?p=20)のサイトに従い、QueryStringのエンコード問題を解決した。
1.3 どこを対象とするか
今回はクエリーの収集・解析であるが、対象とするサイトやそのサイトの検索パラメータが分からない。少し古いが、“検索エンジンのURL“(http://www.geocities.jp/hima_hibi/surl.html)のサイトでは、いろいろな検索サイトが紹介されていて、これをベースに、更に収集ログの目視により調査を行った。これらの調査を元に、”http://・・・/search?・・・”、”http://・・・/Suggestions?・・・”などの持つURL検索サイトをクエリー解析の対象とした。これらのurlの中で、”q=”、”p=”、”qu=”、”qry=”、”query=”、”keyword=”などに続く文字列がQueryStringである。尚、”/search?”で始まる次のようなサイトは対象外とした。
http://www.navitime.co.jp/transfer/search?orvStationName=東京&dnvStationName=名古屋・・・
1.4 やっかいな話
日本語変換中のインクリメンタルサーチの検索では次のようなクエリーが流れる。これらは日本語変換中のもので、時系列的には同一のものである。変換中のそれぞれを検索ワードとするのはおかしく、検索クエリーとしては、最後のもの以外は検索クエリーとすることはできない。
インクリメンタルサーチ型検索の例
http://suggestqueries.google.com/complete/search?hl=ja&qu=wifi
http://suggestqueries.google.com/complete/search?hl=ja&qu=wifis
http://suggestqueries.google.com/complete/search?hl=ja&qu=wifiせ
http://suggestqueries.google.com/complete/search?hl=ja&qu=wifiせt
http://suggestqueries.google.com/complete/search?hl=ja&qu=wifiせつ
http://suggestqueries.google.com/complete/search?hl=ja&qu=wifiせつz
http://suggestqueries.google.com/complete/search?hl=ja&qu=wifiせつぞ
http://suggestqueries.google.com/complete/search?hl=ja&qu=wifiせつぞk
http://suggestqueries.google.com/complete/search?hl=ja&qu=wifiせつぞく
http://suggestqueries.google.com/complete/search?hl=ja&qu=wifi接続
1.5 QueryStringに対する前処理
日本語にはいろいろな”揺らぎ”があるため、多くの日本語解析ではテキストの前処理が成されている。今回QueryStringの解析に当たり、次のような前処理を施した。
(1) 日本語のノーマライズ
日本語解析の最初の前処理として、次のような日本語のノーマライズを行う。
・スペースの半角化、連続スペースを1つにする。
・複数ある長音記号を”ー”に統一、並びに連続長音を1つにする。
・同様にダブルクォート、カンマも同様に対応する。
・英数字に挟まれた長音はハイフンに変換する。
・全角英数字、記号の半角変換及び英文字の小文字化をする。
・半角カタカナを全角カタカナに変換する。
(2) 漢字、カタカナのひらがな変換
日本語を、”Kakasi”(http://kakasi.namazu.org/index.html.ja)を用い、ひらがなに変換する。
(3) Hebon変換
ひらがな文字列をヘボン式英字列に変換する。
上記のインクリメンタルサーチ型クエリーは次のような入力ストリームとなる。
wifi
wifi s
wifi se
wifi set
wifi setsu
wifi setsuz
wifi setsuzo
wifi setsuzok
wifi setsuzoku
wifi setsuzoku
(4) 文字列比較
同一IPで、事前のクエリーq0と、次に来たクエリーq1を比較し、次の判定式に従い、包含関係から”大”なものを最終的なクエリーとして採用する。比較関数は、String.startsWith、String.endsWithを用いた。
q0
⊆ q1 → q0 = null、q0 = q1
q0
⊃ q1 → q1 = null
この処理を順次繰り返すと、変換過程の文字列はなくなり、最終的に“wifi 接続”がインクリメンタルサーチ型クエリーとして残る。
但し、Hebon変換では、じしん(jishin)、自信(jishin)、自身(jishin)、地震(jishin)など同じ読み文字列は、同じアルファベット列となる。
(5) 比較関数の改良
ログを調べる中から、次のようなログを発見した。”ハンディクリーナー おすすめ”で検索した後、”おすすめ”の前から新しいインクリメンタルサーチを始めている例である。前述の比較関数では、包含関係はなく、異なる文字列となる。
インクリメンタルサーチを行っている文字列では、1文字しか追加されないため、Levenshtein距離(編集距離)による判定を検討した。しかし汎用的に考えると、異なるところが1文字のみならず、複数個ある場合も考慮しなければならない。そこでunix流のdiffの流れを汲んでいるdiff.jar(http://aarkiton.blogspot.jp/2009/05/diff.html)を利用した。
diff.jarを利用すると、文字列a、文字列bから成る文字列Aと、文字列a、文字列c、文字列bから成る文字列Bとの間で、文字列A ⊂ 文字列Bの関係を見出すことができる。
文字列A 文字列a 文字列b
文字列B 文字列a 文字列c 文字列b
次の例では、Hebon式変換を経て、最終的クエリーとして”ハンディクリーナー
コードレス
おすすめ”を得ることができた。
1401409079706
192.168.161.87 bing.com ハンディクリーナー おすすめ
1401409080824
192.168.161.87 bing.com ハンディクリーナー kおすすめ
1401409080824
192.168.161.87 bing.com ハンディクリーナー koおすすめ
1401409081217
192.168.161.87 bing.com ハンディクリーナー ko-おすすめ
1401409081217
192.168.161.87 bing.com ハンディクリーナー ko-dおすすめ
1401409081334
192.168.161.87 bing.com ハンディクリーナー ko-doおすすめ
1401409081524
192.168.161.87 bing.com ハンディクリーナー ko-dorおすすめ
1401409081613
192.168.161.87 bing.com ハンディクリーナー ko-doreおすすめ
1401409081817
192.168.161.87 bing.com ハンディクリーナー ko-doresおすすめ
1401409082072
192.168.161.87 bing.com ハンディクリーナー ko-doresuおすすめ
1401409103219
192.168.161.87 bing.com ハンディクリーナー コードレスおすすめ
1401409103219
192.168.161.87 bing.com ハンディクリーナー コードレス おすすめ
(6) 形態素解析
・形態素解析はJava環境で動作する”Igo”(http://igo.sourceforge.jp/)を利用。
・1.5(1)のノーマライズ処理及び英字の小文字化の後、String.split(' ')により得られた文字列配列に対して、検索対象となり難い文字列の除外を行う。
数字列及び記号列の除外
数字のみ、記号のみ、郵便番号、電話番号、IPアドレスなどの文字列を除外
1文字の英字、ひらがな、カタカナの除外
数詞+助数詞で表される数値の除外
500円、千五百円、10代、40台、37度などの数値を除外
・英数記号列はキーワードとして採用する。
・日本語混じりのテキストは形態素解析し、名詞を中心にそれぞれをキーワードとして採用する。この処理で、助詞などのストップワードが除外できる。尚、ストップワード除外辞書を利用してないため、”これ”、”それ”などのキーワードとして採用される。
・キーワードの連結 ”接続エラー”、”データ削除”などは分離せず、連結操作を行った。 ”子供の病気”などで、連体化助詞”の”を挟む名詞も連結操作を行った。 ・ダブルクォートや括弧で囲まれている場合、これらの削除を行った。 形態素解析の例 1401423101943 192.168.161.71 yahoo.co.jp 【本日の一枚】この写真を出すか迷ったのですが、あまりに衝撃写真だったので(*´ω`*)ねこじゃらしをキャッチできなかった表情と姿がポイントです(笑) ↓ 形態素解析を行うと。 本日 写真 衝撃写真 ねこ キャッチ 表情 姿 ポイント 笑 2.ログの保存と集計 ログの保存にはMongoDBを採用した。MongoDBはドキュメント指向のデータベースで、JSON形式のドキュメントが簡単に保存でき、JSONオブジェクトとして参照や変更も簡単にできる。JSONが分かるプログラマーにとって大変扱い易いDBである。 今回。MongoDBの扱いに慣れていないため、単純なシングルノード構成とした。 2.1 squidのログ保存 squidのログデータから、"time", "year", "month",
"day", "hour", "duration", "clientAddress", "resultCode",
"statusCode", "byte", "requestMethod", "url",
"domain", "type"を読み込み、これらをJSONデータとしてMongoDBに登録した。squidログに加え、"year", "month", "day",
"hour"は"time"から、"domain"は"url"から求めたもので、以後の検索・集計を簡便に行うためのデータである。 2.2 登録・集計スピード squidログ(2014/4/10から5/9)1,700万件のMongoDB登録時間は、70分強(ノートPC)であった。 また、1,700万件の収集ログに対してMapReduce(本来aggregateを使うべきであろう)による12時台で、各ドメインへのアクセス数集計は、i5-3337U CPU, 1.8GHzのPC環境で、14分強の時間で終わった(総なめでこの速さ)。 3.結果 多くの検索サイトが採用しているインクリメンタルサーチではタイピングの都度、クエリーが流されていて、クレンジングしなければ正しいログ収集ができない。今回、テキスト解析を行い、有効なクレンジングができた。 4.リソース 利用ライブラリー mongo-2.10.1.jar commons-lang.jar kakasi.jar igo-0.4.5.jar kuromoji-0.7.7.jar diff.jar プログラム(querystring.zip) FileReadSquidQuery.java FileSearch.java HebonConvert.java LogString.java QueryString.java StringCompare.java StringUtil.java TokenizerModeSample.java TwoByteToOneByte.java (記 大坂 哲司)