ruby + mongoDBでapacheログの解析システムを作る②


前回の続き。

MongoDBだと、開発楽ですねー。DBの型を最初に決めずに、開発しながらテキトーに変更していくみたいな開発が可能です。素人プログラマーにやさしい仕様で素敵です。

さて、前回取り敢えず、MongoDBにログデータをぶち込むだけのスクリプトを作りました。
これだけで、単純なPVとかリファラーとかは取得できるんですが、そんなものは必要ねーと。
必要なのはセッションベースでユーザーの行動を見ることです。

ということで、前回作成したコレクションからセッションベースのコレクションを作成するrubyスクリプトを作りました。
処理の内容としては、

  • ユニークユーザーをキーとして、閲覧ページ情報などを抜き出す
  • 閲覧時間をキーにして並べ替える
  • ユニークユーザーをセッションレベルに分解する

という感じです。

今回は、ユニークユーザーのキーとして、「IPアドレス+UserAgent 」を利用しました。
Apacheログにcookieを吐き出している場合は、cookieの方がより正確です。cookieを使うようにしましょう。
慣例に習い、30分以上閲覧間隔が空いた場合はセッションを切るという設定にしています。
セッションカットのメソッドを実行しなければ、ユニークユーザーレベルのコレクションとして、格納されるはずです。

このコレクションさえあれば、各セッションでページを見た順番がわかるようになります。
ここから、ページ閲覧情報だけ抜き出せば、SiteCatalystのフルパスレポートと同じようなものを出すことができます

さらに導線のデータを抽出しやすくするように、前後ページでのコレクションも作成しました。
データをカウントすると、
・前ページのデータ、次ページのデータ : 合計数
というデータが出てきます。
セッションの始めの場合は、前ページのデータがリファラーに。
セッションの終わりの場合は、次ページのデータが”end”。
として、抽出されます。

このデータを使えば、例えば

  • トップページの次にどのページを見たのか?
  • ランディングページと参照元
  • 閲覧終了ページ

などのデータを簡単に出すことができるようになります。
でも、わざわざコレクション作らなくてもメモリ上で集計できたかもなーってちょっと思ってます。。。

データ処理の概念図としては、↓こんな感じです。

次はここから、データを取り出すスクリプトを開発する予定です。

collect.rb

# -*- coding: utf-8 -*-
Encoding.default_external="UTF-8"

require 'mongo'
require 'pp'

#configDB
    $con   = Mongo::Connection.new
    $db   = $con.db('log')
    $logs = $db.collection('Log')
    $s_cols = $db.collection("S_col")
    $s_paths = $db.collection("S_path")




class SetSessionCollection

  def initialize
    #set the session key
    @ss = "ip_ua"
    @sessions = $logs.distinct(@ss);
    self.make_session(@sessions)
  end

  # session cut method
  def ss_cut_pos(vals)
    @ss_time = 30 * 60
    @cursor = Array.new
    @i = 0
    vals["time"].each do |x|
      time_dif = 0
      if(@i!=0)
        time_dif = x - @last_time
      end
      if(time_dif > @ss_time)
        @cursor << @i
      end
      @i += 1
      @last_time = x
    end
    @cursor << vals["time"].length
    return @cursor
  end
  def ss_cut_do(vals, arr)
    res_arr = Array.new
    if(arr.length == 1)
      res_arr << vals
    else
      @i = 0
      arr.each do |num|
        tmp_vals = Hash.new
        vals.each_pair do |x,y|
          tmp_vals[x] = y[@i..(num-1)]
        end
        res_arr << tmp_vals
        @i = num
      end
    end

    return res_arr
  end

  #make session collection
  def make_session(sessions)
    @i = 0
    @datas = Array.new
    @dom = /http://www.sample.net/
    sessions.each do |x|
      all_arr = Array.new
      vals = Hash.new
      page_arr = Array.new
      ref_arr = Array.new
      time_arr = Array.new


      $logs.find({@ss=>x},:sort=>"time").each do |y|
        page_arr << y["page"]
        tmp_ref = y["referer"]
        if(tmp_ref)
          if(tmp_ref.match(@dom))
            ref_arr << ""
          else
            ref_arr << tmp_ref
          end
        end
        time_arr << y["time"]
      end
      vals["id"] = @i
      @i += 1
      vals = {
        @ss => Array.new(page_arr.length, x),
        "page" => page_arr,
        "refer" => ref_arr,
        "time" => time_arr
      }

      # session cutting by the time over 30 min.
      ss_arr = self.ss_cut_pos(vals)
      ss_vals = self.ss_cut_do(vals, ss_arr)
      ss_vals.each do |x|
        #pp x["time"]
        $s_cols.insert(x)
      end
        #puts "-----------------"
    end
  end

  def show(i)
    pp $s_cols.find().limit(i).to_a
  end

end



class SetPathCollection
  def initialize
    #set the session key
    @ss = "ip_ua"
    self.make_path($s_cols)
  end


  def make_path(s_cols)
    s_cols.find().each do |x|
    i = 0
      x["page"].each do |y|
       val = Hash.new
        if(i == 0)
            val ={
              "pre" => x["refer"][i].to_s,
              "post" => y.to_s,
              "length" => x["page"].length,
              "num" => i,
              @ss => x[@ss][0]
              }
            $s_paths.insert(val)
            i += 1
            val = Hash.new

            val ={
              "pre" => y.to_s ,
              "post" => x["page"][i].to_s,
              "length" => x["page"].length,
              "num" => i,
              @ss => x[@ss][0]
              }
            $s_paths.insert(val)

        elsif(i == x["page"].length)
             val ={
              "pre" => y.to_s ,
              "post" => "end",
              "length" => x["page"].length,
              "num" => i,
              @ss => x[@ss][0]
              }
            $s_paths.insert(val)

        else
            val ={
              "pre" => y.to_s ,
              "post" => x["page"][i].to_s,
              "length" => x["page"].length,
              "num" => i,
              @ss => x[@ss][0]
              }
            $s_paths.insert(val)

        end
        i += 1
      end
    end
  end

  def make_Map_Reduce
    #$s_paths.find().each do |x| p x end
    map = "function () {" +
            "emit({pre:this.pre, post:this.post}, {count:1});" +
          "}"

    reduce = "function (key, val) {" +
                "var sum = 0;" +
                "for (var i = 0; i < val.length; i++) {" +
                    "sum += 1;" +
                "}" +
                "return {count:sum};" +
             "}"
    @res = $s_paths.map_reduce(map, reduce)
  end
  def out_FN
    #Count the former page and the after page
      self.make_Map_Reduce
      puts "formertaftertcountn"
    @res.find().each do |x|
      print x["_id"]["pre"].to_s
      print "t"
      print x["_id"]["post"].to_s
      print "t"
      print x["value"]["count"].to_s
      print "n"
    end
  end

end







ss_data = SetSessionCollection.new
#ss_data.show(30)
path_data = SetPathCollection.new
#path_data.out_FN

Leave a Reply