Search by

vinou / bot-counter

t3agent

Know which bots actually hit your site. Counts crawler visits into a JSON file with a lifetime total and rolling 7- and 30-day windows, so you see at a glance whether a bot is new, gone, or ramping up. No database, no dependencies, PHP 5.6+.

v1.0.0 2026-09-11 16:59 UTC

This package is not auto-updated.

Last update: 2026-09-12 15:32:09 UTC


README

Counts crawler visits in a small JSON file. Per bot it keeps a lifetime total plus rolling time windows, by default 7 and 30 days.

Comparing the three numbers says more than any single one:

Pattern Meaning
totallast7dayslast30days new bot, just showed up
high total, both windows 0 stopped coming
last7days close to last30days ramping up right now
high total, small last7days steady, low-frequency visitor

No database, no service, no configuration — one file, one call.

Installation

composer require vinou/bot-counter

Requires PHP 5.6 or newer. The counter itself has no dependencies.

Usage

use Vinou\BotCounter\BotCounter;

$counter = new BotCounter(__DIR__ . '/var/logs/bots.json');
$counter->count('Googlebot');

That is all. The file is created on first use — its directory has to exist.

With crawler detection

If you use jaybizzle/crawler-detect, the optional adapter detects and counts in one call:

composer require jaybizzle/crawler-detect
use Vinou\BotCounter\BotCounter;
use Vinou\BotCounter\CrawlerDetectAdapter;

$bots = new CrawlerDetectAdapter(new BotCounter(__DIR__ . '/var/logs/bots.json'));
$bots->record();   // counts only if the request came from a crawler

Reading the numbers

$data = $counter->read();

echo $data['total'];                          // all visits ever counted
echo $data['bots']['googlebot']['last7days']; // rolling window

read() recalculates the windows, so they are accurate even if nothing has been counted for a while.

The file

{
    "updated": "2026-09-11T18:14:41+02:00",
    "total": 60,
    "bots": {
        "googlebot": {
            "total": 40,
            "last7days": 40,
            "last30days": 40,
            "first": "2026-09-11T18:14:37+02:00",
            "last": "2026-09-11T18:14:41+02:00",
            "days": { "2026-09-11": 40 }
        }
    }
}

Bots are sorted by total, busiest first. days holds one bucket per day and is what the windows are summed from — rolling windows cannot be plain counters, because they have to shrink again. Buckets older than the longest window are dropped on write, so the file does not grow over time.

Options

$counter = new BotCounter($file, [
    'windows'     => [1, 7, 30],  // produces last1days, last7days, last30days
    'maxEntries'  => 500,         // beyond this, everything lands under 'other'
    'overflowKey' => 'other',
]);

Notes

Concurrency. Several bots can hit a site in the same moment, so the whole read-modify-write cycle runs under an exclusive lock on a separate .lock file next to the counter. Do not delete that file while the application is running.

An earlier version held a single file handle across the cycle and locked that — it lost counts under load (54 of 60 concurrent requests) because a stream opened before the lock was granted can still serve buffered content afterwards.

Every bot is recalculated on each write, not just the active one. Otherwise a bot that stopped visiting would keep the window values of its last visit forever.

Bot names are treated as input. They usually come from a user agent, so they are lowercased, reduced to [a-z0-9._- ] and capped at 60 characters before becoming a key.

Nothing throws. Every failure — unwritable directory, damaged JSON, full disk — is swallowed and reported through the return value of count(). A damaged file starts over. A counter is never worth a broken page.

It counts requests, not sessions. A crawler fetching ten pages appears ten times.

Caching matters. If a CDN or proxy sits in front of the application, or bots honour your Cache-Control headers, requests never reach PHP and are not counted. The file measures load on your server, which is usually the question anyway.

License

MIT