最終更新日



アクセス・カウンタのプログラム

1 はじめに

ホームページで、データを残さないといけないとき、wwwサーバ上に保存します。そのための約束事がCGI(Common Gateway Interface)。多くはPerlで組まれています。アクセス・カウンタは、データを残さないといけないという点で、やはりCGIを使う必要があります。

2 アクセス・カウンタ 、杜甫々さんのwcnt311.cgiを使わせていただいていたのですが、複数ページにおくように自力で改変しました。カウンタのデータ自体はいたって簡単ですが、CGIはデバッグが難しくて初めて取り組むのは結構大変でした。その苦闘の克明な記録と改変したソースリストです。

3 ページ毎のカウンタを一括して見るcounter-table.cgi は自作、CGIのソースリストを出しますが、主に、HTTPサーバによるデバッグ過程での失敗を紹介します。

4 トータルカウンタのグラフィック表示 は、2では省略した杜甫々さんのwcnt311.cgiの一部、数字画像を扱う部分をそのまま使ったもので、ソースも短いものです。

2 アクセス・カウンター

1 複数ページのカウンタ(杜甫々さんの「WwwCount Ver3.13」を改変)

Perlの勉強はある程度できて、いよいよCGI。 CGI・Perl入門 作成者:kamitani なども見て、「CGIプログラミング入門」 秋本祥一、古川剛著 SHOEISHA 1997/2初版を読みつつ、まずはアクセス・カウンターにとりかかりました。

私の場合、全体的なカウンタとして、ホームページ開設したときから、プロバイダである@niftyの提供しているものを利用していましたが、マニュアルに従っただけのことで、全然分かっていませんでした。それとは別にあるページだけのアクセスを見たいということで、とほほのCGIソフト集 にあるwcnt313.cgiを入れてみました(2001/9より)。丁寧な解説もあり、比較的簡単にできました。

そのreadme.htmには、複数ページにカウンタをつける方法もありますが、別々のカウタファイルをもうけることになります。勉強をかねて、これを一つのファイルでやってみたいということで苦闘しました。

最初は各ページにグラフィックカウンターで表示するようにしていたのですが、一括して見られればその方がいいだろうと、隠しカウンタ(wwwcount.cgi?hide+momig.gif+C121")に変更し、一括してみるための別プログラム、counter-table.cgiを作る事にしました。

2 「WwwCount Ver3.13」を改変したソース

#!/usr/local/bin/perl

#杜甫々さんの「WwwCount Ver3.11」使用、「利用/改造/再配布可能。確認メール不要」
#ということで、かなり大幅に改変。オリジナル版は、下記をご覧下さい。
# 最新版入手先: http://tohoho.wakusei.ne.jp/
# 取り扱い: フリーソフト。「利用/改造/再配布可能。確認メール不要」。
#==================================================================
#主な改変事項(2002.4 高橋 賢)
#1 複数ページにカウンタを置くが、1ケのデータファイルとする。
#2 トータルカウンタを別におく
#3 汎用的なチェック機能を簡素化。
#4 wwwサーバがNiftyの場合、ホスト名が取得できないようなので、省略。
#5 一つ前のURLを取得するHP_REFERERもうまくいかないので、省略。
#6 隠しカウンタのみのルーティンとし、テスト、グラフィックモードを省略。
#7  アクセスログのメール送信、省略
#8 使いかた、更新履歴に関するコメント文 省略
#==================================================================
# ファイルロック機能をオン。
$do_file_lock = 1;

# 同アドレスチェック機能をオン。
$do_address_check = 1;

#==================================================================
# 処理部:
#==================================================================
# カウンター名他
$file_count  = "wwwcount.cnt";
$file_date   = "wwwcount.dat";
$file_access = "wwwcount.acc";
$file_lock   = "lock/wwwcount.loc";

# 引数を解釈する
if ($ARGV[0] eq "hide") {
		$mode = "hide";
		$giffile = $ARGV[1];
		$count_name = $ARGV[2];
}
#==================================================================
#以下はほぼ杜甫々さんのオリジナル版(アクセスログのメール送信は省略)
$ENV{'TZ'} = "JST-9";

# ロック権を得る
if ($do_file_lock) {
	foreach $i ( 1, 2, 3, 4, 5, 6 ) {
		if (mkdir("$file_lock", 0755)) {
			# ロック成功。次の処理へ。
			last;
		} elsif ($i == 1) {
			# 10分以上古いロックファイルは削除する。
			($mtime) = (stat($file_lock))[9];
			if ($mtime < time() - 600) {
				rmdir($file_lock);
			}
		} elsif ($i < 6) {
			# ロック失敗。1秒待って再トライ。
			sleep(1);
		} else {
			# 何度やってもロック失敗。あきらめる。
			exit(1);
		}
	}
}

# 途中で終了してもロックファイルが残らないようにする
sub sigexit { rmdir($file_lock); exit(0); }
$SIG{'PIPE'} = $SIG{'INT'} = $SIG{'HUP'} = $SIG{'QUIT'} = $SIG{'TERM'} = "sigexit";

# 日付ファイルから最終アクセス日付を読み出す。
if (open(IN, "< $file_date")) {
	$date_log = <IN>;
	close(IN);
} else {
	$date_log = "";
}

# 今日の日付を得る
($sec, $min, $hour, $mday, $mon, $year) = localtime(time());
$date_now = sprintf("%04d/%02d/%02d", 1900 + $year, $mon + 1, $mday);
$time_now = sprintf("%02d:%02d:%02d", $hour, $min, $sec);

# 日付が異なる、つまり、今日初めてのアクセスであれば
if ($date_log ne $date_now) {
	# アクセスログを初期化する
	open(OUT, "> $file_access");
	close(OUT);

	# 今日の日付を日付ログファイルに書き出す
	open(OUT, "> $file_date");
	print(OUT "$date_now");
	close(OUT);
}

# すでに同アドレスからのアクセスがあればカウントアップしない
$count_up = 1;
if ($do_address_check==1) {
	open(IN, "$file_access");
	while (<IN>) {
		if ($_ eq "ADDR  = [ $ENV{'REMOTE_ADDR'} ]\n") {
			$count_up = 0;
			last;
		}
	}
	close(IN);
}
#以上は、ほぼ杜甫々さんのオリジナル版(アクセスログのメール送信は省略)
#==================================================================

# カウントアップ処理(2002/3/13複数ページカウンタ用に改変)

#カウンターファイルからカウンター値を読み出し、一時ファイルに書きだす。
$tempfile="temp";
open(FILE, $file_count) or die "Can't open $input.";
open(TMP,">$tempfile") or die "Can't open $input.";
while ($line=<FILE>) {
#  1行内、ページ識別記号とカウントを分離。
        ($hp,$count)=split(/,/,$line);
	chomp($count);
        if ($hp eq $count_name)  {
#        該当ページのカウンタをひとつインクリメントする
              if ($count_up == 1) {
                   $count++;
                }
               print TMP "$hp,$count\n";
#           後で使うため、別名で保存。
	       $hp1=$hp;
    	       $count1=$count;
 	} else {
         	print TMP "$line";
        }
}
close(FILE);
close(TMP);

#一時ファイルをカウンタファイルに書き戻す
rename($tempfile,$file_count);

#トータルカウント処理。
$totalcount="totalcount.cnt";
open(IN, "$totalcount") or die "Can't open $input.";
$totalcnt=<IN>;
close(IN);
chomp($totalcnt);
if ($count_up == 1) {
	#カウンタをひとつインクリメントする
	$totalcnt++;
	open(OUT, ">$totalcount");
	print OUT "$totalcnt\n";
	close(OUT);
}
#後で使うため、別名で保存。
$ttcnt=$totalcnt;

#アクセスログを記録する
open(OUT, ">>$file_access");
print(OUT "Page=$hp1 ");
print(OUT "COUNT=$count1 ");
print(OUT "TOTAL-COUNT=$ttcnt ");
print(OUT "TIME=$time_now\n");
$addr = $ENV{'REMOTE_ADDR'};
print(OUT "ADDR  = [ $addr ]\n");
print(OUT "AGENT = [ $ENV{'HTTP_USER_AGENT'} ]\n");
print(OUT "\n");
close(OUT);

#隠しモード処理
if ($mode eq "hide") {
	printf("Content-type: image/gif\n");
	printf("\n");
	$size = -s $giffile;
	open(IN, $giffile);
	binmode(IN);
	binmode(STDOUT);
	read(IN, $buf, $size);
	print $buf;
	close(IN);
 }

# ロック権を開放する
if ($do_file_lock) {
	rmdir($file_lock);
}

#perl -cw G:\HPTak\cgi-bin\counter\wwwcount.cgi

3 改変したソースへのコメント

最後のコメント文は、コマンドプロンプトで、文法ミスなどをチェックするためのものです。 こんな長いものをその都度、一々打つのはいやなので、コピー貼り付けでできるように用意してあるだけです。Win98のときはだめだったのですが、Win2000になって、コマンドプロンプトで、コピーしたものが右クリックだけで貼り付けできるようになったようです。助かります。

主な改変はコメントの形でプログラムの中に書いていますが、最も大きいのは複数ページのカウンタファイルを一つにして処理する部分です。その考え方、方法とバグとりなどの苦闘ぶりは、CGI、Perl の4-4に書いています。 最初、ページ毎のカウントの合計がとれない問題もなかなか解決しなかったのですが、同上の4-6に書いております。

各ページの文末に、このカウンタを呼び出すために、
 <img src="http://hpcgi1.nifty.com/mstak/counter/wwwcount.cgi?hide+momigi.gif+T0" ALT="ページ独自カウント">
というタグ文をおいています。wwwcount.cgi?hide+momigi.gif+T0の?以下は、3ケの引数です。隠しカウンタ、momigiという小さなgif画像、そしてページ毎の記号(T0はトップページの場合)です。これらの引数の受け取りは、

   # 引数を解釈する
   if ($ARGV[0] eq "hide") {
		$mode = "hide";
		$giffile = $ARGV[1];
		$count_name = $ARGV[2];
   }

CGI、Perl の3-1に書いたコマンドラインの引数受け取りと同様、@ARGVの配列です。 +の処理が必要かと思ったのですが、何もいらないようです。CGIスクリプトへ渡すパラメータ 参照。

プログラム中のロックに関しては、杜甫々さんご自身の解説、ファイルのロックに関する基礎知識 に詳しい。「ロックディレクトリをまず作ってみて作成できたら、「誰も処理中でない」=「自分が処理してもよい」と判断する」というもの。

ただし、flockが使えるようなら、その方がいいかともあります。そのときの注意事項が、とほほのperl入門(リファレンス編) にあり、3,4章のプログラムでは読み出し用のflock(IN,1 )を使っています。

4 記録されるデータ例(wwwcount.acc)

記録されるデータは、全体のトータルをとるtotalcount.cnt、環境変数の情報、アクセス時刻など個々のアクセスの詳細を記録するwwwcount.acc(1日限り)、各ページ毎のアクセス累積情報 wwwcount.cnt、年月日のwwwcount.datです。

CGI、Perl の3-3に書いた環境変数のうち、クライアントのホスト名 $ENV{'REMOTE_HOST'}は、niftyにおいたこのカウントプログラムでは、アドレスと同じにしかならなかったですが、大東文化大においてみたところ、ちゃんと出るようになりました。下記参照。

しかし、「ひとつ前のページのURL」となるはずの$ENV{'HTTP_REFERER'}は、当のURLにしかならず、全く意味がありません。why?

Page=T0 COUNT=16 TOTAL-COUNT=71 TIME=13:07:23
HOST  = [ p1128-dpb27yokoni.kanagawa.ocn.ne.jp ]
ADDR  = [ 61.199.49.128 ]
AGENT = [ Mozilla/4.0 (compatible; MSIE 5.5; Windows 95; ocnie55w0) ]
REFER = [ http://www.t.daito.ac.jp/~t028050/index.html ]

Page=I13 COUNT=1 TOTAL-COUNT=71 TIME=13:07:41
HOST  = [ p1128-dpb27yokoni.kanagawa.ocn.ne.jp ]
ADDR  = [ 61.199.49.128 ]
AGENT = [ Mozilla/4.0 (compatible; MSIE 5.5; Windows 95; ocnie55w0) ]
REFER = [ http://www.t.daito.ac.jp/~t028050/Print/internet3.html ]

Page=T0 COUNT=17 TOTAL-COUNT=72 TIME=18:00:39
HOST  = [ abit ]
ADDR  = [ 192.168.1.2 ]
AGENT = [ Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0) ]
REFER = [  ]

上の例で、HOSTがabitというのは、私のアクセスです。コンピュータ名を簡単にabitとしています(マザーボードがabit社製)。 全体のトータルは同じ日に同じアドレスからのアクセスに対してはカウントしないことにしましたが、いろんなページを見てくだった場合には、ページ毎カウントは増やすことににしました。あるページの新規登録、更新、デバッグのときにやたらに増えたり、表示がうまく行かないときなのか、数秒もしないうちに、ブラウザの「更新」ボタンを押し直しただけでもカウントされるという問題はありますが、妥協しておきます。

したがって、最も厳格なのは、全体のトータルのtotalcount.cnt、次がNiftyのカウンタ(他のアドレスからのアクセスがあった場合には、重複カウントを認めているらしい)、最もアクセス数が多くみえるのは、ページ毎カウントの合計ということになります。

3 ページ毎のカウンタを一括して見るcounter-table.cgi

1 呼び出し方

各ページはカウンタを表示しない隠しカウンタとして、一括してみるための別プログラム、counter-table.cgiを作り、これをトップページから呼び出すことにしました。

  <FORM  NAME="myFORM"  ACTION="http://hpcgi1.nifty.com/mstak/counter/counter-table.cgi" 
   METHOD="POST">
  <P><INPUT TYPE="submit" VALUE="カウンタ一覧表示"></P>
  </FORM>

下記,プログラム中、elsif文で判定しているところは、かなり省略しています。 switch文のようなものはなさそうなので、elsif文の羅列になりました。

2 カウンター一覧のソースリスト

#!/usr/local/bin/perl

print <<EOM;
<HTML>
<HEAD>
<TITLE>カウンター一覧</TITLE>
</HEAD>
<BODY>
<H1>カウンター一覧</H1>
EOM

$file_count = "wwwcount.cnt";

# カウントファイルからページ毎のカウントを読み出す。
open(IN, "< $file_count") or die "Can't open $input.";
flock(IN,1);
@count = <IN>;
close(IN);

# ページ記号からページ名に変換し、カウントをテーブルに記入していく。
$total=0;
print "<table border=1>";
foreach $_ (@count) {
        ($hp,$count)=split(/,/,$_);
        $total=$total+$count;
	if ($hp eq "T0") {print "<tr><td>T0<td>トップページ<td>$count";}
        elsif ($hp eq "C100") {print "<tr><td>C100<td>コンピュータメモ<td>$count";}
        elsif ($hp eq "C111") {print "<tr><td>C111<td>JavaScript基本<td>$count";}
        elsif ($hp eq "C121") {print "<tr><td>C121<td>ADSL導入顛末記<td>$count";}
        elsif ($hp eq "C141") {print "<tr><td>C141<td>MP3,wmaファイルの作成<td>$count";}
        elsif ($hp eq "CC200") {print "<tr><td>CC200<td>コンサートのページ<td>$count";}
        elsif ($hp eq "CC221") {print "<tr><td>CC221<td>コンサート交流詩<td>$count";}
        elsif ($hp eq "CC231") {print "<tr><td>CC231<td>若手指揮者、西本智実さん<td>$count";}
        elsif ($hp eq "T411") {print "<tr><td>T411<td>ペルシャ紀行<td>$count";}
        elsif ($hp eq "T412") {print "<tr><td>T412<td>スペイン紀行<td>$count";}
        elsif ($hp eq "S511") {print "<tr><td>S511<td>ともしび」のスキー<td>$count";}
	elsif ($hp eq "S514") {print "<tr><td>S514<td>さくら・さくら<td>$count";}
	}
}
print "<tr><td>total<td>カウント合計<td>$total";
print "</table><BR>";

# 環境変数TZを日本時間に設定する
$ENV{'TZ'} = "JST-9";

# 今日の日付を得る
($sec, $min, $hour, $mday, $mon, $year) = localtime(time());
$date_now = sprintf("%04d/%02d/%02d", 1900 + $year, $mon + 1, $mday);
$time_now = sprintf("%02d:%02d:%02d", $hour, $min, $sec);

# トータルカウントファイルからカウント総数を読み出す。
$file_totalcount = "totalcount.cnt";
if (open(IN, "< $file_totalcount")) {
	flock(IN,1);
	$totalcount = <IN>;
	close(IN);
} else {
	$totalcount = 0;
}

print "<table border=1>";
print "<tr><td>$date_now<td>$time_now<td align='center'>現在</tr>";
print "<tr><td>トータルカウント<td>$totalcount<td>2002.4.6以来</tr>";
print "</table>";
print "<P>Nifty Count=23830の時、カウント開始</P>";
print "</BODY></HTML>";

3 結果

カウンター一覧 例
T0    トップページ             75
C100  コンピュータメモ         13
C111  JavaScript基本           41
C121  ADSL導入顛末記          175
C141  MP3,wmaファイルの作成    16
CC200 コンサートのページ        7
CC221 コンサート交流詩         16
CC231 若手指揮者、西本智実さん 41
T411  ペルシャ紀行             20
T412  スペイン紀行             23
S511  ともしび」のスキー        1
S514  さくら・さくら            1

total カウント合計           592 

2002/04/13     23:43:32     現在 
トータルカウント 550    2002.4.6以来 

このプログラムは、niftyにおいたときは、何の問題もなかったのに、大東文化大のサーバではまったく動作せず、なかなか、原因がつかめませんでした。冒頭は、niftyと違って、#!/usr/bin/perlとlocalは不要ということも設定したのに。 
 print "<HTML><HEAD><TITLE>カウンター一覧</TITLE></HEAD><BODY><H1>カウンター一覧</H1>"として、改行\nを入れなかったのがいけなかったのかと、ヒアドキュメントとして,改行もいれたのですが、それではなく、最初に
 print "Content-type: text/html\n\n"; が必要でした。この1行で解決、やっとできました。CGIの仕様 参照。

4 HTTPDでのテスト方法

大東文化大へのFTP接続は、NiftyオンリーのADSLではだめなので、通信費の節約のため、AN HTTPDのWindowsサーバでもトライしましたが、同じ現象がありました。中田 昭雄さんの AN HTTP Server Home Page からダウンロードしたHTTPサーバによるHTTPDのオプションで、

   ドキュメントルートを counter-table.cgiのあるフォルダ G:/HPDaito/cgi-bin/counter2
   ブラウザで、http://192.168.1.2/counter-table.cgi

ブラウザのアドレス覧に書くのは http://192.168.1.2/.... でないといけないことをよく忘れます(192.168.1.2は、私の場合のLAN接続の自分自身のコンピュータ・アドレス)。

HTTPDの利用をもうひとつ。wwwcount.cgiをAN HTTPDでテストするにはどうするのか? http://192.168.1.2/wwwcount.cgiと、直接呼ぶしかないのかな? 呼び出しているのが、G:/HPDaito/index.htmlと、フォルダが違うのでそれを呼ぶのは無理? が、臨時に、index.htmlを G:/HPDaito/cgi-bin/counter2と同一フォルダにを移してテストする手はどうかということでやってみました。いけそうです。

4 トータルカウンタのグラフィック表示

1 背景

大東文化大においたホームページは、Niftyのカウンタを使うわけにはいかず、トップページに杜甫々さんのカウンタをそのままおいていましたが、これをトータルカウンタに変えてみることにしました。2-2で省略した「WwwCount Ver3.13」のカウンタ・グラフィック表示の部分を拝借して簡単にできました。

2 「トータルカウンタのグラフィック表示」ソース

#!/usr/bin/perl

# ★ 表示桁数 6桁に指定。
$figure = 6;

# 引数を解釈する
if ($ARGV[0] eq  "gif") {
	$mode = "gif";
}

# トータルカウンタを読み出す
$file_totalcount = "totalcount.cnt";
if (open(IN, "< $file_totalcount")) {
	flock(IN,1);
	$count = ;
	close(IN);
} else {
	$count = 0;
}

# 杜甫々さんのWwwCount Ver3.13のグラフィック表示部分を借用
if ($figure != 0) {
	$cntstr = sprintf(sprintf("%%0%dld", $figure), $count);
} else {
	$cntstr = sprintf("%ld", $count);
}
if ($mode eq "gif") {
	printf("Content-type: image/gif\n");
	printf("\n");
	for ($i = 0; $i < length($cntstr); $i++) {
		$n = substr($cntstr, $i, 1);
		push(@files, "$n.gif");
	}
	require "gifcat.pl";
	binmode(STDOUT);
	print &gifcat'gifcat(@files);
}

5 あとがき

4月15日(月)は、新学期が始まって最初の授業があり、大学においた私のホームページを一部見せました。トップページ、インターネットのメニューページ、2001年度の学生の作品をまとめたページですが、3コマ、100名近い学生に見せたため、カウンタ一覧の該当ページと合計は急増しました。しかし、トータルカウントの方は、LAN接続で全員同じアドレスということで、全く増えていません。ページ毎のカウントとその合計は、同一アドレスからのアクセスかどうかのチェックは外していることから、大きな違いが出たものです。各ページへのアクセス状況のおよその傾向を見るには、この方法が向いているでしょう。学生がきちんとやっているかどうかも見当がつきます。

しかし、このような大きな違いはそうそうはありません。2-4にも書きましたが、最も厳格なのは、全体のトータルのtotalcount.cntです。同じ日の同一アドレスからのチェックは全くカウントしません。次がNiftyのカウンタです。

詳しくはわかりませんが、例えば、同じ日の同一アドレスの場合、時間をおいてアクセスしても、他からのアクセスがない場合は増えないが、それがあるときは増えるように見えます。totalcount.cntでは、この場合も一切増えないので、多少、Niftyのカウンタの方が多くなります。

どんな場合でもカウントされるのがページ毎カウントの合計だから、最もアクセス数が多くみえます。どこかのページにアクセスいただいて、他のページもいろいろと見ていただいた場合に、そのページのカウントを増やした方が、ページ毎のアクセス傾向を見るには都合がいいです。単純な戻りの場合など、増加させたくないときも増えてしまう欠点はあり、経過時間などでチェックするなど避ける方法もあるでしょうが。そこまではやっていません。

FTPでないと見られないのですが、wwwcount.accには克明に記録が残りますから、補完的に見ると、参考になります。

ページ独自カウント