Apacheアクセスログと私(UAと外部のリファラーを集計)

こないだ作ったリクエストファイル集計部だが,ちょっと変えれば他のデータも集計できるはず.他に集計したいと言ったら,ユーザーエージェント(どんなブラウザが多いか分かれば表示確認の参考にできるからね)と,後は外部リンク(これは単純に興味で)かなあ.

またまたつまずきながら作ってみる.

#集計表示
#引数 adddata は,集計するデータの種類を指定
#page=リクエストファイル
#ua=ユーザーエージェント
#lnk=外部リンク
#all=上記3種を上位10番目まで表示
#引数logおよびdateは,adddata=allの時に全件データへのリンクを張る際に使う.
def addup(file, adddata, log, date)
  case adddata #ヘッダー処理
    when "page"
      header_out("リクエストファイル")
    when "ua"
      header_out("ユーザーエージェント")
    when "lnk"
      header_out("外部リンク")
    when "all"
      header_out("1日の集計")
  end

  date_out(file) #処理ファイル表示

  robot_re = robot_name
  ignore = Regexp.new("#{IGNORE_FILE_END}$|#{IGNORE_FILE}",true)
  logfile = open(file)
 
  page_count = Hash.new(0) #リクエストファイルを数え上げるためのハッシュ
  ua_count = Hash.new(0) #ユーザーエージェントを数え上げるためのハッシュ
  lnk_count = Hash.new(0) #外部リンクを数え上げるためのハッシュ
  ip_prev = String.new #同一IPからのデータを除く(UA処理で使用)

  while logline = logfile.gets
    line = Accesslog.new(logline)
    #無視するファイル・エラーコード・クローラーは処理しない
    unless ignore =~ line.requestURI || /^[4|5]/ =~ line.status || robot_re =~ line.user_agent
      #リクエストファイルの数え上げ
      if adddata == "page" || adddata == "all" then
        tmp_uri = line.requestURI
        #"/"と"/index.shtml"は一緒のファイルなので,同じにしておく
        tmp_uri = "/" if tmp_uri == "/index.shtml"
        unless page_count.key?(tmp_uri) #もし,まだ数えたことのないページなら
          page_count[tmp_uri] = 1 #ページをキーとするハッシュの要素に1を設定.
        else
          page_count[tmp_uri] += 1 #すでに数えたことがあれば+1
        end
      end
      #ユーザーエージェントの数え上げ
      if adddata == "ua" || adddata == "all" then
        tmp_ua = line.user_agent
        #直近の同一IPからのアクセスは除く
        ip_last = line.ip #IP保持
        unless ip_last == ip_prev #IPが前と同じでなかったら
          unless ua_count.key?(tmp_ua)
            ua_count[tmp_ua] = 1
          else
            ua_count[tmp_ua] += 1
          end
        end
        ip_prev = ip_last
      end
      #外部リンクの数え上げ
      if adddata == "lnk" || adddata == "all" then
        tmp_ref = line.referer
        #リファラーが?も=も含んでおらず(検索サイト除外)
        #リファラー無しでもなく,自サイトでなければ
        unless /\A#{URI_SELF}/ =~ tmp_ref || tmp_ref == "-" || /[\?|=]/ =~ tmp_ref then
          unless lnk_count.key?(tmp_ref)
            lnk_count[tmp_ref] = 1
          else
            lnk_count[tmp_ref] += 1
          end
        end
      end
    end
  end
  logfile.close

  #多い順に並べ替え・同数ならデータの名前順
  page_count = page_count.to_a.sort{|a, b| (b[1] <=> a[1]) * 2 + (a[0] <=> b[0]) }
  ua_count = ua_count.to_a.sort{|a, b| (b[1] <=> a[1]) * 2 + (a[0] <=> b[0]) }
  lnk_count = lnk_count.to_a.sort{|a, b| (b[1] <=> a[1]) * 2 + (a[0] <=> b[0]) }

  #リクエストファイル集計表示
  if adddata =="page" || adddata == "all" then
    print "<h3>リクエストファイル集計</h3>\n"
    print "<table summary=\"リクエストファイル集計(エラーファイル・クローラーによるアクセス・画像等へのアクセスは除く)\">\n"
    print "<tr><th class=\"data-name\">リクエストファイル</th><th class=\"number\">訪問数</th><th><br></th></tr>\n"
    maxcount = page_count[0][1] #最大リクエスト数を保持
    rank = 0 #UA集計を同時に表示するときのカウンタ
    f_value = 0 #前のハッシュの値を保持

    page_count.each{|key, value|
      #UA等も同時に表示するときは上位データのみ表示
      if adddata == "all" then
        if rank >= 11 #adddateがallで11番目のデータが来たら
          unless value == f_value #前のデータの値と同じでない限り
            #全データへのリンクを張って
            print "<tr><td colspan=\"3\"><a href=\"#{CGI_FILE}?log=#{log}&amp;date=#{date}&amp;mode=addup&amp;adddata=page\">全リクエストファイル</a></td></tr>\n"
            break #イテレータをぬける
          end
        end
        rank += 1
        f_value = value
      end
      if %r|^/diary/.+| =~ key #もしリクエストファイルがdiary内だったら
        key = URI.decode(key) #キーをURIデコード
      end
      graphlen = 400 * value / maxcount #棒グラフの長さを設定
      print "<tr><td>#{key}</td><td>#{value.to_s}回</td>\n"
      print "<td><img src=\"graph.gif\" width=\"#{graphlen}\" height=\"10\" alt=\"\"></td></tr>\n"
    }
    print "</table>\n"
    rank = 0 #次で使うので初期化
  end
  if adddata == "ua" || adddata == "all" then
    print "<h3>ユーザーエージェント集計</h3>\n"
    print "<table summary=\"ユーザーエージェント集計(エラーファイル・クローラーによるアクセス・画像等へのアクセス・直近の同一IPからのアクセスは除く)\">\n"
    print "<tr><th class=\"data-name\">ユーザーエージェント名</th><th class=\"number\">訪問数</th><th><br></th></tr>\n"
    maxcount = ua_count[0][1] #最大数を保持
    ua_count.each{|key, value|
      if adddata == "all" then #上位データのみ表示する処理
        if rank >= 11
          unless value == f_value
            print "<tr><td colspan=\"3\"><a href=\"#{CGI_FILE}?log=#{log}&amp;date=#{date}&amp;mode=addup&amp;adddata=ua\">全ユーザーエージェント</a></td></tr>\n"
            break
          end
        end
        rank += 1
        f_value = value
      end
      graphlen = 400 * value / maxcount
      print "<tr><td>#{key}</td><td>#{value.to_s}回</td>\n"
      print "<td><img src=\"graph.gif\" width=\"#{graphlen}\" height=\"10\" alt=\"\"></td></tr>\n"
    }
    print "</table>\n"
    rank = 0 #次で使うので初期化
  end
  if adddata == "lnk" || adddata == "all" then
    print "<h3>外部リンク集計</h3>\n"
    print "<table summary=\"外部リンク集計(エラーファイル・クローラーによるアクセス・画像等へのアクセス・検索サイトからのリンクは除く)\">\n"
    print "<tr><th class=\"data-name\">リンクURI</th><th class=\"number\">訪問数</th><th><br></th></tr>\n"
    maxcount = lnk_count[0][1] #最大数を保持
    lnk_count.each{|key, value|
      if adddata == "all" then #上位データのみ表示する処理
        if rank >= 11
          unless value == f_value
            print "<tr><td colspan=\"3\"><a href=\"#{CGI_FILE}?log=#{log}&amp;date=#{date}&amp;mode=addup&amp;adddata=lnk\">全外部リンク</a></td></tr>\n"
            break
          end
        end
        rank += 1
        f_value = value
      end
      graphlen = 400 * value / maxcount
      print "<tr><td><a href=\"#{key}\">#{key}</a></td><td>#{value.to_s}回</td>\n"
      print "<td><img src=\"graph.gif\" width=\"#{graphlen}\" height=\"10\" alt=\"\"></td></tr>\n"
    }
    print "</table>\n"
  end

  footer_out() #フッタ表示
end

ぐはあ……美しくない,まったく美しくないよ!似たようなことを何回も書いているんだよなあ.もっとすっきりしたコードにできないものか.

今回,引数を付け加えました.adddataというのが一番大事で,これでどのデータを集計するかを分けます.ちなみに,allは最初は概観だけ表示されるのがよろしかろうと思って加えたもので,指定すると,データが多いものは,上位10位ほどだけ出て,残りは全データ表示へのリンクになります.このリンクを張るときに使うのがlogとdateの引数.

ユーザーエージェントの数え上げでは,同一の人の使うブラウザを何度も数えてもしょうがないなあと思い,前のデータとIPを比べて違っていたときだけカウントしています.もっとも,同時に違う人がアクセスしていて,アクセスログに交互に記録が残ると,結局,たくさんカウントされてしまうので,あんまり頭のいい処理ではない.いっそのこと,同一IPは省いちゃうのが吉かなあ.

外部リンクの数え上げは,リファラーを拾っています.自サイトからの移動は必要ないので除外,リファラーなしも除外.そして,検索結果からの移動も除外します.この検索結果からの移動というのはずいぶん迷ったのですが,URIに?や=があったら機械的に抜きました.ただ,これだと,掲示板やブログ等から飛んできた場合も抜いてしまうことになる場合があるわけで,大いに悩むところです.

検索結果と言えば,どんな単語の検索に引っかかっているかも気になるところなので,集計してみたいんですが,これはいろんな検索サイトのURIの規則が分からないとできないわけで,けっこう難題だと思われます.

あ,一応,上述のものの表示結果

日時: 2005年11月13日 | PC/Web > Ruby |

コメントを投稿

(空欄でもかまいません)

(メールアドレスは管理人に通知されますが,Web上には表示されません)

Powered by Movable Type