Check your access logs for these user-agent tokens: GPTBot and ClaudeBot (training), OAI-SearchBot, Claude-SearchBot and PerplexityBot (search indexing), and ChatGPT-User, Claude-User and Perplexity-User (fetches a person triggered). Google-Extended never appears in logs, because it is only a robots.txt token. User agents can be faked, so verify the source IPs against each vendor's published ranges.

Key takeaways

  • These are three kinds of bot, not one. Training crawlers collect content for models, search crawlers build an index for AI search, and user-requested fetchers load a page because someone asked about it.
  • A user-requested fetch is the strongest signal. A hit from ChatGPT-User, Claude-User or Perplexity-User means a person asked an assistant about that page.
  • User-requested fetchers may ignore robots.txt. OpenAI says robots.txt rules may not apply to ChatGPT-User, and Perplexity says Perplexity-User generally ignores them, because a user requested the fetch.
  • Google-Extended is not in your logs. Google documents it as a standalone product token with no separate HTTP user agent; crawling is done with existing Google user agents.
  • Verify by IP, not just by name. OpenAI, Anthropic and Perplexity publish IP ranges as JSON, and in a test a GPTBot-labelled request from outside those ranges was correctly flagged.
  • Zero hits proves less than it seems. Static hosts often keep no raw logs, and most retrieval crawlers do not run JavaScript, so look at where your logs come from before concluding no one visits.

The bots, and what each one is for

As documented by each vendor, October 2026. Vendors change these; check the linked pages.
User agent token Operator Purpose robots.txt
GPTBotOpenAICrawls content that may be used to train generative modelsDisallowing signals content should not be used for training
OAI-SearchBotOpenAISurfaces sites in ChatGPT's search featuresOpted-out sites are not shown in ChatGPT search answers, though can still appear as navigational links
ChatGPT-UserOpenAIUser-initiated actions in ChatGPT and custom GPTsRobots.txt rules may not apply; not used to decide search appearance
ClaudeBotAnthropicCollects web content that could help train modelsBlocking signals future content should be excluded from training
Claude-SearchBotAnthropicIndexes content to improve search result qualityBlocking may reduce visibility in user search results
Claude-UserAnthropicRetrieves pages when a user asks Claude a questionBlocking may reduce visibility in user-directed web search
PerplexityBotPerplexitySurfaces and links sites in Perplexity search results; not used for training foundation modelsPerplexity recommends allowing it to appear in results
Perplexity-UserPerplexityVisits a page when a user's question needs itGenerally ignores robots.txt, since a user requested the fetch

The vendor pages are OpenAI's bots documentation, Anthropic's crawler help article and Perplexity's bots guide. Each publishes a list of IP ranges as JSON. One name you will see recommended in blocking guides is missing from the table on purpose: Google-Extended is documented as a standalone product token with no separate HTTP user agent, so it controls how Google may use content but never shows up as a visitor in a log.

Where your logs are

This depends entirely on your hosting. A server you run yourself writes access logs by default. A static host or CDN may keep none, or offer them only through a paid or separate feature, so look at what yours provides before you conclude anything from an empty result. Browser-based analytics cannot see these bots at all, because they do not run your tracking script. And since most retrieval crawlers do not execute JavaScript, as the AI citation guide notes, a page whose content only exists after scripts run can be fetched and still look empty to them.

The script

It reads a standard combined-format access log, counts requests from the eight user agents above, and checks each request's source IP against the vendor's published ranges. Run it with the command below:

python3 ai_bots.py access.log
import re, sys, json, ipaddress, urllib.request, collections

BOTS = {  # user-agent token: (operator, purpose, published IP ranges)
    'GPTBot':          ('OpenAI',     'training crawler',      'https://openai.com/gptbot.json'),
    'OAI-SearchBot':   ('OpenAI',     'search crawler',        'https://openai.com/searchbot.json'),
    'ChatGPT-User':    ('OpenAI',     'user-requested fetch',  'https://openai.com/chatgpt-user.json'),
    'ClaudeBot':       ('Anthropic',  'training crawler',      'https://claude.com/crawling/bots.json'),
    'Claude-SearchBot':('Anthropic',  'search crawler',        'https://claude.com/crawling/bots.json'),
    'Claude-User':     ('Anthropic',  'user-requested fetch',  'https://claude.com/crawling/bots.json'),
    'PerplexityBot':   ('Perplexity', 'search crawler',        'https://www.perplexity.ai/perplexitybot.json'),
    'Perplexity-User': ('Perplexity', 'user-requested fetch',  'https://www.perplexity.ai/perplexity-user.json'),
}

LINE = re.compile(r'^(\S+) .*?"(?:GET|HEAD|POST) (\S+) [^"]*" (\d{3}) .*?"[^"]*" "([^"]*)"\s*$')

ranges = {}
def published(url):
    if url not in ranges:
        req = urllib.request.Request(url, headers={'User-Agent': 'ai-bot-check/1.0'})
        data = json.load(urllib.request.urlopen(req, timeout=20))
        ranges[url] = [ipaddress.ip_network(p.get('ipv4Prefix') or p.get('ipv6Prefix')) for p in data['prefixes']]
    return ranges[url]

def verified(ip, url):
    addr = ipaddress.ip_address(ip)
    return any(addr in net for net in published(url))

hits = collections.defaultdict(lambda: {'n': 0, 'ok': 0, 'paths': collections.Counter(), 'codes': collections.Counter()})
for line in open(sys.argv[1], encoding='utf-8', errors='replace'):
    m = LINE.match(line)
    if not m:
        continue
    ip, path, code, ua = m.groups()
    for token, (_, _, url) in BOTS.items():
        if token.lower() in ua.lower():
            h = hits[token]
            h['n'] += 1; h['paths'][path] += 1; h['codes'][code] += 1
            h['ok'] += verified(ip, url)
            break

if not hits:
    print('No requests from the listed AI user agents.')
for token, h in sorted(hits.items(), key=lambda kv: -kv[1]['n']):
    operator, purpose, _ = BOTS[token]
    print('%-16s %-10s %-22s %4d requests, %d from published IP ranges, %d not' % (token, operator, purpose, h['n'], h['ok'], h['n'] - h['ok']))
    print('    top paths: ' + ', '.join('%s (%d)' % p for p in h['paths'].most_common(3)) + '   status: ' + ', '.join('%s x%d' % c for c in h['codes'].items()))

It was tested on a synthetic eight-line log (not real traffic) containing three GPTBot requests, one of them from an address outside OpenAI's published ranges, plus ChatGPT-User, ClaudeBot and PerplexityBot requests from addresses inside their ranges, a Googlebot line and an ordinary browser line. The output, trimmed to the summary lines:

GPTBot           OpenAI     training crawler          3 requests, 2 from published IP ranges, 1 not
    top paths: /hosting/ (2), /seo/seo-audit-checklist (1)   status: 200 x3
ChatGPT-User     OpenAI     user-requested fetch      1 requests, 1 from published IP ranges, 0 not
ClaudeBot        Anthropic  training crawler          1 requests, 1 from published IP ranges, 0 not
PerplexityBot    Perplexity search crawler            1 requests, 1 from published IP ranges, 0 not

Googlebot and the browser line were correctly ignored, and the spoofed GPTBot request was flagged. One practical detail from building it: one vendor's range file returned a 403 to Python's default user agent, so the script sends its own.

Reading the results

What a pattern in the log usually means. Interpretation, not vendor documentation.
You see It suggests What to do
A user-requested fetcher on a pageSomeone asked an assistant about that page, or a topic it coversTreat it as demand: check the page answers the question in the first screen
Search crawlers visiting regularly, no sign of citationsYou are in the index but not being chosenWork on the page: see the three gates in the AI citation guide
Training crawlers onlyYour content is being collected, not necessarily retrievedDecide your policy; it is separate from search visibility
A bot name from an IP outside the published rangesPossibly a spoofed user agentDo not trust the label; rate-limit or block by IP if it causes load
No hits at allEither no visits, or you cannot see themConfirm your logs are complete and robots.txt is not blocking them first

A page that these bots can fetch still has to be one your own search ranking supports. If your pages are not in Google's index, see Discovered, currently not indexed and Crawled, currently not indexed. The llms.txt study found no measured link between that file and AI citations, so do not read its absence in your logs as a signal either way.

robots.txt: independent settings

The vendors say each token is controlled separately. OpenAI says a site can allow OAI-SearchBot to appear in search while disallowing GPTBot, and Perplexity says each setting works independently. Both say changes can take up to about 24 hours to apply. Anthropic documents blocking ClaudeBot with a plain rule and says it supports the non-standard Crawl-delay extension; it also warns that blocking IP addresses may not reliably opt you out, because it can stop the crawler reading your robots.txt.

User-agent: GPTBot
Disallow: /

User-agent: OAI-SearchBot
Allow: /

That example blocks OpenAI's training crawler while allowing its search crawler. This site's own robots.txt is a single rule that allows every crawler, so none of these bots are blocked.

What this does not cover

Documentation as of October 2026, no log data. Vendors add and rename bots, so the table is a snapshot. The list is not exhaustive: other crawlers exist and some do not identify themselves. The script was tested on a synthetic log, and this page reports no real traffic from this site, because no access logs were available to measure.
After the crawl. What those fetchers see once they arrive depends on your raw HTML: is your content visible without JavaScript?.

Badri Dutta

Software engineer · 8 years technical SEO

Written because the question 'is AI crawling my site?' usually gets an answer about robots.txt, which only says what a bot is asked to do. The logs say what it did, and the vendors publish enough to tell real traffic from a copied name.

Full background →