Vérifier qu'un robot GPTBot, ClaudeBot ou PerplexityBot est authentique - plages IP et DNS inverse
Confirmer qu'une requête GPTBot, ClaudeBot ou PerplexityBot vient bien de son éditeur grâce aux plages IP officielles ou au DNS, puis bloquer les imitateurs.
N'importe quel script peut envoyer l'en-tête User-Agent: GPTBot. Dans les journaux d'un site, une partie des visites attribuées à des robots d'IA provient en réalité de collecteurs qui empruntent leur nom pour passer les filtres, ou simplement pour se fondre dans le trafic. Common Crawl le signale d'ailleurs sur sa propre page : des robots se font passer pour CCBot. La seule façon de trancher est de rapprocher l'adresse IP de la requête de ce que publie l'éditeur. Ce guide indique la méthode documentée pour chaque robot, donne des commandes prêtes à l'emploi, puis montre comment bloquer les imitateurs avec nginx et fail2ban.
Quelle méthode pour quel robot
Les éditeurs n'utilisent pas tous la même méthode. La plupart publient un fichier JSON de plages IP ; Google, Apple et Common Crawl documentent en plus la vérification par DNS inverse puis direct.
| Robot | Méthode documentée | Source de vérité |
|---|---|---|
GPTBot |
Plages IP | https://openai.com/gptbot.json |
OAI-SearchBot |
Plages IP | https://openai.com/searchbot.json |
ChatGPT-User |
Plages IP | https://openai.com/chatgpt-user.json |
ClaudeBot, Claude-SearchBot, Claude-User |
Plages IP (une liste unique pour tous les robots Anthropic) | https://claude.com/crawling/bots.json |
PerplexityBot |
Plages IP | https://www.perplexity.com/perplexitybot.json |
Perplexity-User |
Plages IP | https://www.perplexity.com/perplexity-user.json |
Googlebot et fetchers Google |
DNS inverse + direct, ou plages IP | googlebot.com, google.com, googleusercontent.com |
Applebot |
DNS inverse ou plages IP | *.applebot.apple.com, https://search.developer.apple.com/applebot.json |
CCBot |
Plages IP, DNS inverse + direct en IPv4 | *.crawl.commoncrawl.org, https://index.commoncrawl.org/ccbot.json |
OpenAI, Anthropic et Perplexity ne documentent pas de nom d'hôte en DNS inverse : pour ces robots, ne fondez pas une décision sur le DNS, utilisez les fichiers JSON. Leur format est le même partout : un objet prefixes dont chaque élément contient ipv4Prefix ou ipv6Prefix en notation CIDR, accompagné d'un horodatage creationTime.
{
"creationTime": "...",
"prefixes": [
{ "ipv4Prefix": "192.0.2.0/24" },
{ "ipv6Prefix": "2001:db8::/32" }
]
}
Les plages changent : OpenAI a par exemple régénéré chatgpt-user.json le 20 octobre 2026. Téléchargez-les régulièrement plutôt que de les recopier une fois pour toutes.
Extraire les plages avec curl et jq
# Liste des préfixes CIDR publiés pour GPTBot
curl -fsS https://openai.com/gptbot.json | jq -r '.prefixes[] | .ipv4Prefix // .ipv6Prefix'
# Date de génération du fichier
curl -fsS https://claude.com/crawling/bots.json | jq -r '.creationTime'
# Nombre de préfixes par fichier
for u in https://openai.com/gptbot.json \
https://openai.com/searchbot.json \
https://claude.com/crawling/bots.json \
https://www.perplexity.com/perplexitybot.json; do
printf '%5d %s\n' "$(curl -fsS "$u" | jq '.prefixes | length')" "$u"
done
jq ne sait pas tester l'appartenance d'une adresse à un réseau CIDR. Pour cette étape, Python et son module standard ipaddress sont plus fiables qu'une expression régulière.
Tester une adresse IP avec Python
Le script suivant télécharge les listes officielles et indique à quel robot appartient une adresse. Il n'utilise que la bibliothèque standard.
#!/usr/bin/env python3
"""Indique si une adresse IP appartient aux plages publiées d'un robot d'IA."""
import ipaddress
import json
import sys
import urllib.request
SOURCES = {
"GPTBot": "https://openai.com/gptbot.json",
"OAI-SearchBot": "https://openai.com/searchbot.json",
"ChatGPT-User": "https://openai.com/chatgpt-user.json",
"Anthropic (ClaudeBot, Claude-SearchBot, Claude-User)": "https://claude.com/crawling/bots.json",
"PerplexityBot": "https://www.perplexity.com/perplexitybot.json",
"Perplexity-User": "https://www.perplexity.com/perplexity-user.json",
}
def prefixes(url):
req = urllib.request.Request(url, headers={"User-Agent": "verif-ip/1.0"})
with urllib.request.urlopen(req, timeout=15) as resp:
data = json.load(resp)
for p in data.get("prefixes", []):
cidr = p.get("ipv4Prefix") or p.get("ipv6Prefix")
if cidr:
yield ipaddress.ip_network(cidr, strict=False)
def main():
if len(sys.argv) != 2:
sys.exit("usage : verifier_bot.py <adresse IP>")
ip = ipaddress.ip_address(sys.argv[1])
trouve = False
for nom, url in SOURCES.items():
try:
reseaux = list(prefixes(url))
except Exception as exc: # réseau, JSON invalide…
print(f"[erreur] {nom} : {exc}", file=sys.stderr)
continue
for net in reseaux:
if ip.version == net.version and ip in net:
print(f"{ip} appartient à {nom} ({net})")
trouve = True
if not trouve:
print(f"{ip} n'appartient à aucune plage publiée vérifiée")
sys.exit(0 if trouve else 1)
if __name__ == "__main__":
main()
python3 verifier_bot.py 20.171.206.5
# 20.171.206.5 appartient à GPTBot (20.171.206.0/24) (résultat au 25 octobre 2026)
# Passer en revue toutes les IP qui se présentent comme GPTBot dans un journal
grep -i 'GPTBot' /var/log/nginx/access.log | awk '{print $1}' | sort -u |
while read -r ip; do python3 verifier_bot.py "$ip" >/dev/null || echo "SUSPECT $ip"; done
Le code de sortie (0 si l'adresse est trouvée, 1 sinon) permet de l'utiliser dans un script. Pour un gros volume, chargez les listes une seule fois plutôt que de relancer le script pour chaque adresse.
Attention à la granularité : la liste d'Anthropic couvre tous ses robots sans les distinguer. Une IP qui y figure prouve que la requête vient d'Anthropic, pas qu'il s'agit de ClaudeBot plutôt que de Claude-User.
DNS inverse puis direct, quand c'est la méthode documentée
Pour Google, Apple et Common Crawl, la vérification se fait en deux temps : résolution inverse de l'IP vers un nom d'hôte, contrôle du domaine, puis résolution directe de ce nom qui doit redonner la même IP. La seconde étape est indispensable, car le propriétaire d'une adresse IP choisit librement son enregistrement PTR.
# Exemple publié par Google
host 66.249.66.1
# 1.66.249.66.in-addr.arpa domain name pointer crawl-66-249-66-1.googlebot.com.
host crawl-66-249-66-1.googlebot.com
# crawl-66-249-66-1.googlebot.com has address 66.249.66.1
# Apple : le nom doit se terminer par .applebot.apple.com
host 17.58.101.179
# Common Crawl : le nom doit se terminer par .crawl.commoncrawl.org (IPv4 seulement)
dig +short -x 18.97.14.84
dig +short 18-97-14-84.crawl.commoncrawl.org A
La même logique en Python, pour automatiser :
#!/usr/bin/env python3
"""Vérification DNS inverse puis directe (FCrDNS)."""
import ipaddress
import socket
import sys
DOMAINES = {
"googlebot": (".googlebot.com", ".google.com", ".googleusercontent.com"),
"applebot": (".applebot.apple.com",),
"ccbot": (".crawl.commoncrawl.org",),
}
def fcrdns(ip, suffixes):
try:
nom = socket.gethostbyaddr(ip)[0].rstrip(".")
except OSError:
return None, False
if not nom.endswith(suffixes):
return nom, False
try:
adresses = {ipaddress.ip_address(ai[4][0].split("%")[0])
for ai in socket.getaddrinfo(nom, None)}
except OSError:
return nom, False
return nom, ipaddress.ip_address(ip) in adresses
if __name__ == "__main__":
if len(sys.argv) != 3 or sys.argv[1] not in DOMAINES:
sys.exit("usage : fcrdns.py googlebot|applebot|ccbot <adresse IP>")
nom, ok = fcrdns(sys.argv[2], DOMAINES[sys.argv[1]])
print(f"{sys.argv[2]} -> {nom} : {'authentique' if ok else 'NON vérifié'}")
sys.exit(0 if ok else 1)
Le contrôle porte sur le suffixe précédé d'un point : googlebot.com.example.net ou faux-googlebot.com sont rejetés. Google précise que les fetchers déclenchés par un utilisateur résolvent vers google.com ou gae.googleusercontent.com selon les cas ; un nom en googleusercontent.com peut donc correspondre à un service hébergé sur Google Cloud et non à Google lui-même.
Bloquer les imitateurs avec nginx
L'approche la plus robuste consiste à laisser nginx comparer, pour chaque requête, le robot annoncé et l'éditeur propriétaire de l'IP. On génère d'abord un fichier de plages pour le module geo :
#!/usr/bin/env bash
# /usr/local/sbin/maj-ip-robots-ia.sh - à lancer chaque jour (cron ou timer systemd)
set -euo pipefail
sortie=/etc/nginx/robots-ia-ip.conf
tmp=$(mktemp)
ajoute() { # $1 = éditeur, $2 = URL du JSON officiel
curl -fsS "$2" | jq -r --arg v "$1" '.prefixes[] | (.ipv4Prefix // .ipv6Prefix) + " " + $v + ";"' >> "$tmp"
}
ajoute openai https://openai.com/gptbot.json
ajoute openai https://openai.com/searchbot.json
ajoute openai https://openai.com/chatgpt-user.json
ajoute anthropic https://claude.com/crawling/bots.json
ajoute perplexity https://www.perplexity.com/perplexitybot.json
ajoute perplexity https://www.perplexity.com/perplexity-user.json
[ -s "$tmp" ] || { echo "liste vide, abandon" >&2; exit 1; }
sort -u "$tmp" > "$sortie.new" && rm -f "$tmp"
mv "$sortie.new" "$sortie"
nginx -t && systemctl reload nginx
Avec set -e, un téléchargement en échec interrompt le script et l'ancienne liste reste en place. La configuration nginx, dans le bloc http :
# Éditeur propriétaire de l'IP d'après les listes officielles
geo $ip_editeur_ia {
default none;
include /etc/nginx/robots-ia-ip.conf;
}
# Éditeur revendiqué par le User-Agent
map $http_user_agent $ua_editeur_ia {
default "";
"~*GPTBot|OAI-SearchBot|ChatGPT-User" openai;
"~*ClaudeBot|Claude-SearchBot|Claude-User" anthropic;
"~*PerplexityBot|Perplexity-User" perplexity;
}
# 1 = robot annoncé mais IP hors des plages de son éditeur
map "$ua_editeur_ia:$ip_editeur_ia" $robot_usurpe {
default 1;
"~^:" 0; # aucun robot d'IA annoncé
"openai:openai" 0;
"anthropic:anthropic" 0;
"perplexity:perplexity" 0;
}
log_format robots_usurpes '$remote_addr [$time_local] "$request" "$http_user_agent"';
Puis dans le bloc server du site :
# Une directive access_log dans server remplace celles héritées de http :
# on redéclare donc le journal principal.
access_log /var/log/nginx/access.log combined;
access_log /var/log/nginx/robots-usurpes.log robots_usurpes if=$robot_usurpe;
if ($robot_usurpe) {
return 403;
}
Les vrais robots passent, les imitateurs reçoivent une 403 et sont consignés dans un journal dédié. Pour bloquer une adresse précise sans attendre, la directive classique suffit : deny 203.0.113.7; dans le bloc server, ou un fichier inclus regroupant ces lignes.
Si votre site est derrière un CDN ou un proxy, $remote_addr contient l'adresse du proxy. Configurez d'abord le module realip (set_real_ip_from et real_ip_header) pour retrouver l'IP du client, sinon les vrais robots seront classés comme imitateurs.
Bannir les récidivistes avec fail2ban
Le journal robots-usurpes.log ne contient que des requêtes déjà identifiées comme frauduleuses : fail2ban peut s'en servir pour bannir l'IP au niveau du pare-feu, ce qui épargne aussi les autres services de la machine.
# /etc/fail2ban/filter.d/nginx-robots-usurpes.conf
[Definition]
failregex = ^<HOST> \[
ignoreregex =
# /etc/fail2ban/jail.d/nginx-robots-usurpes.local
[nginx-robots-usurpes]
enabled = true
filter = nginx-robots-usurpes
logpath = /var/log/nginx/robots-usurpes.log
port = http,https
maxretry = 3
findtime = 10m
bantime = 1d
fail2ban-regex /var/log/nginx/robots-usurpes.log /etc/fail2ban/filter.d/nginx-robots-usurpes.conf
systemctl reload fail2ban
fail2ban-client status nginx-robots-usurpes
Derrière un CDN, un bannissement au pare-feu de la machine est inopérant, puisque les connexions arrivent depuis le CDN : appliquez alors la règle au niveau du CDN.
Choisir la réponse adaptée
Un imitateur n'est pas forcément malveillant : il peut s'agir d'un outil de test, d'un service de veille ou d'un développeur qui vérifie son propre robots.txt. Graduez la réponse selon le volume et le comportement observés.
| Situation | Réponse proportionnée |
|---|---|
| Quelques requêtes isolées | Les consigner, sans bloquer |
| Exploration soutenue avec un faux User-Agent | Code 403 via nginx, comme ci-dessus |
| Exploration agressive, plusieurs adresses | Bannissement fail2ban, ou règle au niveau du CDN |
| Charge excessive d'un vrai robot | robots.txt (et Crawl-delay pour les robots qui le respectent, comme ClaudeBot et CCBot), puis contact de l'éditeur |
Pour un vrai robot trop insistant, le blocage par IP n'est pas la bonne voie : Anthropic, Amazon et Meta publient une adresse de contact dans leur documentation, et Anthropic demande d'écrire depuis une adresse du domaine concerné pour que la demande soit vérifiable.
Surveillez enfin le journal des imitateurs après chaque mise à jour des listes : une hausse soudaine de « vrais » robots classés comme imitateurs signale presque toujours une liste périmée ou un problème de CDN, pas une attaque.
Ce qu'il ne faut pas faire
- Bloquer les plages IP d'un éditeur pour refuser l'entraînement. Anthropic indique que cette méthode n'est pas fiable, car elle empêche aussi la lecture de robots.txt ; utilisez robots.txt pour exprimer un refus.
- Se fier au DNS inverse pour OpenAI, Anthropic ou Perplexity : aucune de ces documentations ne le prévoit.
- Recopier les plages une fois pour toutes : elles évoluent, et une liste périmée finira par bloquer un vrai robot.
- Déployer sans tester : une expression régulière trop large dans
mappeut classer du trafic légitime comme imitateur. Contrôlez la configuration avecnginx -t, le filtre avecfail2ban-regex, et relisez les premières lignes derobots-usurpes.log.
Pour vérifier votre site, utilisez le scanner IABIG.
Sources officielles
- OpenAI, Overview of OpenAI Crawlers : https://developers.openai.com/api/docs/bots
- Anthropic, Does Anthropic crawl data from the web : https://support.claude.com/en/articles/8896518
- Perplexity, Perplexity Crawlers : https://docs.perplexity.ai/docs/resources/perplexity-crawlers
- Google, Verifying Googlebot and other Google crawlers : https://developers.google.com/crawling/docs/crawlers-fetchers/verify-google-requests
- Apple, About Applebot : https://support.apple.com/en-us/119829
- Common Crawl, CCBot et FAQ : https://commoncrawl.org/ccbot et https://commoncrawl.org/faq
- nginx, modules geo, map, realip et log : https://nginx.org/en/docs/http/ngx_http_geo_module.html, https://nginx.org/en/docs/http/ngx_http_map_module.html, https://nginx.org/en/docs/http/ngx_http_realip_module.html, https://nginx.org/en/docs/http/ngx_http_log_module.html
- fail2ban : https://github.com/fail2ban/fail2ban
À lire aussi
- robots.txt - bloquer l'entraînement des IA et rester visible dans ChatGPT Search, Perplexity et ClaudeUn robots.txt commenté qui refuse GPTBot, ClaudeBot ou CCBot sans quitter ChatGPT Search, Perplexity et Claude, avec les pièges de la RFC 9309 à éviter.
- Mesurer les visites des robots d'IA dans les logs nginx et Apache - grep, awk et GoAccessCompter les visites de GPTBot, ClaudeBot, PerplexityBot et autres robots d'IA dans les logs nginx ou Apache, par robot et par page, avec un script complet.
- Liste des robots d'IA en 2026 - user-agents, jetons robots.txt et rôle de chaque crawlerGPTBot, ClaudeBot, OAI-SearchBot, PerplexityBot, Google-Extended… éditeur, jeton robots.txt, rôle, respect de robots.txt et plages IP officielles.