vinou / bot-counter
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+.
Requires
- php: >=5.6
Requires (Dev)
- phpunit/phpunit: >=5.7
Suggests
- jaybizzle/crawler-detect: Enables CrawlerDetectAdapter, which detects and counts a crawler in one call
Provides
None
Conflicts
None
Replaces
None
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 |
|---|---|
total ≈ last7days ≈ last30days |
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