WWWサーバとはネットワーク上のホストからHTTPによるページの要求があった場合にそれに応えて、要求されたページデータを送信するサーバである。サーバプログラムとしては現在Apacheと呼ばれるフリーウェアがもっとも多く利用されている。今回はその記録(ログ)を解析し、どのページがどういう頻度で読まれているか統計的に処理するスクリプトを作成する。
まず、Apacheが残すログであるが、RedHat系Linuxの場合、/var/log/httpd/access_logとして残されることになっている。このログはデフォルトでは日曜日の朝から取り始め、一週間経つと圧縮されてアーカイブとして別の名前で保存され、新規に新しい記録がaccess.logの名前で作られる。書式は以下のようになっている。
xxx.90.209.98 - - [18/Jun/2000:04:02:33 +0900] "GET /~hoge/hore1.html HTTP/1.1" 200 12449 xxx.90.209.98 - - [18/Jun/2000:04:02:35 +0900] "GET /~hoge/line4.gif HTTP/1.1" 200 1078 xxx.90.209.98 - - [18/Jun/2000:04:03:46 +0900] "GET / HTTP/1.1" 200 1993 xxx.90.209.98 - - [18/Jun/2000:04:03:48 +0900] "GET /logo.GIF HTTP/1.1" 200 2662 xxx.90.209.98 - - [18/Jun/2000:04:04:05 +0900] "GET /~huga HTTP/1.1" 301 257 xxx.90.209.98 - - [18/Jun/2000:04:04:06 +0900] "GET /~huga/ HTTP/1.1" 200 4953 xxx.90.209.98 - - [18/Jun/2000:04:04:08 +0900] "GET /~huga/kabe6.gif HTTP/1.1" 200 816 xxx.90.209.98 - - [18/Jun/2000:04:04:08 +0900] "GET /~huga/hero2.gif HTTP/1.1" 200 11434
上記のログを解析するに当たって注意するのは、ログの内容にはページに埋め込まれた画像を読みに来たログも記録されていることである。以下のスクリプトではそのような画像に対する要求を取り除く処理が含まれている。
本日の例題は以下のようなスクリプトとなっている。
#!/usr/bin/ruby j = 0 k = 0 add = 0 ary_ip_address = [0] ary_hit_number = [0] while line = gets() if /(jpg|jpeg|JPG|JPEG|gif|GIF)\s+/ !~ line ary_log = line.split(/\s+/) ip_address = ary_log[0] if k == 0 ary_ip_address[0] = ip_address end catch(:tag) do for i in 0..j if ip_address == ary_ip_address[i] add = 0 ary_hit_number[i] += 1 throw :tag else add = 1 end end end if add == 1 ary_ip_address << ip_address ary_hit_number << 1 end k += 1 j += add end end total = 0 for i in 0..j total += ary_hit_number[i] end print "LOG result -- Total access was ", total, "\n" print "detail\n" for i in 0..j print ary_hit_number[i], " : ", ary_ip_address[i], "\n" end
$ cp /virtual/home/tmp/access_log .
である。上のコマンドはコピーコマンドの第一引数としてファイル名を、第二引数として保存場所をとっている。保存場所は「.」、すなわち、カレントディレクトリ(現在作業をしている場所)である。
次に、スクリプトの内容について説明する。
後ほど使う予定の変数を3つ定義しておく。また、配列を2つ定義しておく。変数は複数のループに亘って使用するので、冒頭で定義する必要がある。また、配列は名前を見るとわかるが、ary_ip_addressがアクセスしてきたIPアドレスを格納する場所で、ary_hit_numberがアクセス回数を格納する場所となる。
毎度おきまりであるが、whileとgets()を利用して、access_logの中身を1行ずつ読み込んで処理をするのが基本ループとなる。
上で述べたように画像データに対するログは不要であるのでそれを省くために、wwwで利用可能な画像形式であるJPEGとGIFに関して、それらを拡張子としたファイルに関する記述を省くようにする。
lineとして読み込んだログの中身をスペースごとに区切って配列 (ary_log) の要素とする。このときに必要なのは配列の1番目の要素であるIPアドレスだけであるが、とりあえず配列としてすべてのデータを保存する。
一番最初の行のアドレスをまずary_ip_address[0]に入れる。2度目のループ処理からは配列に格納されているIPアドレスの中に今読み込んだIPアドレスがあるかどうか探し、ある時には変数addを0、ヒット数を1増やして、ループから抜ける。( catchとthrowの機能を利用している。)また、今までに読み込んだIPアドレスに一致するものがなければ、add=1として次に進む。その後のif...endで今のIPアドレスを配列要素に追加するとともにそこからのアクセス数を1にしている。
次のforループで総アクセス数を計算する。また、各IPアドレスごとにそのアクセス数を表示する。このとき、アクセス数、IPアドレスの順番としている。理由は後で述べる。
アクセス数の多い順に表示させる方法
UNIXには覚えておくと便利なコマンドがたくさんあるが、ここではsortを紹介する。sortは数字の順番や文字コードの順番にファイルの中身を並べ替えるコマンドである。詳しくは
$ man sort
として確認しておくこと。アクセス数は任意の桁の数字であるので、単純にsortしたのでは、2よりも10の方が「小さい数」として判断されてしまう。そこで、オプションとして-n(数字の文字列を数値として評価)をつける。また、数字の大きい順に並べるので-r(逆順)オプションも必要である。このようにsortコマンドを使用して並べ直すために、ログの解析結果をアクセス数が先に来るようにスクリプトを組んだ。
結果として、以下のような操作によりアクセス数の多い順にIPアドレスを表示できる。
$ ./log.rb access_log | sort -nr | less
ここで、スクリプトのファイル名がlog.rb、ログがaccess_logとしている。上のコマンドは中間ファイルを作成すれば、パイプを使わないでも実行できる。最後のlessは結果が一度に表示できないので少しずつ表示させるために付けている。同様の操作は
$ ./log.rb access_log > log.txt
$ sort -nr log.txt > order.txt
$ less order.txt
としても可能であるが、中間ファイルをいちいち作らなくてすむのでパイプを利用した最初の方がスマートではある。
本日の課題はこのページに。
次のページに6.6の課題に関する解答例を示す。