Verify AI crawlers against vendor IP ranges, PTR and ASN. Weekly email report for Traefik.
Find a file
2026-08-11 02:41:57 +04:00
README.md first commit 2026-08-11 02:41:57 +04:00

forgejo-crawlers-statistics

Crawler monitoring for the Forgejo instance at git.asxp.io / git.czsk.it. Reads the Traefik access log, mails a threshold alert daily and a summary weekly, and separates real AI crawlers from the ones only claiming to be.

Why IP verification

A user-agent header is self-declared and free to forge. Measured over 2.27M requests (Nov 2025 - Aug 2026), most traffic labelled as an AI crawler did not come from the vendor it named:

Bot Claimed Verified Spoofed
Claude-User 5,000 0 4,992
ChatGPT-User 2,670 13 2,657
PerplexityBot 2,038 0 846
ClaudeBot 4,301 1,070 852
Google-Extended 879 0 747
GPTBot 1,864 766 1,098

The forgeries share infrastructure: a pool of ~100 Google Cloud hosts rotates through six vendor identities, sometimes within the same hour. Their requests target /.env, /terraform.tfstate, /serviceAccountKey.json — credential harvesting wearing a crawler badge. Verified bots, by contrast, made zero robots.txt violations across 2,682 requests and never fetched repository source.

So the report counts a request as legitimate only when the source IP falls inside the vendor's published range list.

Verification sources

Vendor Endpoint
OpenAI openai.com/{chatgpt-user,gptbot,searchbot}.json
Anthropic claude.com/crawling/bots.json
Google developers.google.com/static/crawling/ipranges/*.json
Perplexity perplexity.ai/perplexitybot.json
Apple search.developer.apple.com/applebot.json

Meta, ByteDance, Amazon, Diffbot and Cohere publish no range file; their traffic is reported but cannot be verified by IP.

Ranges are fetched at run time and cached at /var/cache/crawler-alert/ranges.json. A failed fetch falls back to the cache and the mail says so — a vendor outage degrades the report rather than breaking it.

Modes

crawler-alert.py              # daily: mail only if yesterday > 100k requests
crawler-alert.py --weekly     # Monday: 7-day summary, always mails
crawler-alert.py --dry-run    # print instead of mailing

Only --weekly fetches ranges; the daily threshold check stays fast.

Caveats

Cloudflare-proxied requests cannot be verified. For traffic arriving through Cloudflare the logged IP is the CF edge, not the client. Those hits are counted in a separate via CF column rather than being called real or fake. Enabling a real-IP header in Traefik would resolve them.

Perplexity's list is stale (8 prefixes, last updated 2025-02) and Apple's dates to 2023, so some genuine traffic from those two may fail verification.

Install

sudo install -m755 crawler-alert.py /usr/local/bin/
sudo cp crawler-*.{service,timer} /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now crawler-alert.timer crawler-weekly.timer

SMTP credentials are read from Forgejo's own app.ini [mailer] section via docker exec, so there is no second copy to maintain. Recipient and threshold are constants at the top of the script.

Requirements

Python 3.9+ (stdlib only), read access to /opt/traefik/logs/access.log, and docker exec on the forgejo container. Traefik must keep the User-Agent header in its access log:

accessLog:
  filePath: /var/log/traefik/access.log
  fields:
    headers:
      defaultMode: drop
      names:
        User-Agent: keep