Your Traffic Chart Is Lying: The Bot Share Nobody Measures
01One line on your chart, three different visitors
Your dashboard shows a single line called visits, and that line quietly averages three groups with nothing in common. Scanners hunting for a file to break. Answer engines reading you so a model can quote you. People.
Traffic is not one number. It is three, and most dashboards average your best readers with your worst.
Why your analytics cannot see the split
Analytics runs in JavaScript. It counts a visitor only if a browser executed a script, so it gets machines wrong in both directions at once. Scanners never run the script, so your numbers look calmer than your server does. Answer engines never run it either, so the most valuable requests you receive appear nowhere in the product you open every morning.
Analytics does not measure traffic. It measures the traffic that runs JavaScript. The access log measures everything, and it is the only file that records who actually asked.
02Ninety percent of it was machines
Last month I audited one of my own products, a crypto tool with a chart that looked healthy. Visits every day, a steady curve, referrals from Google. I had even wired an alert that pinged me on every arrival, and it kept saying the same cheerful thing: new visitor from www.google.com.
I audited a site with a healthy traffic chart. Ninety percent of it was machines pretending to be people.
It was one bot. The same browser string, rotating addresses across seven countries, a Google referrer it had typed in itself, and a session that lasted the same two seconds every time. Real humans on that product were two or three a day. Then the part I still think about. I started requiring eight seconds on the page before a visit counted, and within days the bot was arriving at exactly eight seconds.
A bot that copies a browser is annoying. A bot that adapts to your filter is proof that a visit is a claim, not a fact.
The same test on a site I run, this week
I ran it again while writing this, on a site I run myself, straight from the server log instead of a dashboard. Seven days, 26,919 requests.
15,675 requests, 58 percent of the week, came from clients that declared themselves machines. 6,900 of those sent no user agent at all. 6,545 were curl. Then the number that stings: 11,805 requests, almost 44 percent, were answered with a 404. Of those, 6,840 asked for a .php file on a site that has never served a single line of PHP. That is not an audience. That is people rattling doors.
I tried to measure the browsers next, by counting requests for JavaScript and stylesheets. The count came back at 296 for the week, which looked damning until I read my own server config. Static assets are excluded from the log on purpose, so the only asset lines that survive are the ones nobody has: config.js, secrets.json, env.js. Half of what a log seems to tell you is really telling you how the log was configured.
The part that was not waste
525 of those machine requests were answer engines. ChatGPT fetched pages 384 times, OpenAI's crawlers another 54, Perplexity 49, Anthropic's 38. Those are not noise to be filtered. They are the most valuable requests on the box, because each one is a model reading me in order to answer a person, and that reading shows up later as a citation.
Anyone can claim to be a browser. Only the log remembers what they did next.
The first version of this entry reported a third figure, 4.7 percent, as the share that was plausibly human, and read 296 logged JavaScript requests as browser behaviour. Both came from the log rather than from the truth. Asset requests were switched off in that server config, and every address in the file was a Cloudflare edge node rather than a visitor. The split above now uses only what a request itself proves. Leaving the wrong number up would have been a strange way to make this argument.
03How to separate the three yourself
You do not need a product for this. You need your access log and twenty minutes. Run these and read the ratios, not the totals.
# 1. how many requests admitted they were machines
grep -icE 'bot|crawl|spider|curl|wget|python-requests|Go-http|scrapy|headless' access.log
# 2. requests for files that do not exist
awk '$9==404' access.log | wc -l
# 3. what the scanners were hunting for
awk '$9==404 {print $7}' access.log | sed 's/?.*//' | sort | uniq -c | sort -rn | head
# 4. the answer engines, counted on their own
grep -oiE 'ChatGPT-User|GPTBot|OAI-SearchBot|ClaudeBot|PerplexityBot|Google-Extended' access.log \
| sort | uniq -c | sort -rn
# 5. the shape of the day, hour by hour
awk -F: '{print $2}' access.log | sort | uniq -c
Five tests, cheapest first
Read the declaration. Most machines still say what they are, and an empty user agent is a declaration too. That one line settles half the argument.
Count the 404s. Humans do not ask for wp-login.php on a site with no WordPress. A high miss rate is the cheapest scanner detector ever written and it needs no blocklist to maintain.
Check the asset ratio. A browser takes the page, then the stylesheet, the script and the fonts. A fetcher takes the page and leaves. Two caveats most guides skip. A CDN serves those assets from its own edge, so run this test where the traffic lands. And check whether your server logs assets at all before you conclude anything, because mine did not.
Look at the clock. Human attention has a curve. Scheduled jobs have spikes. Totalled by hour across that week, my busiest hour held 4,591 requests and my quietest held 344, and the peak was not lunchtime. It was a cron.
Name the answer engines and count them apart. ChatGPT, Perplexity and Claude deserve their own line, because they are the only machines whose visits turn into other people's attention.
Check that your log knows who is knocking. If a CDN sits in front of you and your server never restores the original address, every line in your file says the CDN. You can still count what was asked for, but you can no longer tell one visitor from a thousand, or a real ChatGPT fetch from a scanner wearing its name.
What to do with the three numbers
Humans get judged on what they do next. Answer engines get judged on citations, which live in a free Bing report and never in your analytics. Scanners get a firewall rule and nothing else.
Then the audit turned on me. I went to check those 10,371 requests address by address and found that every address in the file belonged to Cloudflare, because this site never told nginx to restore the real one. Seven days of traffic, and my own server could not name a single visitor. You cannot prove a visitor is human. You can only remove everything that provably is not, and be honest about the part your log was never able to see.
None of this makes your traffic bigger. It makes it honest, and an honest number is the only one worth working on, now that the thing being counted has quietly changed.
Tomorrow: the free checker that quietly became a third of a product's search impressions.
>How much of my website traffic is bots?+
>Why does Google Analytics not show bot traffic?+
>How do I tell AI crawlers apart from bad bots?+
Abd Shanti. "Your Traffic Chart Is Lying: The Bot Share Nobody Measures" CITED, Entry 016, Aug 09 2026. unknown.ps/blog/your-traffic-chart-is-lying/
