Open your analytics tool and search for GPTBot. You will find nothing. Not a small number. Nothing. The same is true for ClaudeBot, PerplexityBot, OAI-SearchBot, Google-Extended and every other AI crawler currently reading your site.
This is not a filter you forgot to turn off. It is a consequence of how browser analytics works, and no amount of configuration will change it. Every JavaScript-based analytics tool (Google Analytics, Plausible, Fathom, Matomo's default install, and the Traceten snippet) measures by running code in a browser. AI crawlers do not run a browser. They issue one HTTP GET, take the HTML, and leave. There is nobody home to execute your tracking script.
So the most consequential audience your content has acquired in three years is the one audience your measurement stack cannot see at all.
Why the snippet cannot see it
A browser analytics snippet works in a specific sequence. The browser requests the HTML. It parses it. It finds a <script> tag. It fetches, parses and executes that script. The script reads the page context, builds an event, and sends it to a collector.
An AI crawler's request stops at step one. It wants the HTML (the words) and it has no reason to spend compute rendering the page. Rendering is expensive at scale, and at the scale these crawlers operate the expense is enormous.
Where a crawl and a browser visit diverge
The two paths are identical for exactly one step. Everything a browser analytics tool measures happens after the point where the crawler has already left.
GET /page
Identical for both. Your server returns the same HTML to a crawler and a person.
Parse HTML
Both parse it, but for different reasons: one to render, one to extract text.
Execute JavaScript
The browser runs your snippet here. The crawler does not run this step at all.
Beacon sent
Only the browser reaches this. The crawl leaves no trace in any client-side tool.
A few crawlers do render. Some search-oriented fetchers execute JavaScript for a subset of pages, and the picture will keep shifting as AI search products mature. But designing your measurement around the exceptions is backwards. The dominant behaviour today is fetch-and-leave, and a measurement approach that only works when a bot voluntarily runs your code is not a measurement approach.
The size of what you are missing
The reason this matters is not that crawls are interesting in themselves. It is the ratio.
Cloudflare has published crawl-to-refer ratios: how many pages a given AI operator crawls for every one human visit it sends back. The numbers are not close to balanced, and the spread between operators is enormous.
Pages crawled per human referral sent back
Order-of-magnitude figures from Cloudflare's 2025 reporting on AI crawl and referral traffic. These are point-in-time and have already moved, so check Cloudflare Radar for current values rather than quoting these.
| Operator | Crawl-to-refer ratio | Reading |
|---|---|---|
| Anthropic | Tens of thousands to one | Almost entirely collection; referral traffic is a rounding error |
| OpenAI | Roughly a thousand to one | Heavily one-sided, but a real referral channel exists |
| Roughly ten to one | The closest thing to a conventional search exchange |
Cloudflare, 2025 reporting on AI crawl-to-refer ratios
Whatever the exact figures are on the day you read this, the shape holds: for most AI operators, the overwhelming majority of your relationship with them consists of activity you currently do not log, do not report, and cannot see.
Two consequences follow.
The first is operational. Crawlers consume bandwidth, hit your origin, and can pin an under-provisioned server. If you cannot count them, you cannot capacity-plan for them or reason about the cost.
The second is strategic, and it is the bigger one. A page cannot be cited in an AI answer if it was never fetched. Crawl coverage is upstream of every AI visibility outcome you care about. Watching only the referral end of the funnel means you find out about a content gap weeks after it started costing you, if you find out at all.
The one-line version
Referral data tells you what already worked. Crawl data tells you what is eligible to work. If you only have the first, every diagnosis you make is retrospective.
Where you can actually observe it
There are exactly three places a crawl is visible, and none of them is the browser.
Observation points for AI crawler traffic
Ranked by how much work stands between the raw data and an answer to a real question.
| Observation point | What it gives you | What it costs |
|---|---|---|
| CDN / edge logs | Complete request record including blocked and cached requests the origin never sees | Log export pipeline, storage, and a parser per vendor format |
| Origin server logs | Every request that reached your app, with user agent and IP | Log retention, rotation, and a job to parse and aggregate them |
| Application middleware | A structured event at request time, with routing context already resolved | A dependency in the request path, which must never slow it down |
| Browser snippet | Nothing. Structurally cannot observe a non-rendering client | Not applicable |
Log analysis is the traditional answer, and it works. It is also where this problem goes to die in practice: the logs exist, someone writes a grep, the numbers get pasted into a spreadsheet once, and nobody does it again the following month. Fetching a log export is a task. A dashboard is a habit.
Middleware is the alternative: a small piece of code in your request path that recognises a crawler and reports it, in the same way the snippet reports a browser visit. Done correctly it never blocks the response, never throws, and reports after the response has already been sent.
The three hard parts
Recognising a crawler in a log line looks trivial until you try to answer a question with the result.
Hard part one: the user agent is a claim, not a proof. User-Agent: GPTBot is a string that anyone can send. Cooperative crawlers from major labs identify themselves honestly, which covers most legitimate volume, but any scraper wanting your content can wear the same name, and the moment you make a business decision on crawl data, "someone is impersonating a crawler" stops being hypothetical. Verification means reverse DNS or checking the request IP against the operator's published ranges. Both are real work, and the ranges change.
Hard part two: token boundaries. A naive userAgent.includes("GPTBot") also matches NotReallyGPTBot/1.0 and evil-GPTBot-clone. Match on token boundaries, not substrings.
Hard part three, and the one that ruins reports: not all crawls mean the same thing. A training crawler, a search index crawler and a user-triggered fetch are three different events with three different business meanings, and they arrive over the same HTTP connection wearing the same shape.
Three crawl categories, three meanings
These belong in three different reports. Blending them produces a number that answers no question.
| Category | Example agents | What a hit actually means |
|---|---|---|
| Training | GPTBot, ClaudeBot, CCBot, Google-Extended | Your content may inform a future model. No traffic implication today |
| Search index | OAI-SearchBot, PerplexityBot | You are eligible to be cited in answers now. The leading indicator that matters |
| User-triggered fetch | ChatGPT-User, Claude-User, Perplexity-User | A specific person asked an assistant about this specific page. Live demand |
That third row is the one most teams have never seen and would most want to. A user-triggered fetch is not a bulk crawl. It is one page retrieved because one human asked about it, right then. There is no other place in your stack where that signal appears.
The highest-signal request on your site
If you instrument only one thing, instrument the crawler-facing files: /robots.txt, /llms.txt, and your sitemaps.
These are boring text files that no human ever requests and that most analytics setups exclude by default as noise. They are also the clearest possible evidence of intent. When GPTBot fetches /llms.txt, you have learned that an AI operator went looking for your machine-readable guidance, a fact you can otherwise only speculate about.
Watch your deny-lists
Most bot-logging setups skip .txt and .xml to avoid drowning in asset noise. That default silently discards the single most informative request an AI crawler makes. Whatever you build, carve out an explicit exception for robots, llms and sitemap paths.
What a crawl record needs to contain
Enough to answer a question weeks later, and no more than that. In practice:
- Path, with the query string stripped. Query strings carry session tokens, email addresses and other things that should never enter an analytics store. Strip at the edge, not later.
- Timestamp, so freshness and cadence are computable.
- Declared agent, kept as the raw claim it is.
- Resolved provider and category, decided server-side against a registry so it stays current without a redeploy on your side.
- Verification status: verified by reverse DNS or published range, or unverified. Never a silent boolean that implies proof it does not have.
- Status code, because a crawler receiving a run of 404s or 429s from you is a problem you would otherwise learn about never.
Notice what is not on that list: no visitor identifiers, no cookies, no fingerprints. A crawler is infrastructure, not a person. The privacy questions that make visitor analytics genuinely hard mostly do not apply here, provided you stay disciplined about the query strings.
How to start this week
- Grep one day of logs.
awkover yesterday's access log for the known crawler tokens. You now have a baseline number and, usually, a surprise. - Split it three ways by category. The training/search/user-triggered breakdown will reframe how you read the total.
- Check crawl coverage. Take your twenty most commercially important URLs and check which have been fetched by a search index crawler in the last thirty days. The misses are your priority list.
- Watch
/llms.txtand/robots.txtspecifically. Who asks, and how often. - Then make it continuous. A monthly grep decays. Middleware or a log pipeline that feeds a dashboard does not.
The gap here is not that this data is difficult to obtain. It is sitting in your logs right now. The gap is that nothing in the standard analytics stack was built to look at it, so almost nobody does, which is exactly why the teams that start looking find things their competitors have not.
Frequently asked
Google Analytics measures by executing JavaScript in a browser. AI crawlers like GPTBot request the HTML and never execute the page's scripts, so the tracking code never runs and no event is ever sent. This applies to every client-side analytics tool, not just GA4, and it cannot be fixed with configuration. The only way to observe a crawl is server-side.
Most do not. Bulk training crawlers such as GPTBot, ClaudeBot and CCBot fetch raw HTML because rendering at their scale would be prohibitively expensive. Some search-oriented and user-triggered fetchers render a subset of pages, and behaviour changes over time, but building your measurement on the assumption that a bot will voluntarily run your analytics code is not a reliable strategy.
Server-side. Three options: parse your CDN or edge logs, parse origin access logs, or add middleware in your application's request path that recognises AI crawler user agents and reports them asynchronously after the response is sent. Middleware gives you structured, continuous data without a log pipeline; log parsing avoids adding anything to the request path.
It is adequate for counting cooperative crawlers, which is most legitimate volume, and inadequate as a control you would act on financially or block traffic with. User agent strings are self-declared and trivially forged. Verification requires reverse DNS lookup or matching the request IP against the operator's published IP ranges. Match on token boundaries too: a substring check for 'GPTBot' also matches 'FakeGPTBot'.
The number of pages an AI operator crawls for every one human visitor it sends back to your site. Cloudflare's 2025 reporting put this at roughly ten to one for Google and in the tens of thousands to one for some AI-only operators, though the figures move continuously. A high ratio means the operator is consuming far more content than it is returning traffic for, and it is the clearest single measure of how one-sided the exchange currently is.
Sources & further reading
- 01Overview of OpenAI crawlers and user agents, OpenAI
- 02Does Anthropic crawl data from the web, and how can site owners block the crawler?, Anthropic Support
- 03PerplexityBot and Perplexity-User, Perplexity
- 04AI Insights: crawl-to-refer ratios and AI bot traffic, Cloudflare Radar
- 05Google crawlers and fetchers overview, Google Search Central