アクセスカウンター

【PHP】アクセスカウンター(リロード対策・クローラー、指定IP除外)を自作してみた

2024/8/7 

ご覧いただきありがとうございます、しいたけと申します。

本ホームページを作るにあたってアクセスカウンターを自作したのでその備忘録を書いていきます。 色々機能付けてみたので、もしこれからアクセスカウンター自作しようとしてる方が見てくださってるならよければ参考にでもしてくださいませ。 コピペして使いたいという方も下の方に全文css付きで載せてますのでご自由にどうぞ。

カウンターの仕様

まずは仕様の説明から。アクセスカウンターにも色々ありますが、今回作ったのは以下のような仕様になっております。

  • 総カウント、本日のカウント、昨日のカウント、今日の日付を表示
  • 訪問者のIPを記録し、同日中の同一IPでのアクセスはカウントしない
  • クローラー(Bot)をユーザーエージェントで判別して除外
  • 指定したIP(自分のIPなど)を除外

基本的なカウント機能に加え、連続カウントの防止、クローラーと自分によるアクセスのカウントも除外するようにしました。 同日中の同一IPでのアクセスをカウントしないというのにしたのは、同時に複数の人(A,B)がサイトをA→Bの順で訪れた時、 Aさんがページ内を移動してまたトップページ(カウンターを設置してる場所)に戻ってきたときにカウントされてしまうのを防ぐためです。

完成したコードは最後に載せるとして、まずはそれぞれどのように実装したか触れていきます。

各カウント、日付の表示

これは比較的簡単で、カウント数を記録するファイル(counter.dat)を 総カウント数,本日のカウント数,昨日のカウント数,最後にカウントが更新された日付 というような形式でカウントされた際に出力し、次に読み込まれた際にこれをうまく利用することで各カウントごとに表示できます。

日付はdateで取得できるのでそれを表示させるだけです。

IPの記録、同日中のカウントをしない

これは、同様にカウントされた際に訪問者のIPを記録するファイル(ip_list.dat)を出力し、 次に読み込まれた際にこのファイルにあるIPと一致したらカウントしないようなコードを書くことで出来ます。 訪問者のIPの取得は$_SERVER['REMOTE_ADDR']で出来ます。

ただし、これだと一度訪問した人は永遠にカウントされなくなってしまうので、 それを回避するため、先ほどのcounter.datに記録されている日付とこれを読み込んだ際の日付が異なる際にIPリストを空にするような処理をしてあげます。 これによって日付が変わればまた同一IPでもカウントされるようになります。 一度訪問した人を永遠にカウントしない仕様にしたければこの処理部分をなくせばOKです。

クローラーの除外

同じように、訪問者のユーザーエージェントを$_SERVER['HTTP_USER_AGENT']で取得し、 これとクローラー(Bot)のユーザーエージェントが一致した際にカウントしないようにすれば実装できます。

これだけ聞くと簡単に思えますが、クローラーは世界中で色んな会社が運用しておりものすごい種類があるため、 クローラーのユーザーエージェントのリストを用意するのはなかなか骨が折れます。 有名どころのものだけリストにすれば問題なさそうですが、実際カウンターを試験的に運用した段階で全然知らんとこのBotが記録されてしまったのを見るとそうもいかないなと。 そこで色々調べていると、同じようにクローラーを判定しようとしてる方の記事から、 こちらクローラーのユーザーエージェントをJSONでリスト化してくれている素晴らしいリポジトリが公開されているのを発見しました。 これをありがたくダウンロードさせて頂き、以下のようなPHPを組んで正規表現のパターンをまとめた配列(crawler-user-agents-list.txt)を作りました。

<?php
//ファイルの内容を文字列として読み込む
$json = file_get_contents("crawler-user-agents.json");

//文字列を配列に変換
$array = json_decode($json, true);

//パターンをリスト化する
$list = array();
foreach($array as $value){
    $pattern = array($value["pattern"]);
    $list = array_merge($list, $pattern);
}

//リストの配列の要素を改行で区切った文字列に変換
$crawler_user_agents_list = implode("\n",$list);

//なぜかバックスラッシュが混ざっていてこの後利用しにくいので削除する
$crawler_user_agents_list = str_replace('\\', '', $crawler_user_agents_list);

//crawler-user-agents-list.txtとして出力する
file_put_contents("crawler-user-agents-list.txt",$crawler_user_agents_list);
?>

これによって得られるtxtファイルがこんな感じ↓

実際はこんな感じのBotが下までずっと続いてます。 これでクローラーのユーザーエージェントのリストが用意出来たので除外することが出来るようになりました。 ちなみに上記のPHPはカウンター本体の方に埋め込んでも多分大丈夫ですが自分はカウンターが動くたびにこの処理させるのがなんか嫌だったので分けました。

指定したIPを除外

これは$_SERVER['REMOTE_ADDR']と指定したIPが一致した際の処理を分けるだけです。 自分のIPは除外したほうがいいでしょう。

カウンター本体+αを作る

いよいよカウンター本体(counter.php)を作っていきます。まずは以下のファイルを用意します。

  • counter.php
  • counter.dat
  • ip_list.dat
  • crawler-user-agents-list.txt(先ほどのもの)

この時、counter.datには初期値として「0,0,0,適当な日付」と入力しておいてください。 ではさっそくcounter.phpの中身を見ていきましょう!

<?php
//タイムゾーンを日本(東京)に設定
date_default_timezone_set ('Asia/Tokyo');

//日付の取得
$day=date("Y/m/d");

//ファイルの読み込み、ロック
$fp=fopen('counter.dat',"r+");
$fp_ip=fopen('ip_list.dat',"r+");
flock($fp,LOCK_EX);
flock($fp_ip,LOCK_EX);
$array=fgets($fp);
$array_ip=fgets($fp_ip);

//counter.datの,で区切られたデータをそれぞれ総カウント数、本日、昨日のカウント数、最後にカウントが更新された日付に分割
list($count,$today,$yesterday,$file_day)=explode(",",$array);

//IPリストのデータを/で区切って配列$ip_listにする
$ip_list=array();
$ip_list=explode("/",$array_ip);

//現在の日付と最後にカウントが更新された日付が異なる場合に、昨日のカウントに前日のカウント数を代入、当日のカウントとIPリストをリセット
if($day!=$file_day){
    $yesterday=$today;
    $today=0;
    $ip_list=array();
    ftruncate($fp_ip,0);
}

//訪問者のIPがIPリストに含まれるか、または指定したIPと一致するかを確認し、どちらかが真であった場合にフラグをtrueにしてループを終了
$ip_found=false;

foreach ($ip_list as $ip) {
    if ($_SERVER['REMOTE_ADDR']==$ip || $_SERVER['REMOTE_ADDR']=='指定したIP') {
        $ip_found=true;
        break;
    }
}

//クローラーのユーザーエージェントのリストを読み込み、訪問者のユーザーエージェントを取得
$bot=false;
$file="crawler-user-agents-list.txt";
$bot_list=file($file);
$user_agent=$_SERVER['HTTP_USER_AGENT'];

//各ユーザーエージェントに対して空白と改行を取り除き、それが訪問者のものに含まれるかチェックし、見つかった場合はフラグをtrueにしてループを終了
foreach($bot_list as $bot_value){
    $bot_value=trim($bot_value);
    if(stripos($user_agent, $bot_value) !== false){
        $bot=true;
        break;
    }
}

//2つのフラグがtrueでない場合(訪問者のIPがリストになく、指定したIPでもなく、クローラーでもない場合)、総カウント、本日のカウント数を+1し、IPリストに追加、IPリストのファイルを書き出す
//ここのif内でわざわざ再び指定したIPでないという条件を書いてるのは、これ無いと指定したIPであっても日付変更直後にカウントされてしまったからです。なんで???
if(!$ip_found && !$bot && $_SERVER['REMOTE_ADDR']!='指定したIP'){
    $count++;
    $today++;
    array_push($ip_list, $_SERVER['REMOTE_ADDR']);
    ftruncate($fp_ip,0);
    rewind($fp_ip);
    fwrite($fp_ip,implode('/', $ip_list));
}

//counter.datに新しいカウント数と現在の日付を書き込み、ロックを解除してファイルを閉じる
ftruncate($fp,0);
rewind($fp);
fwrite($fp,"$count,$today,$yesterday,$day");
flock($fp,LOCK_UN);
flock($fp_ip,LOCK_UN);
fclose($fp);
fclose($fp_ip);
?>

<!--HTML部分--> 
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>アクセスカウンター</title>

<!--cssは別ファイルを読み込み、フォントがフリーフォント使ってるのでそれも読み込み-->
    <link rel="stylesheet" href="counter.css">
    <link rel="preload" href="../font/源泉丸ゴシック_サブセット化.ttf" as="font">
</head>
<body>
<div class="counter">

<!--表示される部分。念のためhtmlspecialchars関数を使って、出力される値を安全にエスケープ。-->
    <p class="total">あなたは<?php echo htmlspecialchars($count); ?>番目のお客様です</p>
    <p>本日<?php echo htmlspecialchars($today); ?>&emsp;&emsp;昨日<?php echo htmlspecialchars($yesterday); ?></p>
    <p>今日は<?php echo htmlspecialchars(date("Y年n月j日")); ?>ですよぅ!</p>
</div>
</body>
</html>

ある程度コメント入れて分かりやすいようにしたつもりですがどうでしょうか? まあ分からなかったら調べるなりChatGPTに聞くなりしてください(丸投げ)。 各関数の使い方とかまで触れてるとキリがないのでね...。 もちろんここのコメントで質問して頂ければ出来る限りお答えしますが多分調べたほうが早いです。 あとプログラミング詳しい方でここおかしいだろみたいな指摘があればお願いします。 如何せん勉強し始めて日が浅いのでちょっと不安。動作テストはしましたが...。

これはおまけですが、デザイン変更しやすいようCSSも載せておきます。 変更したければここの色なりいじってください。ちなみにデザインはサ終した某ソシャゲを参考にしてます。

/*フリーフォントの読み込み*/
    @font-face {
    font-family: '源泉丸ゴシック_サブセット化';
    src: url('../font/源泉丸ゴシック_サブセット化.ttf') format('truetype');
    }

/*フォント指定、テキストの中央寄せ*/
body{
    font-family: '源泉丸ゴシック_サブセット化','Avenir','Helvetica Neue','Helvetica','Arial','Hiragino Sans','ヒラギノ角ゴシック',YuGothic,'Yu Gothic','メイリオ', Meiryo,'MS Pゴシック','MS PGothic';
    text-align: center;
}

/*背景色、要素の四隅の丸め、余白、影の設定*/
.counter{
    background: white;
    border-radius: 16px;
    padding: 10px;
    box-shadow: 0px 0px 4px -1px #4c4c4c;
}

/*文字色、余白、フォントサイズの設定*/
p{
    color: #6e4135;
    margin: 0px auto 10px auto;
    font-size: 15px;
}

/*総カウント数表示部分の文字色、文字影、背景色、要素の四隅の丸め、フォントサイズ、余白の設定*/
.total{
    color: white;
    text-shadow: 1px 1px #c77074;
    background: #ff9ab9;
    border-radius: 8px;
    font-size: 18px;
    padding: 2px 0px 2px 0px;
}

あとはカウンターを埋め込みたい場所に↓を挿入すればOKです。

<iframe src="counter.php" frameborder="0" scrolling="no"></iframe>

必要に応じて参照先変更したりクラス追加したりしてください。

これで終わり!!!いや~いい勉強になりました。完成品はトップページに設置してるのでそちら参照してください(ページ誘導)。 今やフリーで色々公開されてたりそもそもアクセスカウンターなんて設置しないでもGA4とかあったりしますが、 自作したからこそ学べるものも大いにあると思います。

このカウンターを作るにあたって下記の記事、プログラムを主に参考にさせて頂きました。ありがとうございます。

それではご覧いただきありがとうございました!

参考文献

コメント

コメントはありません

カテゴリー

新着記事

2024/10/14

【麻雀入門】完全初心者向けルール解説

2024/9/20

麻雀 待ち・役判定+点数計算ツール 使い方

2024/8/7

【PHP】アクセスカウンターを自作してみた

2024/7/29

ホームページ開設あたって