The Bot Crawls 002: the second look
Three complete days, 22 July to 24 July 2026, the block after the launch week that Bot Crawls 001 measured. Cloudflare's adaptive dataset returned an estimated 8,617 requests. This time one query per day captured everything, so the whole edge is visible: people, search engines, social unfurlers, AI crawlers, and the scanners that hunt every new site for secrets. 510 requests carried user-agent strings associated with AI crawlers and fetchers, 5.9% of the whole.
One word does the same work it did in 001. Cloudflare can verify a crawler through bot detection IDs, and that field was not in this workflow, so every figure here is matched on the user-agent STRING, which anyone can type. So the traffic below is AI-labelled, never "verified". Two things make 002 stricter than 001 on this point, not looser: the query was unrestricted, so nothing was pre-filtered, and it carries no client address and no network at all, so where 001 could show that a burst of probes shared one source, 002 cannot. It can describe behaviour. It cannot count actors.
The window
22 July to 24 July 2026, UTC, three complete days taken as three separate one-day queries because Cloudflare enforced a maximum one-day query window for this zone and dataset configuration. Each query banked its own text beside the response, with an explicit request for a confidence interval on the day's overall count. That is the one thing 001 could not do after the fact, and it is fixed here.
3,640
3,422
1,555
Across the three days: an estimated 8,617 requests, 50.67 MB in and 132.86 MB back, 1,145 visits. The 0.95 intervals belong to each day's overall count and to nothing else: not the byte sums, not the AI subset, not the three-day total, which is a sum of three estimates and carries no combined interval. Every number on this page is an ESTIMATE from an adaptive, sampled dataset, roughly one request in 1.57 to 1.79 sampled and scaled up depending on the day, never a raw server-log tally.
The total is not a measure of interest, and the honest reading needs that said first. 26.6% of all traffic in the window was our own: my audit scanner, the desk's curl, Codex's site audit. It is named in full below and removed from every figure about the outside world. The field excluding our own tooling is 6,327 requests, and that is the sounder base for anything this study says about who came. The single largest class is browser or app traffic, 4,898 requests (56.8%): user agents that present as a browser or app. That may be people, user-driven apps, automation or scanners, so it is not a count of human visitors, and the scanner section finds the two busiest probers wearing exactly these strings.
Everything that came, by what it claims to be
One row per identity class, each user agent placed in exactly one, so the classes add up to the whole. This is what it ASSERTS, not what it is: the behaviour comes next.
| Class | Requests | Share | Distinct agents |
|---|---|---|---|
| first-party | 2,290 | 26.6% | 4 |
| ai-crawler | 510 | 5.9% | 21 |
| search-engine | 353 | 4.1% | 12 |
| social-preview | 248 | 2.9% | 5 |
| seo-research-tool | 97 | 1.1% | 8 |
| automation-library | 33 | 0.4% | 13 |
| browser-or-app | 4,898 | 56.8% | 115 |
| empty-or-minimal-ua | 188 | 2.2% | 3 |
| All traffic | 8,617 | 100% |
What is excluded, and why
| Agent | Requests | Why it is excluded |
|---|---|---|
| TLM-Audit-Scanner | 1,161 | Kish's own site audit scanner |
| curl | 1,007 | The desk checking headers and status codes from the command line |
| CodexSiteAudit | 122 | OpenAI Codex's site audit, run for this project |
| Total excluded | 2,290 | 26.6% of all traffic in the window |
Who came, and what they did
510 requests carried AI-associated labels, 5.9% of the study, or 453 once two edge cases are set aside: Google-Read-Aloud, an accessibility text-to-speech fetcher, and CCBot, Common Crawl, which is not an AI company though its archive feeds AI training. The finding is not the count. It is that the AI-labelled traffic splits cleanly into two behaviours.
| Agent, as its user agent gives it | Inferred operator | Requests | Content fetches | Probes |
|---|---|---|---|---|
| GoogleOther | 235 | 233 | 0 | |
| Amazonbot | Amazon | 93 | 93 | 0 |
| ClaudeBot | Anthropic | 55 | 52 | 2 |
| Google-Read-Aloud (edge) | 46 | 46 | 0 | |
| OAI-SearchBot | OpenAI | 17 | 12 | 2 |
| ChatGPT-User | OpenAI | 16 | 7 | 4 |
| CCBot (edge) | Common Crawl | 11 | 5 | 4 |
| Google-CloudVertexBot | 10 | 3 | 2 | |
| xAI-SearchBot | xAI | 9 | 2 | 3 |
| DeepSeekBot | DeepSeek | 9 | 3 | 3 |
| GPTBot | OpenAI | 4 | 0 | 1 |
| PerplexityBot | Perplexity | 4 | 2 | 2 |
| meta-externalagent | Meta | 1 | 1 | 0 |
The high-volume agents behaved consistently with ordinary content crawling: GoogleOther,
Amazonbot, ClaudeBot and the Read-Aloud fetcher pulled real pages, style sheets, fonts, and the
governance files a crawler is meant to read. The low-volume tail behaves nothing like it: GPTBot, xAI-SearchBot,
Google-CloudVertexBot, DeepSeekBot and some ChatGPT-User requests are mostly 404s on credential
paths. A real training or search crawler does not ask for /.aws/credentials. A UA
that claims to be one and does is behaviour consistent with a probably spoofed label, and that
is the bridge to the next section.
Governance files, and who fetched them
robots.txt, the sitemap and llms.txt, by declared non-first-party agents. Fetching these is ordinary, well-behaved crawling.
| Googlebot | 60 |
| ClaudeBot | 47 |
| LinkedInBot | 37 |
| browser/app | 13 |
| bingbot | 13 |
| OAI-SearchBot | 10 |
| AhrefsBot | 7 |
| facebookexternalhit | 4 |
| BuiltWith | 2 |
| CCBot | 1 |
What the status codes say
| Status | Requests | Share |
|---|---|---|
| 200 | 4,529 | 52.6% |
| 404 | 2,426 | 28.2% |
| 301 | 1,277 | 14.8% |
| 204 | 152 | 1.8% |
| 403 | 137 | 1.6% |
| 405 | 34 | 0.4% |
| 302 | 27 | 0.3% |
| 308 | 18 | 0.2% |
| 304 | 17 | 0.2% |
More than a quarter of all requests, 28.2%, were 404s. That is not all broken links, but nor is it all scanning. External hostile-path probing accounts for 648 of them, almost all 404s; our own security auditing for 844 more; and the rest are ordinary not-founds, paths that never existed or no longer do, which a status code alone cannot call a probe.
The scanners
Set the first-party auditing aside and 648 external requests asked for canonical attack-target paths that were not present in the reviewed site source and returned client errors: credential files, config files, source-control paths, CMS and shell files. Every one is counted only where BOTH hold, a hostile path and a client error, so nothing here rests on a user agent alone.
149
172
327
The sweep grows across the window, 149, then 172, then 327.
And here is the part worth pausing on: the two busiest probers wore plain browser user
agents, no bot label at all. The heaviest asked for 46 distinct attack paths, the next 26. A further 76
requests came with an EMPTY user agent, hunting for PHP shells like /wso.php and
/wp-login.php. Of the whole 648, only
23 carried AI-associated labels, spread
thinly across nine identities
(CCBot, ChatGPT-User, ClaudeBot, DeepSeekBot, GPTBot, Google-CloudVertexBot, OAI-SearchBot, PerplexityBot, xAI-SearchBot), and that set includes CCBot, an archival crawler, not
only AI-company labels. Each is a UA claiming an AI crawler while asking for credentials, which
is behaviour consistent with a spoofed label, and not evidence the named company did
it.
What 002 cannot say, and 001 could. This capture has no client address and no network. In 001 a focused raw-event query could show that most of the launch-day probe events came through one source, which supported, without proving, a single coordinated scanner. Nothing of that kind is possible here. 002 can say the behaviour is consistent with automated scanning and with spoofed labels. It cannot say how many actors there were, or that any two requests came from the same machine. The absence is a property of this evidence, not a finding about the world.
The most-requested hostile paths
/cloud/.env | 21 |
/administrator/phpinfo.php | 21 |
/sendgrid/.env | 21 |
/src/.env | 21 |
/.env.json | 21 |
/scripts/.env | 18 |
/admin/function.php | 12 |
/erp/.env | 12 |
/server/.env | 12 |
/xmlrpc.php | 11 |
/99.php | 10 |
/archive.php | 10 |
/2p.update.php | 10 |
/1.php | 10 |
/wp-load.php | 8 |
All fifteen listed paths returned client errors during the capture. Fourteen returned only 404
responses; /xmlrpc.php returned eight 404 and three 405 responses. No successful
resource response was observed. The capture retained status aggregates, not response bodies.
Our own scanner is in this picture too, and is kept out of the figure above. My audit scanner and the desk's curl asked for the same hostile paths 844 times, auditing my own attack surface. That is a security check of my own site, not an attack on it, so it is reported here and excluded from the 648 external total. A credential-path count read as all-external would be wrong by more than half.
Against the launch week
Both studies were reproduced from their own frozen evidence before any line was drawn between them. 001 is the launch week, 19 to 21 July; 002 is the second three-day block, 22 to 24 July. The 001 column uses 001's frozen primary figures throughout, its 8,492 total included: that window holds 71 hourly buckets, because the 21 July capture covered 23 hours, against 002's 72, and the completed-window replication put 001 at 8,541. Read every row with its caveat: the launch itself is a confound, the classifier and the query design differ between the two, and two three-day windows are not a trend.
| Measure | 001, launch week | 002, second block |
|---|---|---|
| Total request estimate | 8,492 | 8,617 |
| First-party excluded | 1,770 | 2,290 |
| AI-labelled, like-for-like | 585 | 424 |
| AI-labelled byte share, like-for-like | 31.0% | 17.0% |
| ClaudeBot / GPTBot | 239 / 179 | 55 / 4 |
| GoogleOther / Amazonbot | 2 / 46 | 235 / 93 |
| Credential probing | 99, one launch-day hour, all AI-labelled | 648 over 3 days, 23 AI-labelled |
| Claude-SearchBot | 37 | 0 |
What changed
The AI-crawler mix flipped. At launch, Anthropic and OpenAI led: ClaudeBot made 239 requests, GPTBot 179. By the second block, Google and Amazon led: GoogleOther 235, Amazonbot 93, while ClaudeBot fell to 55 and GPTBot to 4. The AI-labelled response-byte share was higher in the launch week, 31.0% against 17.0% under the same classifier, subject to the classifier, window and sampling differences described here. And AI-labelled credential probing fell sharply, 99 to 23, even as total hostile scanning rose and moved to browser and empty-UA disguises.
What persisted, and what stays unknown
The site is still scanned for secrets and shells; that pattern did not go away, it changed costume. Declared crawlers still fetched robots.txt and the sitemap, consistent with ordinary crawling. What stays unknown: who runs the scanners, because there is no source data to cluster them; whether the operator shift is a real trend or the launch surge draining away under sampling noise; and why Claude-SearchBot, present in 001, is absent here, whether it finished its work or simply was not sampled at these low counts. Every one of these is a two-point observation. None is a direction.
Limits, stated rather than buried
- A user agent is a claim, not an identity. Without bot detection IDs nothing here is verified, and this capture has no address or network to corroborate one. Every total is AI-labelled.
- Every count and byte sum is an estimate.
httpRequestsAdaptiveGroupsis an adaptive, sampled dataset. Freezing the responses makes the numbers reproducible; it does not turn them into exact origin-log counts. - The confidence intervals cover one thing. Each day's 0.95 interval is on that day's OVERALL request count. Nothing about operators, agents, paths, statuses, bytes, visits or the three-day total has an interval behind it, and the daily bounds must not be summed.
- No source data. No client IP and no ASN, so no clustering, no geography, and no claim about how many actors sit behind the scanner traffic.
- The total is mostly not the outside world. First-party auditing is 26.6% of it. The field figure, 6,327, is the base for anything said about who came.
- Two windows are not a trend. The comparison with 001 reads differences between two short, adaptive-sampled blocks, one of them launch week. Direction is not claimed.
- requestSource is eyeball, Cloudflare's end-user request-source class. It is not a raw log, does not verify identity, and does not guarantee every Cloudflare-branded service agent is absent.
Evidence and reproducibility
Three frozen GraphQL responses, one per day, their SHA-256 fixed, with the exact query banked
beside each. They live in an immutable evidence archive OUTSIDE this repository and are not
copied into it. site/scripts/derive-bot-crawls-002.mjs reads them, verifies the
checksums, re-derives every figure here, and fails on a broken checksum, a non-null error, an
out-of-window row, a missing hour, a category that will not reconcile, a 25 July row, or a
private field in the output. Same bytes in, same numbers out.
How this was made
Three parties, the same division of labour the series keeps arguing for.
- I set the question, hold every interpretive and ethical decision, and hold final editorial authority over every word.
- OpenAI Codex designed and ran the capture, banked each query beside its response, assembled the checksum-fixed archive, and set the study boundaries.
- Claude Code never touched Cloudflare. It verified the archive, recomputed every figure from the frozen responses, built the classifier and the gates, and wrote this page from the evidence.
Refer to this study
Rees, K. The Bot Crawls 002: the second look. drkishanrees.com. Data captured 22 to 24 July 2026 UTC; published 31 July 2026. https://drkishanrees.com/colophon/bot-crawls-002/