Perl/CGI研究室 'PERL-LABO'

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

カウンター履歴グラフ表示

研究内容

ファイルに保存されている過去のアクセス履歴をグラフ表示します。

詳細

いよいよ完成レベルに

CGIプログラムの基本、アクセスカウンターもいよいよ完成? 重複チェック、日毎アクセスの保存。とりあえず実用レベルといえるでしょうか。 それでは、ファイルに保存されている過去のアクセス数の履歴をグラフ表示する プログラムを作りましょう。これで、アクセスカウンターは一段落です。

結果

今までの知識でOK!

当サイトの記事は追加した順番とトップページの記事の順番が違っているので申し訳ないところなのですが、 グラフの表示っていうのはページビューカウンターで既に研究済みです。 番号順に記事を読んでくださった方なら、特に苦労せずに作れるはず。 今回、それほど難しいところはありませんでした。

作成したCGIプログラム

counter.cgi
#!/usr/bin/perl

require 'counter.pl';
require 'getformdata.pl';

$fname    = "count.cgi";
$dayfname = "count_day.cgi";

%form = plab::getformdata();

if ($form{'mode'} ne "acgraph") {
	@ret = plab::incl_counter($fname, $dayfname, 1, 1);
	plab::writecookie(-1);

	print "Content-type: text/html\n";
	print "\n";
	print "<body style=\"margin:0;font-size:15px;\"><tt>";

	if (@ret[0] == 0) {
		print "ERROR";	
	}
	else {
		$txt = sprintf(
			"<small>今日</small> %03d <small>昨日</small> %03d <small>計</small> %04d",
			@ret[1], @ret[2], @ret[3]);
		print $txt;
	}
}
else {
	print "Content-type: text/html\n";
	print "\n";
	plab::viewhistory_counter($fname, $dayfname, 400);
}

counter.pl
# v 1.01

package plab;

require 'lock.pl';
require 'cookie.pl';
require 'stdplab.pl';

# 使い方
#   クッキーによる重複チェックを行う場合はHTTPヘッダ出力時に
#   plab::writecookie() を呼び出すこと

# カウンタ
# 引数(カウントファイル名, 日毎カウントを保存するならファイル名(しないなら空),
#       IPチェックするか否か, クッキーチェックするか否か)
# 戻値は配列で(成功なら1 失敗なら0, 今日カウント, 昨日カウント, 計カウント)
sub incl_counter
{
	local $countfname    = $_[0];
	local $counthisfname = $_[1];
	local $ipcheck       = $_[2];
	local $cookiecheck   = $_[3];

	local($visit, $ip, $lockdir, $line, $day);
	local($totalcount, $todaycount, $yestcount, $prevday, $previp);
	local @ret;

	# クッキーで重複チェック。重複なら$visit が 1 になります。
	$visit = 0;
	if ($cookiecheck) {
		local($prevvisit, $day);
		readcookie();
		$prevvisit = $Cookie{'VISITDATE'};
		$day = getcurrentdatestring();
		if ($prevvisit eq $day) { $visit = 1; }
		$Cookie{'VISITDATE'} = $day;
	}

	# ファイル名など
	$ip        = $ENV{'REMOTE_ADDR'};
	$lockdir   = $countfname . ".lockdir";

	# ロック
	if (! plab::lock($lockdir)) {
		$ret[0] = 0;
		return @ret;
	}

	# 読み込み
	open FILE, "< $countfname";
	$line = <FILE>;
	close FILE;

	($totalcount, $todaycount, $yestcount, $prevday, $previp) = split('<>', $line);

	# 前回と今回でIPアドレスが異なっていて、かつ、
	# クッキーに前回訪問の記録が残っていなかったらカウントします
	if ((! $ipcheck || $previp ne $ip) && ! $visit)
	{
		$day = getcurrentdatestring();
		if ($prevday ne "" && $prevday ne $day) {
			# 日付が変わりました
			# 昨日のアクセス数を別ファイルに移し日付カウントをリセットします
			if ($counthisfname ne "") {
				if (! open(DAYFILE, ">> $counthisfname")) {
					plab::unlock($lockdir);
					$ret[0] = 0;
					return @ret;
				}
			}
			print DAYFILE "$prevday<>$todaycount\n";
			close DAYFILE;
			$yestcount  = $todaycount;
			$todaycount = 0;
		}

		++$totalcount;
		++$todaycount;
	
		if (! open(FILE, "> $countfname")) {
			plab::unlock($lockdir);
			$ret[0] = 0;
			return @ret;
		}

		print FILE "$totalcount<>$todaycount<>$yestcount<>$day<>$ip";
		close FILE;
	}

	plab::unlock($lockdir);

	@ret = (1, $todaycount, $yestcount, $totalcount);
	return @ret;
}

# グラフを表示します
# 引数(日毎データファイル名)
sub viewhistory_counter
{
	local $nowfname      = $_[0];
	local $dayfname      = $_[1];
	local $maxgraphwidth = $_[2];

	# ファイルの読み込み
	if (! open(FILE, "< $dayfname")) {
		print "File open error";
		return;
	}
	local @data = <FILE>;
	close(FILE);

	# 現在のアクセスを追加する
	if (! open(FILE, "< $nowfname")) {
		print "File open error";
		return;
	}
	local $line = <FILE>;
	close(FILE);
	local ($totalcount, $todaycount, $yestcount, $prevday, $previp) = split('<>', $line);
	local $day = getcurrentdatestring();
	push(@data, "$day<>$todaycount");	

	# 年集計、月集計を得る
	local %yeardata;
	local %monthdata;
	foreach (@data) {
		local ($date, $count) = split('<>');
		local ($y, $m, $d) = split('/', $date);
		local $ym = "$y/$m";
		$yeardata{$y} += $count;
		$monthdata{$ym} += $count;
	}

	# 直近の日毎データを連想配列に入れる
	local $n = @data;
	local $nline = 0;
	local %daydata;
	for ($i = $n - 1; $i >= 0 && $nline < 30; --$i, ++$nline) {
		local ($date, $count) = split('<>', $data[$i]);
		$daydata{$date} = $count;
	}
	
	# 以下、HTMLの出力

	print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=Shift_JIS\">\n";
	print "<body style=\"background-color: #F8F8F8;\">\n";
	print "<style type=\"text/css\">\n";
	print "<!--\n";
	print "span.pv { background-color: #5050D0; font-size: 70%; }\n";
	print "div.pvh { border-width: 1 1 1 1; border-style: solid; font-size: 90%;\n";
	print "          background-color: #F0F0F0; line-height: 1.5em; padding: 0 1em 0 1em; }\n";
	print "div.pv  { padding-left: 2em; font-size: 90%; }\n";
	print "td.pv   { font-size: 90%; }\n";
	print "-->\n";
	print "</style>\n";

	print "<div class=pvh>日毎アクセス</div>\n";
	print "<br>\n";
	viewhistory_counteA(\%daydata, $maxgraphwidth);

	print "<br>\n";
	print "<div class=pvh>月毎アクセス</div>\n";
	print "<br>\n";
	viewhistory_counteA(\%monthdata, $maxgraphwidth);

	print "<br>\n";
	print "<div class=pvh>年毎アクセス</div>\n";
	print "<br>\n";
	viewhistory_counteA(\%yeardata, $maxgraphwidth);

	print "<br>\n";
	print "<hr size=1 style=\"padding: 0 1em 0 1em;\">";
	print "<div align=right>\n";
	print "© <a href=http://www.perl-labo.org/>PERL-LABO</a>\n";
	print "</div>";
}
sub viewhistory_counteA
{
	local $dataref       = $_[0];
	local $maxgraphwidth = $_[1];

	local @ks = sort(keys(%$dataref));

	local $maxcount = 0;
	foreach (@ks) {
		local $count = $$dataref{$_};
		if ($maxcount < $count) {
			$maxcount = $count;
		}
	}

	# 閲覧回数1回に対応するグラフの棒の長さを算出
	if ($maxcount == 0) { $maxcount = 1; }
	local $pixelperval = $maxgraphwidth / $maxcount;

	print "<div class=pv>\n";
	print "<table cellpadding=1 border=0>\n";
	foreach (@ks) {
		local $count = $$dataref{$_};
		local $width = int($count * $pixelperval);	# グラフ長さ
		print "<tr>\n";
		print "<td nowrap width=70 class=pv>$_</td>\n";
		print "<td align=right width=40 class=pv>$count</td>\n";
		print "<td width=5></td>";
		print "<td class=pv>";
		print "<span class=pv style=\"padding-left:$width\"> </span></td>\n";
		print "</tr>\n";
	}
	print "</table>\n";
	print "</div>\n";
}

1;

実行結果

当サイトのアクセスカウンターの日毎アクセスグラフです。 あまりにもアクセスが少なくて恐縮ですが、グラフ表示には過去のアクセス履歴が 必要でして、このページだけだとなおさらアクセスが少なすぎてグラフにならないと思われますので 当サイトのアクセスグラフを見てください。

恥ずかしながら、 当サイトのアクセス履歴グラフです

お詫び: 当サイトのアクセスカウンターはその後も改良を加えたため、上記のプログラムでは このアクセス履歴グラフは表示されません。表示されるのは最新のアクセスカウンターによる アクセス履歴グラフとなっております。

ホント、アクセスの少ないサイトなので良かったらリンクしてくださいネ (^^

解説

プログラムの解説 - counter.cgi

アクセスをカウントするCGIプログラムとグラフを表示するCGIプログラムは同じものです。 ?mode=acgraph というのがURLに付いているとグラフ表示になります。 グラフ表示は viewhistory_counter という関数を呼び出すことで行います。 引数は、現在のカウントファイル、カウント履歴ファイル、グラフの最大幅の3つです。

プログラムの解説 - counter.pl

viewhistory_counter 関数を追加しました。

まず、履歴ファイルからデータをまとめて読み込みます。 @data = <FILE>; というやつで、ファイルの内容を配列に入れます。 これ、左辺がスカラー変数の場合は1行読み込むという意味になりますが、 左辺が配列の場合、ファイルの中身を全部読むっていう意味になります。 1行ずつ、配列に入ります。まとめて読むときにすごく便利ですね。

続いて、現在のカウントをファイルから読み込みます。 履歴ファイルには現在のカウント(今日のカウント)が含まれていないので、 こうやって別に読み込まないといけません。 そして、履歴ファイルと同じデータ形式 日付<>カウント にして、 配列の最後に追加します。配列の最後にデータを追加するには、 push 関数を使います。push(配列, データ) とします。

ここからですが、ちょっと頭を使ったかな? 日毎アクセスだけじゃなくて、月毎、年毎のアクセスグラフも表示しようと思いました。 そのやり方ですが。連想配列に、月、それに、年、のアクセスを入れています。 入れているといっても、どんどん足しているだけなんですけども。 連想配列に、{日付}=カウント という形式でデータ入ります。 日付のところは、月の集計なら 2004/12 というように月まで、年の集計なら 2004 というように年だけになります。

そして、メインの日毎アクセスの処理ですが、 過去の日毎アクセスをすべて表示すると、 このCGIプログラムを設置してから凄く時間が経っていたりすると量が多くなりすぎてしまうので、 最近の30日分のデータのみを表示するようにしました。 で、配列の末尾から、最大30個のデータを順に取り出して、ここでも {日付}=カウント という形式で連想配列に入れなおしています。 これは、後でグラフを表示するときに、月毎、年毎のグラフを表示するルーチンと 同じルーチンを使えるようにするためです。

で、準備ができたらHTMLの出力です。 グラフの表示は、日毎、月毎、年毎のそれぞれの連想配列を viewhistory_counteA 関数に渡すことで行います。

viewhistory_counteA 関数の関数名ですが、めっちゃてきとうですねぇ。 関数名の末尾の1文字を A に変えたものです。関数名は、なにをする関数なのかはっきりさせるために 分かりやすい関数名にするのがセオリーですが、この関数は viewhistory_counter 関数からしか 呼ばれないっていう前提で書いているので、それをはっきりさせるという意味を込めて このようにしました。

viewhistory_counteA の中身ですが、関数の引数は連想配列の参照です。 これはページビューカウンターのグラフ表示のところで出てきました。 連想配列を関数の引数にする便利な機能でしたね。 そして、キーをソートした配列を作ります。 このアクセスカウンターでは、日付は 2004/01/01 のように必ず 4桁2桁2桁 になっています。 ですから、日付を文字列としてソートすると、ちゃんと日付の順番になります。 2004/1/1 のように、ゼロを付けていない場合は単に文字列としてソートするだけじゃダメなんですが。 これは、初めからソートするつもりでこうしてたんじゃなくて、たまたまです。 で、日付でソートしたら、グラフ表示のために最大カウントを求めて、あとはグラフを表示します。 グラフ表示はページビューカウンターのものをそのまま真似しています。

動きました

これでアクセスカウンターも完成! とりあえず、アクセスをカウントするっていう機能については合格だと思います。 完成したばかりでアレですが、欲を言うなら、曜日毎のアクセス数、 時間毎のアクセス数、が付いてたら良かったかなぁ? そんなデータ、最初は欲しいと思わなかったしだから付けなかったんですが、 CGIプログラミングって、面白いもので、なんかいろいろ機能を付けたくなるんです (^^; でもそれはまたの機会に。アクセスカウンター、これにて完成です!

分かったこと

  1. @data = <FILE> とするとファイルの中身が全て配列に入ります。1行が1つの要素です。
  2. 配列の末尾にデータを追加するには push(配列, データ) とします。
Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

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