Mesurer les visites des robots d'IA dans les logs nginx et Apache - grep, awk et GoAccess
Compter 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.
Les outils d'audience fondés sur un script JavaScript ne voient que les visiteurs qui exécutent ce script. Les robots d'IA qui ne l'exécutent pas, comme CCBot selon Common Crawl, n'y apparaissent jamais. Pour savoir qui lit réellement votre site, quelles pages et à quel rythme, la source fiable reste le journal d'accès du serveur web. Ce guide part du format de log standard, donne les commandes grep, awk et GoAccess utiles, puis un script complet qui produit un résumé par robot.
Le format de log à connaître
nginx utilise par défaut le format « combined », qu'Apache fournit aussi sous le même nom. Une ligne ressemble à ceci :
20.171.206.5 - - [24/Oct/2026:10:00:01 +0200] "GET /guide/ HTTP/1.1" 200 5120 "-" "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.4; +https://openai.com/gptbot"
Dans nginx, ce format est prédéfini sous le nom combined et c'est celui qu'utilise access_log quand aucun format n'est précisé :
# Définition intégrée à nginx (inutile de la recopier)
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
Dans Apache, le format porte le même nom ; vérifiez que votre CustomLog l'utilise bien, et non common qui n'enregistre pas le User-Agent :
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" combined
CustomLog ${APACHE_LOG_DIR}/access.log combined
Emplacements habituels : /var/log/nginx/access.log pour nginx, /var/log/apache2/access.log sur Debian et Ubuntu, /var/log/httpd/access_log sur les distributions dérivées de Red Hat. Les journaux plus anciens sont souvent compressés (access.log.2.gz) : zcat -f lit indifféremment les fichiers compressés et non compressés.
Le découpage sur le guillemet est la clé des commandes awk qui suivent. Avec awk -F'"', le champ $2 contient la requête (GET /guide/ HTTP/1.1), $3 le code HTTP et la taille, $6 le User-Agent.
Les robots à rechercher
Les jetons ci-dessous sont ceux que publient les éditeurs. Ils apparaissent dans l'en-tête User-Agent des requêtes, à l'exception de Google-Extended et Applebot-Extended, qui ne sont que des jetons robots.txt : vous ne les verrez jamais dans un journal.
| Famille | Jetons à chercher |
|---|---|
| Entraînement | GPTBot, ClaudeBot, CCBot, meta-externalagent, MistralAI-Training, Amazonbot, Bytespider |
| Recherche et indexation | OAI-SearchBot, Claude-SearchBot, PerplexityBot, Applebot, Amzn-SearchBot, meta-webindexer, MistralAI-Index |
| À la demande d'un utilisateur | ChatGPT-User, Claude-User, Perplexity-User, MistralAI-User, meta-externalfetcher, Amzn-User, Google-Agent |
Les numéros de version changent (GPTBot/1.4 aujourd'hui) : cherchez toujours le jeton, jamais la chaîne complète.
Commandes rapides
LOG=/var/log/nginx/access.log
BOTS='GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-SearchBot|Claude-User|PerplexityBot|Perplexity-User|CCBot|Bytespider|meta-externalagent|meta-externalfetcher|meta-webindexer|Applebot|Amazonbot|Amzn-SearchBot|Amzn-User|MistralAI-User|MistralAI-Index|MistralAI-Training|Google-Agent'
# Nombre total de requêtes de robots d'IA
grep -ciE "$BOTS" "$LOG"
# Requêtes par robot, journaux archivés compris
# (grep -o compterait aussi les URL de documentation contenues dans le User-Agent :
# on ne garde que la première occurrence dans le champ User-Agent ; GNU awk requis)
zcat -f /var/log/nginx/access.log* \
| awk -F'"' -v bots="$BOTS" 'BEGIN { IGNORECASE = 1 } match($6, bots) { print substr($6, RSTART, RLENGTH) }' \
| sort | uniq -c | sort -rn
# Les 20 pages les plus lues par GPTBot (paramètres d'URL retirés)
awk -F'"' 'tolower($6) ~ /gptbot/ { split($2, r, " "); sub(/\?.*/, "", r[2]); print r[2] }' "$LOG" \
| sort | uniq -c | sort -rn | head -20
# Codes HTTP renvoyés à ClaudeBot (repérer les 403, 404 et 5xx)
awk -F'"' '$6 ~ /ClaudeBot/ { split($3, s, " "); print s[1] }' "$LOG" | sort | uniq -c | sort -rn
# Requêtes par jour pour OAI-SearchBot
awk -F'"' '$6 ~ /OAI-SearchBot/ { print substr($1, index($1, "[") + 1, 11) }' "$LOG" | uniq -c
# Qui lit robots.txt, et avec quel code de réponse
awk -F'"' '$2 ~ /^GET \/robots\.txt/ { split($3, s, " "); print s[1], $6 }' "$LOG" \
| grep -iE "$BOTS" | sort | uniq -c | sort -rn
# Volume transféré (octets) par robot
awk -F'"' -v bots="$BOTS" 'BEGIN { IGNORECASE = 1 }
match($6, bots) { split($3, s, " "); o[substr($6, RSTART, RLENGTH)] += s[2] }
END { for (b in o) printf "%12d %s\n", o[b], b }' "$LOG" | sort -rn
Les commandes qui utilisent IGNORECASE exigent GNU awk (gawk). Avec un autre awk, retirez le bloc BEGIN et respectez la casse des jetons.
La commande sur robots.txt est utile pour contrôler vos réglages : un robot qui reçoit une erreur 5xx sur ce fichier doit, selon la RFC 9309, considérer tout le site comme interdit.
Isoler les robots d'IA dans un journal dédié
Plutôt que de filtrer a posteriori, vous pouvez demander au serveur d'écrire ces requêtes dans un fichier séparé.
Avec nginx, dans le bloc http :
map $http_user_agent $robot_ia {
default 0;
"~*(GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-SearchBot|Claude-User|PerplexityBot|Perplexity-User|CCBot|Bytespider|meta-external|meta-webindexer|Amazonbot|Amzn-|MistralAI-|Google-Agent|Applebot)" 1;
}
Puis dans le bloc server :
# access_log dans server remplace les directives héritées : on garde le journal principal.
access_log /var/log/nginx/access.log combined;
access_log /var/log/nginx/robots-ia.log combined if=$robot_ia;
Avec Apache (modules mod_setenvif et mod_log_config) :
SetEnvIfNoCase User-Agent "(GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-SearchBot|Claude-User|PerplexityBot|Perplexity-User|CCBot|Bytespider|meta-external|meta-webindexer|Amazonbot|Amzn-|MistralAI-|Google-Agent|Applebot)" robot_ia
CustomLog ${APACHE_LOG_DIR}/robots-ia.log combined env=robot_ia
Pensez à ajouter ce fichier à la configuration de logrotate, comme les autres journaux du serveur.
GoAccess
GoAccess analyse les journaux au format combined et produit un rapport HTML. Depuis la version 1.12 (septembre 2026), il range les robots d'IA dans une catégorie « AI Crawlers » distincte des moteurs de recherche classiques.
# Rapport complet
goaccess /var/log/nginx/access.log --log-format=COMBINED -o rapport.html
# Rapport limité aux robots d'IA, journaux compressés compris
zcat -f /var/log/nginx/access.log* \
| grep -iE 'GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-SearchBot|Claude-User|PerplexityBot|Perplexity-User|CCBot|Bytespider|meta-external' \
| goaccess - --log-format=COMBINED -o robots-ia.html
Avec une version plus ancienne, déclarez vous-même les jetons dans une liste fournie via --browsers-file (un mot, une ou plusieurs tabulations, puis la catégorie) :
printf '%s\t\tCrawlers\n' GPTBot OAI-SearchBot ChatGPT-User ClaudeBot Claude-SearchBot Claude-User PerplexityBot Perplexity-User > ia.list
goaccess /var/log/nginx/access.log --log-format=COMBINED --browsers-file=ia.list -o rapport.html
Si votre serveur écrit l'hôte virtuel en tête de ligne, utilisez VCOMBINED au lieu de COMBINED.
Script prêt à l'emploi
Ce script lit un ou plusieurs journaux au format combined, compressés ou non, et affiche le nombre de requêtes par robot, les codes HTTP, le nombre d'adresses IP distinctes, l'activité par jour et les pages les plus lues.
#!/usr/bin/env bash
# robots-ia-logs.sh - résumé des visites de robots d'IA (format « combined »)
# Usage : ./robots-ia-logs.sh /var/log/nginx/access.log* (fichiers .gz acceptés)
set -euo pipefail
[ "$#" -ge 1 ] || { echo "usage : $0 fichier_log [fichier_log…]" >&2; exit 1; }
# Jetons recherchés dans le User-Agent (insensible à la casse)
BOTS="GPTBot OAI-SearchBot ChatGPT-User ClaudeBot Claude-SearchBot Claude-User PerplexityBot Perplexity-User Google-Agent Applebot Amazonbot Amzn-SearchBot Amzn-User meta-externalagent meta-externalfetcher meta-webindexer CCBot Bytespider MistralAI-User MistralAI-Index MistralAI-Training"
zcat -f -- "$@" | awk -F'"' -v bots="$BOTS" '
BEGIN { n = split(bots, B, " "); for (i = 1; i <= n; i++) L[i] = tolower(B[i]) }
{
ua = tolower($6); bot = ""
for (i = 1; i <= n; i++) if (index(ua, L[i])) { bot = B[i]; break }
if (bot == "") next
split($2, req, " "); url = req[2]; sub(/\?.*/, "", url) # chemin sans paramètres
split($3, st, " "); code = st[1]
split($1, f, "["); jour = substr(f[2], 1, 11) # 25/Oct/2026
split($1, h, " "); ip = h[1]
hits[bot]++; codes[bot " " code]++; pages[bot "\t" url]++
jours[jour " " bot]++; ips[bot " " ip] = 1
}
END {
print "== Requêtes par robot"
for (b in hits) printf "%8d %s\n", hits[b], b | "sort -rn"; close("sort -rn")
print "\n== Codes HTTP par robot"
for (k in codes) printf "%8d %s\n", codes[k], k | "sort -k2,2 -k1,1rn"; close("sort -k2,2 -k1,1rn")
print "\n== Adresses IP distinctes par robot"
for (k in ips) { split(k, a, " "); nip[a[1]]++ }
for (b in nip) printf "%8d %s\n", nip[b], b | "sort -rn"; close("sort -rn")
print "\n== Requêtes par jour"
for (k in jours) printf "%8d %s\n", jours[k], k | "sort -k2,2 -k1,1rn"; close("sort -k2,2 -k1,1rn")
print "\n== 20 pages les plus lues (tous robots d IA)"
for (k in pages) { split(k, a, "\t"); tot[a[2]] += pages[k] }
for (u in tot) printf "%8d %s\n", tot[u], u | "sort -rn | head -20"; close("sort -rn | head -20")
}'
chmod +x robots-ia-logs.sh
./robots-ia-logs.sh /var/log/nginx/access.log /var/log/nginx/access.log.*.gz
sudo ./robots-ia-logs.sh /var/log/apache2/access.log*
Exemple de sortie sur un petit journal de test :
== Requêtes par robot
3 GPTBot
1 PerplexityBot
1 ClaudeBot
1 Claude-SearchBot
== Codes HTTP par robot
1 Claude-SearchBot 200
1 ClaudeBot 200
1 GPTBot 200
1 GPTBot 403
1 GPTBot 404
1 PerplexityBot 200
Le script compte ce que les requêtes déclarent. Les dates sont triées comme du texte, ce qui convient pour un mois donné ; pour une période plus longue, exportez les données vers un tableur ou une base.
Séparer les résultats robot par robot
Le résumé global mélange des robots aux rôles très différents. Pour comparer ce que lit chaque robot, une boucle suffit : elle produit un classement des pages par robot, ce qui permet de voir si un robot de recherche atteint bien vos contenus importants, ou s'il s'épuise sur des pages de filtres, de pagination ou d'archives.
for bot in GPTBot OAI-SearchBot ClaudeBot Claude-SearchBot PerplexityBot; do
echo "== $bot"
zcat -f /var/log/nginx/access.log* \
| awk -F'"' -v b="$bot" 'index(tolower($6), tolower(b)) { split($2, r, " "); sub(/\?.*/, "", r[2]); print r[2] }' \
| sort | uniq -c | sort -rn | head -10
done
Suivre l'évolution dans le temps
Les journaux sont souvent effacés après quelques semaines par logrotate. Pour garder une trace, enregistrez chaque nuit un compteur par robot dans un fichier CSV, à partir du journal de la veille (ici access.log.1, nom habituel après rotation quotidienne) :
# /etc/cron.d/robots-ia : chaque jour à 00 h 30
30 0 * * * root awk -F'"' 'BEGIN { IGNORECASE = 1; d = strftime("\%Y-\%m-\%d", systime() - 86400) } match($6, /GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-SearchBot|Claude-User|PerplexityBot|Perplexity-User|CCBot/) { n[substr($6, RSTART, RLENGTH)]++ } END { for (b in n) print d ";" b ";" n[b] }' /var/log/nginx/access.log.1 >> /var/log/robots-ia.csv
Dans un fichier cron, le caractère % doit être échappé, d'où les \%. Les fonctions strftime et systime sont, elles aussi, propres à GNU awk. Le fichier obtenu s'ouvre dans n'importe quel tableur : une colonne date, une colonne robot, une colonne nombre de requêtes.
Que regarder en priorité ? Trois signaux suffisent le plus souvent. Les robots de recherche (OAI-SearchBot, Claude-SearchBot, PerplexityBot) doivent apparaître régulièrement si vous les autorisez ; leur absence durable mérite de vérifier robots.txt et le pare-feu. Les agents à la demande (ChatGPT-User, Claude-User, Perplexity-User) montrent quelles pages sont consultées au moment où un utilisateur pose une question. Enfin, un robot que vous avez interdit dans robots.txt et qui continue de lire d'autres pages que /robots.txt doit être vérifié : soit il s'agit d'un imitateur, soit la règle ne le vise pas correctement.
Interpréter les chiffres avec prudence
- Usurpations : un User-Agent se falsifie facilement. Une IP qui se dit GPTBot mais n'appartient pas aux plages d'OpenAI n'est pas GPTBot. Croisez la liste des adresses avec les fichiers JSON officiels, comme expliqué dans le guide Vérifier qu'un robot GPTBot, ClaudeBot ou PerplexityBot est authentique.
- CDN et proxy : derrière un CDN, la première colonne contient l'adresse du proxy. Activez le module
realipde nginx oumod_remoteipd'Apache pour journaliser l'IP du client, ou utilisez les journaux du CDN. - Explorations mutualisées : OpenAI indique qu'une même exploration peut servir à la fois à
GPTBotet àOAI-SearchBotsi les deux sont autorisés. L'absence de l'un ne signifie donc pas que vos pages sont ignorées. - Visites à la demande : une requête
ChatGPT-User,Claude-UserouPerplexity-Usercorrespond en général à la question d'un utilisateur. C'est l'indicateur le plus proche d'une lecture réelle de votre contenu par un assistant, mais il ne dit rien du nombre de personnes qui verront la réponse. - Codes d'erreur : un taux élevé de 403 ou 429 pour un robot de recherche signale souvent un pare-feu ou une limite de débit qui le bloque à votre insu.
Pour vérifier votre site, utilisez le scanner IABIG.
Sources officielles
- nginx, module de journalisation : https://nginx.org/en/docs/http/ngx_http_log_module.html
- nginx, module map : https://nginx.org/en/docs/http/ngx_http_map_module.html
- Apache, mod_log_config : https://httpd.apache.org/docs/2.4/mod/mod_log_config.html
- Apache, mod_setenvif : https://httpd.apache.org/docs/2.4/mod/mod_setenvif.html
- GoAccess, manuel et notes de version 1.12 : https://goaccess.io/man et https://github.com/allinurl/goaccess/blob/master/ChangeLog
- 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
- Common Crawl, FAQ (exécution de JavaScript) : https://commoncrawl.org/faq
- Google, Google-Extended : https://developers.google.com/crawling/docs/crawlers-fetchers/google-common-crawlers
- Google, fetchers déclenchés par l'utilisateur (Google-Agent) : https://developers.google.com/crawling/docs/crawlers-fetchers/google-user-triggered-fetchers
- Apple, About Applebot : https://support.apple.com/en-us/119829
Outils gratuits
Scanner de visibilité IA gratuit
Analysez gratuitement si ChatGPT, Claude, Perplexity et Gemini peuvent lire et citer votre site - robots.txt, llms.txt, JavaScript, texte caché.
Générateur de robots.txt pour les robots d'IA (GPTBot, ClaudeBot, PerplexityBot…)
Créez en un clic un robots.txt qui bloque l'entraînement des IA tout en restant visible dans ChatGPT Search, Claude et Perplexity. Gratuit.
Testeur de robots.txt pour les robots d'IA
Collez votre robots.txt et voyez instantanément, robot par robot, si GPTBot, ClaudeBot, PerplexityBot ou Google-Extended peuvent lire une page.
À 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.
- Vérifier qu'un robot GPTBot, ClaudeBot ou PerplexityBot est authentique - plages IP et DNS inverseConfirmer 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.
- 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.