lolipopaccesslog.rb
#**********************************************************#
#
#  ロリポップのアクセスログ解析クラス
#
#    【2006/1/31 更新】
#
#**********************************************************#

class LolipopAccessLog

  #  各ログ項目のソート用配列格納後のインデックス
  IDX_IP      = 0
  IDX_DATE    = 1
  IDX_CMD     = 2
  IDX_RETCODE = 3
  IDX_SIZE    = 4
  IDX_REFERER = 5
  IDX_AGENT   = 6
  
  CNT_INVALID_FLD  = 2  #  IP fieldの後ろにある無効なfieldの数
  CNT_EXTRA_FLD    = 1  #  Shellwords.shellwords()でDATE fieldが
                        #  2つに分割されるためにずれるインデックス数

  #  検索エンジン種
  SE_GGL  = 0
  SE_YAH  = 1
  SE_MSN  = 2

  #  検索エンジンリファラ識別用
  SE_URL_YAH_1  = /http:\/\/search\.yahoo\.co\.jp\/search\?/
  SE_URL_YAH_2  = /http:\/\/search\.yahoo\.co\.jp\/bin\/search\?/
  SE_URL_YAH_3  = /http:\/\/cache\.yahoofs\.jp\/search\/cache\?/
  SE_URL_MSN_1  = /http:\/\/search\.msn\.co\.jp\/results\.aspx\?/
  SE_URL_MSN_2  = /http:\/\/search\.msn\.co\.jp\/spresults\.aspx\?/
  SE_URL_MSN_3  = /http:\/\/search\.msn\.co\.jp\/previewx\.aspx\?/
  SE_URL_GGL_1  = /http:\/\/www\.google\.co\.jp\/search\?/
  SE_URL_GGL_2  = /http:\/\/www\.google\.com\/search\?/
  GGL_IP_1      = 'http://216.239.'
  GGL_IP_2      = 'http://66.102.'
  GGL_IP_3      = 'http://64.233.'
  GGL_IP_4      = 'http://66.249.'
  GGL_IP_5      = 'http://72.14.'
  GGL_SFX        = '/search?'
  
  #  検索語句入力文字コード種
  IE_SJIS_1  = 'sjis'
  IE_SJIS_2  = 'shift-jis'
  IE_EUC    = 'euc'
  IE_UTF8    = 'utf-8'


  #  ログ読み込み
  def initialize(logdata)
    @records = Array.new
    logdata.each do |line|
      items = Shellwords.shellwords(line)  #  1件分を分割し
      record = Array.new
      record << items[IDX_IP]              #  項目ごとに配列に格納
      record << items[ IDX_DATE     + CNT_INVALID_FLD ] +
          ' ' + items[ IDX_DATE      + CNT_INVALID_FLD + CNT_EXTRA_FLD ]
      record << items[ IDX_CMD      + CNT_INVALID_FLD + CNT_EXTRA_FLD ]
      record << items[ IDX_RETCODE  + CNT_INVALID_FLD + CNT_EXTRA_FLD ]
      record << items[ IDX_SIZE      + CNT_INVALID_FLD + CNT_EXTRA_FLD ]
      record << items[ IDX_REFERER  + CNT_INVALID_FLD + CNT_EXTRA_FLD ]
      record << items[ IDX_AGENT    + CNT_INVALID_FLD + CNT_EXTRA_FLD ]
      @records << record                  #  それをさらに配列に
    end
    
    #  検索エンジンのリファラを含むレコードを抽出  #
    @records_yah = Array.new
    @records_msn = Array.new
    @records_ggl = Array.new
    @records.each do |record|
      if    (record[IDX_REFERER] =~ SE_URL_YAH_1)==0  then  @records_yah << record
      elsif  (record[IDX_REFERER] =~ SE_URL_YAH_2)==0  then  @records_yah << record
      elsif (record[IDX_REFERER] =~ SE_URL_YAH_3)==0  then  @records_yah << record
      elsif (record[IDX_REFERER] =~ SE_URL_MSN_1)==0  then  @records_msn << record
      elsif (record[IDX_REFERER] =~ SE_URL_MSN_2)==0  then  @records_msn << record
      elsif (record[IDX_REFERER] =~ SE_URL_MSN_3)==0  then  @records_msn << record
      elsif (record[IDX_REFERER] =~ SE_URL_GGL_1)==0  then  @records_ggl << record
      elsif (record[IDX_REFERER] =~ SE_URL_GGL_2)==0  then  @records_ggl << record
      elsif record[IDX_REFERER] =~ /^#{GGL_IP_1}[0-9]+\.[0-9]+#{GGL_SFX}/
                                                            @records_ggl << record
      elsif record[IDX_REFERER] =~ /^#{GGL_IP_2}[0-9]+\.[0-9]+#{GGL_SFX}/
                                                            @records_ggl << record
      elsif record[IDX_REFERER] =~ /^#{GGL_IP_3}[0-9]+\.[0-9]+#{GGL_SFX}/
                                                            @records_ggl << record
      elsif record[IDX_REFERER] =~ /^#{GGL_IP_4}[0-9]+\.[0-9]+#{GGL_SFX}/
                                                            @records_ggl << record
      elsif record[IDX_REFERER] =~ /^#{GGL_IP_5}[0-9]+\.[0-9]+#{GGL_SFX}/
                                                            @records_ggl << record
      end
    end
  end


  #  ユニークホスト数
  def count_uniqhost
    ips = Array.new
    @records.each do |record|
      ips << record[IDX_IP]  #  IPアドレスのみ配列に抽出
    end
    ips.uniq.length
  end
  
  
  #  検索エンジンからの総アクセス数
  def count_access_from_se(se)
    if    se == SE_GGL  then @records_ggl.length
    elsif  se == SE_YAH  then @records_yah.length
    elsif  se == SE_MSN  then @records_msn.length
    else  return(0)
    end
  end


  #  検索エンジンからのユニークホスト数
  def count_uniqhost_from_se(se)
    if    se == SE_GGL  then records = @records_ggl
    elsif  se == SE_YAH  then records = @records_yah
    elsif  se == SE_MSN  then records = @records_msn
    else  return(0)
    end
    
    ips = Array.new
    records.each do |record|
      ips << record[IDX_IP]  #  IPアドレスのみ配列に抽出
    end
    ips.uniq.length
  end
  
  
  #  レコードごとのリクエストファイル名と検索語句を抽出
  def get_files_and_searchwords(se)
    if    se == SE_GGL
      records  = @records_ggl
      qname    = 'q'		#  「検索語句」パラメータ名
      iename  = 'ie'		#  「検索語句入力文字コード」パラメータ名
    elsif  se == SE_YAH
      records = @records_yah
      qname    = 'p'
      iename  = 'ei'
    elsif  se == SE_MSN
      records = @records_msn
      qname    = 'q'
      iename  = ''
    else
      return(nil)
    end
    
    files_and_searchwords = Array.new  
    
    records.each do |record|
      file_and_words = Array.new              #  ひと組のリクエストファイル名と検索語句
      
      #  リクエストファイル名を取得
      file_and_words << (record[IDX_CMD].split)[1]

      #  検索語句を切り出し
      ref = record[IDX_REFERER]# + '&'

      q = ref.scan(/#{qname}=[^&]+/)      #  "(「検索語句」パラメータ名)="から"&"の前までを切り取り
      if q[0] == nil              #  見つからなければそのレコードは無視
        next
      else
        q[0].sub!(/^#{qname}=/,'')        #  "(「検索語句」パラメータ名)="切り落とし
        words = q[0]
      end
    
      #  検索語句入力文字コードを取得
      enc = ''
      if iename != ''
        ie = ref.scan(/#{iename}=[^&]+/)    #  "(「検索語句入力文字コード」パラメータ名)="から"&"の前までを切り取り
        
        if ie[0] != nil
          ie[0].sub!(/^#{iename}=/,'')      #  "(「検索語句入力文字コード」パラメータ名)="切り落とし
          enc = ie[0].downcase
        end
      end

      #  検索語句をURLデコード
      words = CGI.unescape(words)
      #  検索語句文字コードをsjisに
      if (enc =~ /#{IE_SJIS_1}/) || (enc =~ /#{IE_SJIS_2}/)  #  検索語句入力文字コードがsjis
      elsif enc =~ /#{IE_EUC}/
        words = words.kconv(Kconv::SJIS,Kconv::EUC)
      elsif enc =~ /#{IE_UTF8}/
        words = words.kconv(Kconv::SJIS,Kconv::UTF8)
      else                                                  #  指定なしまたは不明
        words = words.kconv(Kconv::SJIS,Kconv::AUTO)
      end
    
      file_and_words << words
      files_and_searchwords << file_and_words
    end
    files_and_searchwords
  end
  
end