GrepBoard/매뉴얼
47 편 ·

Nginx 로그에서 AI 크롤러 요청 세기

·GNU awk 5.2.1 · Nginx combined 형식 시험용 로그 · jq 1.7 · 각 사 공식 문서 2026-10 기준·6 단계

GPTBot·ClaudeBot·PerplexityBot 같은 AI 봇이 얼마나 왔는지 User-Agent 로 세고, robots.txt 응답과 공식 IP 목록으로 진짜인지 대조하는 명령입니다.

접속 로그의 마지막 따옴표 칸이 User-Agent 이고, AI 봇은 거기에 이름을 남깁니다. 아래 출력은 2026-10-17 하루치로 직접 만든 combined 로그(197줄, 클라이언트는 문서용 주소 203.0.113.x·198.51.100.x·192.0.2.x)에 돌린 결과입니다. 경로는 access.log 로 줄였고, 운영 서버에서는 보통 /var/log/nginx/access.log 입니다. 상태 코드·경로를 awk 로 세는 기본 방법은 이 글과 같습니다.

[1] 먼저 봇 이름을 정합니다. 공식 문서에 나온 User-Agent 이름은 아래와 같습니다.

회사이름용도(공식 설명)
OpenAIGPTBot생성형 AI 모델 학습에 쓸 수 있는 콘텐츠 수집
OpenAIOAI-SearchBotChatGPT 검색 결과에 사이트 노출
OpenAIChatGPT-User사용자가 시킨 동작(페이지 열기 등) · 자동 크롤링 아님
AnthropicClaudeBot모델 개발용 웹 콘텐츠 수집
AnthropicClaude-User사용자 질문에 답하려고 페이지를 가져옴
AnthropicClaude-SearchBot검색 결과 품질 개선
PerplexityPerplexityBot · Perplexity-User각각 검색 색인 · 사용자 요청

[2] User-Agent 칸에서 이름만 뽑아 셉니다. -F'"' 로 나누면 6번째 칸이 User-Agent 입니다.

console
$ awk -F'"' '{print $6}' access.log | grep -oE '(GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-User|Claude-SearchBot|PerplexityBot|Perplexity-User)' | sort | uniq -c | sort -rn     79 GPTBot     18 ClaudeBot     11 OAI-SearchBot      9 PerplexityBot      7 Claude-SearchBot      5 ChatGPT-User      4 Perplexity-User      4 Claude-User

[3] 그 봇이 무엇을 가져갔는지 상태 코드와 경로로 봅니다. 요청 줄은 2번째 칸, 상태 코드는 3번째 칸의 첫 단어입니다.

console
$ awk -F'"' -v bots='GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-User|Claude-SearchBot|PerplexityBot|Perplexity-User' '$6 ~ bots {split($2,r," "); split($3,s," "); print s[1], r[2]}' access.log | sort | uniq -c | sort -rn | head     82 200 /posts/a/     30 200 /posts/b/     11 200 /robots.txt      9 200 /posts/c/      3 404 /posts/b/      2 404 /llms.txt

/llms.txt 에 404 가 두 번 보입니다. 그 파일을 만들지 않았다는 뜻일 뿐, 봇이 그 파일을 따른다는 증거는 아닙니다. 이 글에서는 효과를 판단하지 않습니다.

[4] /robots.txt 요청만 따로 셉니다. 봇은 보통 글을 가져오기 전에 이 파일을 먼저 읽으므로, 200 이 나오는지 확인합니다.

console
$ awk -F'"' -v bots='GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-User|Claude-SearchBot|PerplexityBot|Perplexity-User' '$6 ~ bots {split($2,r," "); if (r[2]=="/robots.txt") {split($3,s," "); print s[1]}}' access.log | sort | uniq -c     11 200

[5] 같은 이름이 어느 IP 에서 오는지 셉니다.

console
$ awk -F'"' -v bots='GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-User|Claude-SearchBot|PerplexityBot|Perplexity-User' '$6 ~ bots {split($1,a," "); print a[1]}' access.log | sort | uniq -c | sort -rn     54 203.0.113.10     25 198.51.100.77     18 203.0.113.13     11 203.0.113.11      9 203.0.113.14      7 203.0.113.16      5 203.0.113.12      4 203.0.113.17      4 203.0.113.15

GPTBot 이 보낸 79건이 IP 두 개로 나뉩니다(54 + 25). User-Agent 는 누구나 적을 수 있는 문자열이라, 이름만으로는 진짜 봇인지 알 수 없습니다.

[6] 공식 IP 목록과 대조합니다. OpenAI 는 봇마다 JSON 을 공개하고(https://openai.com/gptbot.json · searchbot.json · chatgpt-user.json), Anthropic 은 https://claude.com/crawling/bots.json, Perplexity 는 https://www.perplexity.com/perplexitybot.json 과 perplexity-user.json 을 공개합니다. 내려받은 gptbot.json 은 prefixes 배열에 ipv4Prefix 가 들어 있는 모양이었습니다.

console
$ curl -sL https://openai.com/gptbot.json -o gptbot.json$ jq -r '.prefixes[]|.ipv4Prefix // .ipv6Prefix' gptbot.json > gptbot.cidr$ wc -l gptbot.cidr18 gptbot.cidr

CIDR 포함 여부는 awk 로 하기 번거로워 Python 표준 라이브러리로 확인합니다. 아래 chk.py 는 첫 인자로 CIDR 파일을, 표준입력으로 uniq -c 출력(「개수 IP」 두 칸)을 받습니다.

chk.py
import sys, ipaddressnets=[ipaddress.ip_network(l.strip()) for l in open(sys.argv[1]) if l.strip()]for line in sys.stdin:    count, ip = line.split()    ok=any(ipaddress.ip_address(ip) in n for n in nets)    print(count, ip, 'listed' if ok else 'NOT listed')
console
$ awk -F'"' '$6 ~ /GPTBot/ {split($1,a," "); print a[1]}' access.log | sort | uniq -c | sort -rn | python3 chk.py gptbot.cidr54 203.0.113.10 NOT listed25 198.51.100.77 NOT listed1 20.171.206.5 listed

Nginx 로그를 브라우저에서 바로 분석 ↗ — 아무것도 올라가지 않습니다.

#nginx#awk#ai-crawler#robots-txt