star back image
people4
電飾 電飾
moon
astronaut

【PHP】アクセスカウンターを設置したい(追記)

BLOG PHPWEBログ
読了約:23分

機能を見直し追記をしました。
変更点は「夜中0時リセット」「有名Botのカウント排除」「Cookieの導入」です。

Botのカウントいらなくなったのですか?

更新記録
  • 夜中0時リセットに変更
  • 有名Botのカウントを排除
  • Cookie(クッキー)の導入
  • カウントのバックアップ機能追加(2025/3)

それは自己肯定感を爆上げるため

トップページにホームページの訪問者数を表示したい。
そういう方はいらっしゃいますでしょうか。↓こんな感じのものです。

アクセスカウンターを目にすることは現在ほどんどなくなりました。

アクセスカウンターは訪問者数に応じて数字がカウントアップしていく仕組みです。
「見られていると言う実感」を得て「自己肯定感をUP」させて「悦に浸りたい」。そういう方におすすめです。

こんな数字を見せられても、読者にメリットあるのですか?

メリットは。。ないかも。
デメリットはアクセスが少ないサイトだとバレます。

アクセスカウンター全盛期、当時の私にはcgiを駆使するのは難しく実現には高いハードルでした。忍者カウンターとか大人気でした。

PHPでやるのはどうでしょうか。
今はAIが力になってくれます。

数字がカウントUPするたびに自己肯定感が爆上がり!?

自己肯定感って必要ですか。
下の動画を見ましょう。

自己肯定感に意味はないかもしれません。。

でも何か残る感じは得られます。やってみた感想ですが。

っ’ω’)っ))→ astrowaveのトップページの右サイドバーに仕込んでいます。

【共有】ソースコード

数字が多くなってくると本当に自己肯定感が爆上げするのか。
それはやってみて、体験しないとわかりません。

要件は以下です。

・PHP版にしたい
・今日と昨日のアクセス数を見比べたい
・IPの判別をして闇雲に連続カウントUPさせたくない
・クローラーのカウントは欲しい(やめた

用意するもの
・index.php ←トップページ
・counter.php  ←プログラムのファイル
・data.dat ←データを記録するファイル

トップページに設置します。読み込ませるファイルを2つ用意します。

index.php

<!-- counts -->
<div class="counts">
  <?php 
  $countsfilename = 'counter.php';
  include $countsfilename;
  ?>
</div>

↑ トップページの表示したい好きな場所にコードを仕込みます。
↓下のcounter.phpファイルを読み込ませております。

counter.php

<?php
// 🔹 1. タイムゾーンを「日本時間」に固定(夜中0時リセット)
date_default_timezone_set('Asia/Tokyo');

$log = "./data.dat";
$backup = "./backup.dat"; // バックアップファイル
$fig = 5; // 桁数

// 🔹 2. Bot(検索クローラーなど)からのアクセスを除外する判定
$userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
$isBot = preg_match('/bot|crawl|spider|slurp|yahoo|bing|google/i', strtolower($userAgent));

// ファイルがない場合は初期データを作成
if (!file_exists($log)) {
    file_put_contents($log, "0 0 0 " . date("Y-m-d") . " 0");
}

$data = file($log, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (!$data || !isset($data[0])) {
    // もしデータが読み込めなかったら初期化
    $data[0] = "0 0 0 " . date("Y-m-d") . " 0";
}

// 保存データ分割(トータル、今日、昨日、日付、IP)※IPはもう使いませんがフォーマット維持のため残します
list($tal, $tday, $yday, $dy, $addr) = explode(" ", $data[0]);

// 日付の確認(年月日で比較)
$today = date("Y-m-d");

// 🔹 3. Cookieを使った二重カウント防止
// ※このCookieは個人を特定・追跡するものではなく「今日カウントしたか」のフラグのみのため、GDPR等でも同意不要とされる「機能上必須なCookie」に該当します。
$cookieName = "astrowave_visited_today";
$hasVisitedToday = isset($_COOKIE[$cookieName]);

// Botではなく、かつ「今日まだカウントされていない(Cookieがない)」場合のみカウントアップ
if (!$isBot && !$hasVisitedToday) {
    
    // 日付が変わっていたらリセット処理
    if ($today != $dy) {
        $dy = $today;
        $yday = $tday;
        $tday = 0;
    }

    $tal++;
    $tday++;

    // カウンターデータを保存
    $newData = implode(" ", array($tal, $tday, $yday, $dy, $addr));

    $fp = @fopen($log, 'w'); // ファイルを開く
    if ($fp) {
        if (flock($fp, LOCK_EX)) { // ファイルロック
            fputs($fp, $newData);
            flock($fp, LOCK_UN); // ロック解除
        }
        fclose($fp);
    } else {
        echo "エラー: カウンターファイルが開けません。";
    }

    // バックアップ処理
    if (mt_rand(1, 10) == 1) { // 10回に1回の確率でバックアップ
        copy($log, $backup);
    }

    // 🔹 4. カウント完了の証として、今日の終わり(夜中0時)まで有効なCookieを発行
    $expire = strtotime('tomorrow'); // 明日の0時
    setcookie($cookieName, "1", $expire, "/");

} else {
    // 🔹 Botの場合、または今日すでにカウント済みの場合は、日付の更新だけ行う(カウントは増やさない)
    if ($today != $dy) {
        $dy = $today;
        $yday = $tday;
        $tday = 0;
        
        // 日付だけ更新して保存
        $newData = implode(" ", array($tal, $tday, $yday, $dy, $addr));
        $fp = @fopen($log, 'w');
        if ($fp) {
            if (flock($fp, LOCK_EX)) {
                fputs($fp, $newData);
                flock($fp, LOCK_UN);
            }
            fclose($fp);
        }
    }
}

// 表示部分
$bbb = sprintf('ようこそ!あなたは<br><span class="s1">%05d</span><br>人目の訪問者です。<br>', $tal);
$yyy = sprintf('昨日<span class="s2">%03d</span>人 今日<span class="s2">%03d</span>人', $yday, $tday);
echo($bbb);
echo($yyy);
?>

※解説、コードの作成はchatGPTに協力してもらっています。

data.dat

0

0と入力するだけでOKです。パーミッションは644でいいそうです。

counter.phpとdata.datファイルができましたら、FTPソフトでサーバーにアップしましょう。
index.phpと同じところに放り込みます。

.datというファイル名は、あまり見ることのないですね。

補足】data.dat

ページアクセスがありPHPが動作したら、data.datは以下のように書き換えられます。

アクセスカウンターのイメージ
20000 37 12 15 0.0.0.0
↑書き換えられた内訳は以下です。

トータルカウント($tal):20000
今日のカウント($tday):12
昨日のカウント($yday):37
日付($dy):00(今日の日付 例えば「15日」なら 15)
IPアドレス($ip): 0.0.0.0(アクセスしてくれた方のIPアドレスが入る)

AIさんと協力すれば簡単。
記念すべき00001カウントを見たくありませんか。

バチクソ簡単じゃないですか。

こんな数字を見せられても、読者にメリットあるのですか?

メリットは。。ないかもですが、何かイベントを企画するとか、どうでしょうか。

補足】バックアップ機能

過去にカウントが意図せぬリセットがありました。
その原因と対策をchatGPTさんに聞いてみました。
色々と質問していたら自動でbackup.datを作成する方法を教えてくれました。

要件は以下です。

data.dat が消えても backup.dat から復元
data.dat に10アクセスのタイミングで backup.dat に保存
リセットが発生しても backup.dat から復元可能

counter.php(バックアップ機能付き)

<?php
$log = "./data.dat";
$backup = "./backup.dat"; // バックアップファイル
$fig = 5; // 桁数
$ipcheck = 1; // 連続IPカウントしない? yes=1, no=0

$ip = $_SERVER['REMOTE_ADDR']; // IPアドレスの取得

// ファイルがない場合は初期データを作成
if (!file_exists($log)) {
    file_put_contents($log, "0 0 0 " . date("Y-m-d") . " 0");
}

$data = file($log, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (!$data || !isset($data[0])) {
    // もしデータが読み込めなかったら初期化
    $data[0] = "0 0 0 " . date("Y-m-d") . " 0";
}

// 保存データ分割(トータル、今日、昨日、日付、IP)
list($tal, $tday, $yday, $dy, $addr) = explode(" ", $data[0]);

// 日付の確認(年月日で比較)
$today = date("Y-m-d");

if (($ipcheck == 1 && $ip != $addr) || $ipcheck == 0) {
    if ($today != $dy) {
        $dy = $today;
        $yday = $tday;
        $tday = 0;
    }

    $tal++;
    $tday++;

    // カウンターデータを保存
    $newData = implode(" ", array($tal, $tday, $yday, $dy, $ip));

    $fp = @fopen($log, 'w'); // ファイルを開く
    if ($fp) {
        if (flock($fp, LOCK_EX)) { // ファイルロック
            fputs($fp, $newData);
            flock($fp, LOCK_UN); // ロック解除
        }
        fclose($fp);
    } else {
        echo "エラー: カウンターファイルが開けません。";
    }

    // 🔹🔹 ここにバックアップ処理を追加! 🔹🔹
    if (mt_rand(1, 10) == 1) { // 10回に1回の確率でバックアップ
        copy($log, $backup);
    }
}

// 表示部分
$bbb = sprintf('ようこそ!あなたは<br><span class="s1">%05d</span><br>人目の訪問者です。<br>', $tal);
$yyy = sprintf('昨日<span class="s2">%03d</span>人 今日<span class="s2">%03d</span>人', $yday, $tday);
echo($bbb);
echo($yyy);
?>
1. data.dat ファイルが何らかの理由で削除/上書きされた
サーバーのリセット や 書き込みエラー によって data.dat が空になると、カウントがリセットされます。

解決策:
data.dat の バックアップ を定期的に取る。
書き込み処理にエラーがないか error_log() などで確認する。

2. 書き込み失敗(アクセス集中時の競合)
flock($fp, LOCK_EX); を使って排他ロックしているが、アクセスが集中 した場合、上手くカウントが増えずにリセットされることがある。

解決策:
flock($fp, LOCK_EX); でロック失敗時のエラーハンドリングを追加する。
書き込みの成功/失敗を file_put_contents() で確認する。

3. data.dat のデータ形式が破損している
data.dat のフォーマットが崩れ、読み込めなくなった場合、カウントが0から始まる。

解決策:
data.dat の フォーマットチェック を行う。
explode(" ",$data[0]); の前に、if (isset($data[0])) を追加し、空のときのデフォルト値を設定する。

4. date("j")!=$dy の処理で誤動作している
date("j")(日付)と $dy がずれてしまうと、毎日カウントがリセットされる可能性がある。

解決策:
date("Y-m-d") など 年月日で比較 するようにする。

何らかの理由、アクセスの競合。あるのですね。。
ファイルがない場合に初期データを書き込む。など、初期化とは何?カウントをリセット?よくわかりません。

回答:
いいえ、カウントをリセットするわけではありません。
初期化の処理は data.datファイルが存在しない場合にのみ実行されます。

この改修でリセット回避ができそうですね。

補足】クッキーの導入について

あれ?自分のアクセスが、1日に複数カウントされている?

googleアナリティクスに、アクティブなのは自分1人なのに、カウントアップしているのはなぜだろう。AIに聞きました。

Googleアナリティクス(GA4)には、基本的にBot(検索エンジンのクローラーなど)のアクセスは入ってきません。

これには、GA4がアクセスをカウントする「仕組み」が大きく関係しています。

なぜGA4にはBotが入らないのか?

1. JavaScriptを実行できないBotが多いから
GA4は、ページの中に埋め込まれた「JavaScriptのタグ」が、ブラウザ上で実行されることで初めてカウントされます。 多くのBot(特に古いものやシンプルなもの)は、HTMLのテキストを読み取るだけで、JavaScriptを実行する能力を持っていません。そのため、GA4のタグが発火せず、カウントされないのです。

2. GA4自体が「既知のBot」を自動で弾いているから
Googleは世界最大の検索エンジンを持っているので、「どのIPアドレスやユーザーエージェントがBotか」という膨大なリストを持っています。 GA4は、このリストに載っている有名なBot(Googlebot、Bingbot、Yahoo! Slurpなど)からのアクセスを、サーバー側で自動的に除外してレポートに表示しないようにしてくれています。

だからこそ、PHPカウンターとの「差」が生まれる
IPアドレス判定の限界

これまでの「IPアドレス」を使った二重カウント防止は、間に別の人(やBot)のアクセスが挟まると、記憶が上書きされてしまい、自分のアクセスがまたカウントされてしまうという弱点がありました。

解決策はCookie

Cookieを使うと、アクセスしてくれた人のブラウザに「今日カウント済みチケット」を渡すことができます。 これにより、自分が何回リロードしても、他の読者が何回リロードしても、1人につき1日1回しかカウントされないという、Googleアナリティクスに近い『誠実で正確な数字』が取れるようになりました。

ヨーロッパの規制(GDPR)と今回のCookieについて

Cookieってヨーロッパの法律とかで厳しくないの?って思いますよね。
AIに聞いて見ました。

💡 このCookieは同意バナー不要です!
「Cookieを使う」と聞くと、最近よく見る「Cookieに同意しますか?」という邪魔なポップアップ(GDPR対策)が必要になるのでは?と心配になるかもしれません。 しかし、今回使っているCookieは「今日カウントしたかどうかの目印(1か0か)」だけで、個人情報や追跡データは一切含んでいません。 このような「機能上必須なCookie(エッセンシャルCookie)」は、事前の同意なしで使っても法的に問題ないとされていますので、安心して使ってください!

面倒臭いことにならないで良かったですね。

これで技術的にも法的にもいい感じの大満足なアクセスカウンターではないでしょうか。

FTPでアップロードして、すぐにカウンターが動き出します!

【AI】イラストを描いてもらいました

この記事にピッタリなイラストのための考えたリクエストは、「背景にテレビモニターが敷き詰められている記者会見場でインタビューを受ける小太りのパソコンオタクのおじさん。マイクが置かれているテーブルには、記者の人数を表すデジタルの電光掲示パネルがある。」です。

そして選んだモードは以下の3つです。

  • 特撮
  • サイバーパンク風
  • UnrealEngine5風

あの喜びおじさんに似ているような。。

あのおじさんって誰ですか。

参考サイト置き場

参考にさせていただきました。ありがとうございます。

マック屋@ソフトウェアエンジニア
https://zenn.dev/taiyok/articles/363edf347187e9

KOHIMITO LABO
https://kohimoto.com/labo/web/system/6200/

瀬戸餅屋
https://setomochiya.com/articles/PHP/20240807_article.php
クローラーを除外したい時に

けしのみ工房
https://www.keshikan.net/fonts.html
デジタルな7セグ・14セグフォント 「DSEG」

星間旅路のメロディ

「宇宙の静けさに包まれながら、漂流する過去の音楽を捜し求め、銀河の奥底でその旋律に耳を傾ける。」

「この電波はどこの星からきたのだろうか。」

どこかで聞いたことがあるような。