Perl/CGI研究室 'PERL-LABO'

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

IPアドレスで重複回避

研究内容

ページビューカウンターも、IPアドレスで重複カウントを回避するようにしましょう。

詳細

重複カウント回避について

アクセスカウンターの場合、IPアドレスとクッキーの2重チェックで 重複カウントを回避することができました。 ページビューカウンターの場合、IPアドレスでのチェックはできますが、 クッキーでのチェックはできません。 なぜかというと、 アクセスカウンターの場合は、前回サイトに訪問したのがいつかっていうことを 1つだけ記録すればよかったですが、 ページビューの場合、 各ウェブページ毎の前回そのページを閲覧した時刻を記録するということを しないといけないことになり、そうなると情報が多くなりすぎて クッキーに保存するにはデータが大きくなりすぎます。 チェックできないというか、チェックすると負荷が大きくなるということですね。 ページの数が少なければいいですが、 ページの数が多いと大変です。 ですから、ページビューカウンターにはクッキーによる 重複チェックは向かないと思います。 なにかうまいやり方があるのかも知れませんけど、思いつかないです…。

そうなると、IPアドレスでの重複チェックのみということになりますが、 IPアドレスによる重複チェックは、処理速度やCPU負荷を考えると、 前回アクセスされたときのIPアドレスとの比較程度のチェックが現実的なところなんでした。 ですから、IPアドレスでの重複チェックは間に他の人が挟まると機能しないという問題がありました。

クッキーによるチェックが無いのなら、 どうせカウントは正確なものにはならないのだから、 IPアドレスによる重複チェック機能を取り入れてもあんまり意味が無いんでは…というのが 本心です。…ですが、それはアクセス数によりますよね。 このサイトのようにアクセスが少ないサイトの場合は(涙)、 IPアドレスによる重複チェックもかなりよい重複回避の方法になるはずです。 アクセスが少なければ、IPアドレスのチェックのみでも実際のアクセス数に 近いカウントができます。

ということで、ページビューカウンターにIPアドレスによる重複チェック機能を付けることにしました。

結果

方法

各ページ毎に、前回カウントしたときのIPアドレスを記録しておきます。 そのIPアドレスと異なるIPアドレスからのアクセスのみ、カウントを増やす処理を行います。

問題はデータの持ち方です。 データは、URL と 閲覧回数 という形の連想配列で処理していました。 ここに、さらに前回のIPアドレスを保存するにはどうしたらいいでしょうか?

1つ目の案として、 URL と 閲覧回数 という連想配列とは別に、 URL と IPアドレス という連想配列を保存しておくという方法があります。 このやり方だと分かりやすいのですが、処理速度などのことを考えるとイマイチですね。 ファイルが2つになるし。 ファイルアクセスはとてもサーバーに負荷がかかる処理ですから ファイルアクセスが増えるようなやり方はよくありません。

2つ目の案として、連想配列を使うのをやめるという方法があります。 ファイルの1行毎に URL 閲覧回数 IPアドレス というデータを保存しておいて、 連想配列を使わずに、1行ずつ読みながら処理をする感じです。 このやり方、実は処理速度とかCPU負荷などを考えると一番よさそうなんですが、 でもちょっと複雑になるかなぁ?ゼロから作り直しみたいになるし…。

やっぱり連想配列を使う方が楽っぽいですね。 ということで、3つ目の案は、URL と 閲覧回数,IPアドレス というように、連想配列の値に 2つのデータを含ませるというやり方です。 もしアクセスが1日に何十万もあるんなら、2番目の方法にしますけど そんなサイトになるはずもないし、このやり方が良さそうかな (^^;

やりたいことを実現する方法が何通りもあるとき、その中で一番いいものを選ぶっていうのは なかなか大変ですね。

ちなみに、連想配列はファイル内で <> 区切りで保存するようにしていますので、 値に2つのデータを入れるときの区切り記号は別のものにしないといけませんね。 本来はファイル読み書きのときにちゃんと処理すべきなんですけど… (^^;

グラフ表示CGIプログラムも変更が必要です

データファイルにIPアドレスが加わるので、 そのファイルを読み込んでグラフを表示するCGIプログラムの方も 修正が必要になりますね。 データファイルの仕様を変更するっていうのは、 なかなか大変ですね。 最初からIPアドレスを入れるようにしていれば良かったんですけど。

ライブラリ化

あと、ついでというわけではありませんが、ページビューカウンターを1つの関数にします。 これはなんでかというと、アクセスカウンターとか、 ページビューカウンターとか、あとはまだありませんけど、これから リファラーランキングとか、アクセス解析とか、そういうものを作っていったときに、 CGIプログラムの数がどんどん増えていくと、 1つのウェブページが表示されたときに、そのCGIプログラムの数だけ サーバーにリクエストが送られて、複数のCGIプログラムがほぼ同時に実行されて… というようにサーバーへの負荷が大きくなってしまいます。 ですから、カウンターとかアクセス解析のたぐいのものは、 できるだけ全部まとめて1つのCGIプログラムにした方がいいです。 そのために、できるだけ関数の形にしておいた方がいいと思うんです。

ということで、pvcounter.pl という別ファイルにすることにしました。

作成したCGIプログラム

pvcounter.cgi
#!/usr/bin/perl

require 'pvcounter.pl';
require 'counters.pl';

# データファイル名
$fname = 'pvcounter.txt';

print "Content-Type: text/html\n\n";

$arg = $ENV{'QUERY_STRING'};

if ($arg ne "graph") {
	print plab::countpageview($fname);
}
else {
	$maxgraphwidth = 200;	# グラフの棒の最大長さ
	plab::printgraphwithhref_counters($fname, $maxgraphwidth);
}
pvcounter.pl
package plab;

require 'counters.pl';

# 引数(ファイル名)
# ページビューをカウントします
sub countpageview
{
	local $fname = $_[0];
	local $count;

	$ref = $ENV{'HTTP_REFERER'};

	if ($ref ne "") {
		$count = plab::incl_counters($fname, $ref, 1);
	}

	return $count;
}

1;
counters.pl
package plab;

require 'hashfile.pl';
require 'lock.pl';

# 引数(ファイル名, カウントを上げるキー, IPアドレス重複チェックをするかどうか1/0)
# 戻値(カウント)
sub incl_counters
{
	local $fname   = $_[0];
	local $key     = $_[1];
	local $ipcheck = $_[2];

	local $lockdir = $fname . ".lockdir";
	local $ip      = $ENV{'REMOTE_ADDR'};

	plab::lock($lockdir);
		%counters = plab::readhashfile($fname);
		($count, $previp) = split(',', $counters{$key});
		if ($ipcheck == 0 || $ip ne $previp) {
			++$count;
		}
		$counters{$key} = "$count,$ip";
		plab::writehashfile($fname, %counters);
	plab::unlock($lockdir);

	return $count;
}

# ランキング&グラフを出力します
# 引数(ファイル名, グラフの最大幅)
sub printgraphwithhref_counters
{
	local $fname         = $_[0];
	local $maxgraphwidth = $_[1];

	# データ読み込み
	%data = plab::readhashfile($fname);

	# 連想配列の値を閲覧回数のみにする
	while (($k, $v) = each(%data)) {
		($count, $ip) = split(',', $v);
		$data{$k} = $count;
	}

	# 閲覧回数順にソートしたURLの配列を作成
	@sorted_keys = sort { $data{$b} <=> $data{$a} } keys(%data);

	# 閲覧回数1回に対応するグラフの棒の長さを算出
	$maxval = $data{$sorted_keys[0]};
	if ($maxval == 0) { $maxval = 1; }
	$pixelperval = $maxwidth / $maxval;

	# 閲覧回数の合計を算出
	@v = values(%data);
	$tot = 0;
	foreach (@v) {
		$tot += $_;
	}

	# HTMLの出力
	print "<style type=\"text/css\">";
	print "<!--";
	print "span.a { background-color: #5050FF; }";
	print "-->";
	print "</style>";
	print "<table cellpadding=5 border=1 align=center>";
	print "<tr><th>URL</th><th>回数</th><th>割合</th><th>閲覧回数グラフ</th></tr>";
	for (0 .. @sorted_keys - 1) {
		$url = $sorted_keys[$_];			# URL
		$val = $data{$url};			# 閲覧回数
		$width = $val * $pixelperval;		# グラフ長さ
		$per = sprintf("%.1f", $val / $tot * 100);	# 割合
		print "<tr>";
		print "<td nowrap><a href=$url target=_blank>$url</a></td>";
		print "<td align=right>$val</td>";
		print "<td align=right>$per%</td>";
		print "<td><span class=a style=\"padding-left:$width\"> </span></td>";
		print "</tr>";
	}
	print "</table>";
}

1;

実行結果

今回作った pvcounter.cgi も IFRAME タグを使って次のようにウェブページに仕込みます。 一応、そのページの閲覧回数を表示するようにしてみたんですが、 サイズは1x1で最小にして見えないようにしています。

<iframe src=pvcounter.cgi width=1 height=1 frameborder=0></iframe>

このページでも、ここにカウンターを仕込んでます(見えません) →

他にも、pvcounter.cgi を仕込んだページをいくつか作りました。 それぞれ、クリックするとそのページの閲覧回数が1増えます。 IPアドレスで重複チェックしているので、連続してクリックしても回数は1しか増えません。

pvcounter.cgiを仕込んだページ その1
pvcounter.cgiを仕込んだページ その2
pvcounter.cgiを仕込んだページ その3
pvcounter.cgiを仕込んだページ その4
pvcounter.cgiを仕込んだページ その5

現在の各ページの閲覧回数をグラフで表示するにはこちらをクリックしてください。

現在の閲覧回数グラフ

解説

プログラムの補足

カウントするCGIプログラムと、グラフを表示するCGIプログラムを1つにまとめました。 pvcounter.cgi?graph というように ?graph を付けて呼び出すとグラフを表示します。 それ以外のときは、カウントを行います。 なんでこのようにしたかというと、カウントするCGIプログラムと グラフを表示するCGIプログラムは同じデータファイルを参照しますよね。 ということは、データファイルのファイル名が必要ですよね。 これを2つのファイルに書いておくと、 データファイル名を変更する必要があったときに、 2つのファイルを変更しないといけません。 変更し忘れがあると、正しく動きません。 ということで、そういう面倒なこと、トラブルを防ぐために1つに まとめました。これなら、ファイル名は1箇所変えるだけでOKです!

ライブラリにしたので、CGIプログラム本体はとてもすっきりしています。

plab::incl_counter 関数に重複カウントチェック機能を付けましたが、 重複チェックするかどうかを関数の引数で指定できるようにしています。 重複チェックしたくない場合もあるかな?と…

途中、文字列 . 文字列 というのが出てきています。 この . は、文字列をつないで1つの文字列にするっていう文字列結合演算子です。 ただのピリオドなんですけど、Perlではそういう働きがあるんですね。

グラフを出力する部分は、printgraphwithhref_counters というかなりてきとうな 関数にしています。この辺は、まだ変更していく必要がでてくるところだと思います…

動きました

ばっちりです! 重複チェックもばっちり動いてますし、 1つの関数を呼び出すだけになったので、 他のCGIプログラムにページビューカウンター機能を持たせることも簡単です。 これで、ページビューカウンターについては一段落でしょうか? あ、でも、カウントをリセットする機能が無いんですよね、まだ。 何日かしたら自動的にリセットするようにするとかなんか必要ですね。 グラフ表示のところは、もっと綺麗にとか、改良しようと思ったらいくらでも改良できそう。 1つ気になっているのは、URLが生のまま画面に表示されるので、 これをページのタイトルに置き換えたいなっていうこと。 これは、結構近い将来やるかも知れません。

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

Copyright (c) 'PERL-LABO' All Rights Reserved.  リンクフリーです。