본문으로 건너뛰기
네트워크 보안

AI 크롤러를 차단해야 할까요? 봇별로 결정하고 IP로 집행하십시오

AI 크롤러를 역할별로 차단하십시오. 학습용 봇은 막고, AI 검색 봇은 허용하며, 가짜 GPTBot에는 IP를 확인해 403을 반환합니다. 테스트를 거친 nginx 및 Express 코드를 포함합니다.

작성자GeoIPHub 팀게시일읽는 시간40분 분량
검문 렌즈가 있는 빛의 관문. 노드 하나는 통과하고 하나는 억류되어 있으며, 차단을 결정하기 전에 AI 크롤러의 IP를 확인하는 모습을 나타냅니다

접속 로그에 GPTBot, ClaudeBot, 그리고 처음 보는 이름 몇 개가 보입니다. 찾아본 가이드는 모두 같은 말을 합니다. robots.txt에 User-Agent를 나열하고, 서버 설정에서 같은 문자열을 차단하라는 것입니다.

그 조언에는 구멍이 있습니다. User-Agent는 클라이언트가 직접 입력하는 문자열이고, "GPTBot"이라고 쓰는 데는 비용이 들지 않습니다. 이름 기반 차단 목록은 어차피 robots.txt를 따랐을 정직한 봇만 막고, 허용해 둔 이름을 빌려 쓰는 모든 스크레이퍼는 통과시킵니다. 이름은 주장이고 IP는 증거입니다. IP 기반 봇 탐지 전반에 적용되는 원칙과 같습니다.

아래에서는 봇을 역할별로 분류하고, robots.txt가 할 수 있는 일과 할 수 없는 일을 보여 주며, 실제 크롤러 주소 네 개를 우리 API에 조회해 보고, 테스트를 거친 nginx와 Express 코드로 3단계 정책을 제시합니다. 위조된 GPTBot은 403을 받습니다. 진짜 Googlebot은 절대 받지 않습니다.

반투명한 세 겹의 층을 빛줄기가 통과하고, 휘어지고, 느려지는 모습. robots.txt, 검증된 IP, 속도 제한으로 이루어진 3단계 크롤러 정책을 나타냅니다
세 단계: 정중하게 요청하고, IP를 검증하고, 나머지는 속도를 제한합니다.

AI 크롤러를 차단해야 할까요? 봇의 역할에 따라 다릅니다

반대하는 역할의 AI 크롤러는 차단하고, 독자를 보내 주는 크롤러는 유지해야 합니다. AI 봇은 세 가지 서로 다른 일을 하며, 각각을 차단했을 때 치르는 비용도 다릅니다.

  • 학습용 크롤러는 미래 모델의 학습에 쓰일 수 있는 페이지를 수집합니다. OpenAI는 GPTBot을 허용하지 않으면 "사이트의 콘텐츠가 생성형 AI 파운데이션 모델 학습에 사용되어서는 안 된다는 뜻"이라고 밝힙니다. 이들을 차단해도 검색에서 잃는 것은 없습니다.
  • AI 검색 봇은 어시스턴트가 인용할 수 있도록 페이지를 색인합니다. OpenAI는 OAI-SearchBot에서 제외한 사이트는 "ChatGPT 검색 답변에 표시되지 않는다"고 밝힙니다. 이들을 차단하면 인용과 그에 따른 클릭을 잃습니다.
  • 사용자 요청형 페처는 누군가 어시스턴트에게 읽어 달라고 요청했기 때문에 페이지를 불러옵니다. 이들을 차단하면 그 사람의 요청이 실패합니다.

유료 구독 장벽이 있는 퍼블리셔에게는 학습을 거부할 명확한 이유가 있습니다. 어시스턴트가 자사를 추천해 주기를 원하는 브랜드는 검색 봇을 유지해야 합니다. 대부분의 사이트는 학습은 거부하고, 검색은 허용하고, 페처에는 속도 제한을 두는 식으로 나뉩니다. 어떤 OpenAI 봇을 차단할지와 각 선택의 비용은 별도 글에서 다룹니다. 사용자를 대신해 쇼핑하는 에이전트는 별개의 문제이며 결제 시점의 AI 쇼핑 에이전트 사기에서 다룹니다.

어떤 AI 봇이 학습하고 어떤 봇이 인용하는가

이 표는 가장 자주 보게 될 AI 봇을 역할별로 분류하고, robots.txt에 대해 각 운영자가 말하는 내용을 정리한 것입니다. 모든 셀은 2026년 10월 20일에 연 운영자 자체 페이지에서 가져왔습니다.

봇운영자역할robots.txt, 운영자의 표현
GPTBotOpenAI학습Disallow는 콘텐츠가 "학습에 사용되어서는 안 된다"는 뜻
OAI-SearchBotOpenAIAI 검색제외한 사이트는 ChatGPT 검색 답변에 표시되지 않음
ChatGPT-UserOpenAI사용자 요청형 페치robots.txt 규칙이 "적용되지 않을 수 있음"
ClaudeBotAnthropic학습robots.txt와 Crawl-delay를 준수
Claude-SearchBotAnthropicAI 검색robots.txt와 Crawl-delay를 준수
Claude-UserAnthropic사용자 요청형 페치robots.txt와 Crawl-delay를 준수
Google-ExtendedGoogleAI 사용을 위한 토큰, 크롤러 없음robots.txt 토큰일 뿐, 자체 User-Agent 없음
GooglebotGoogle검색, 차단할 AI 봇이 아님일반 크롤러는 "자동 크롤링 시 항상 robots.txt 규칙을 따름"
PerplexityBotPerplexityAI 검색명시 없음. Perplexity는 허용을 권장
Perplexity-UserPerplexity사용자 요청형 페치"일반적으로 robots.txt 규칙을 무시함"
CCBotCommon Crawl누구나 내려받을 수 있는 오픈 웹 아카이브먼저 robots.txt를 확인하며 Crawl-delay를 준수
Applebot-ExtendedAppleAI 학습 사용을 위한 토큰, 크롤러 없음Apple은 "웹페이지를 크롤링하지 않는다"고 밝힘
Meta-ExternalAgentMeta학습 및 색인명시 없음. Meta는 해당 봇용 Disallow 규칙을 문서화
BytespiderByteDance열어 볼 수 있는 페이지에 문서화되어 있지 않음열어 볼 수 있는 페이지에 문서화되어 있지 않음

증명은 정책과는 별개의 문제입니다. 운영자가 크롤링 출발지를 공개해야만 봇을 검증할 수 있습니다.

운영자범위 파일2026년 10월 20일 기준 프리픽스 수역방향 DNS 문서화 여부GeoIPHub 검증 여부
OpenAIgptbot.json, 그리고 searchbot.json과 chatgpt-user.json18, 39, 234문서화되지 않음예, 세 가지 모두
Anthropicbots.json, 세 봇 모두에 파일 하나38 (파일 날짜 2026년 10월 7일)문서화되지 않음예, 일치하면 ClaudeBot으로 표기
Googlecommon-crawlers.json 외 네 개일반 크롤러에 317예: googlebot.com, google.com, googleusercontent.com예
Perplexityperplexitybot.json과 perplexity-user.json8과 4문서화되지 않음예
Common Crawlccbot.json5예, crawl.commoncrawl.org, IPv4만예
Appleapplebot.json24예, applebot.apple.com예, Applebot에 한함
Meta공개하지 않음공개하지 않음문서화되지 않음ASN 일치만 확인, verified: false
ByteDance찾지 못함찾지 못함문서화되지 않음아니요

다른 사이트의 목록을 복사하기 전에 이름을 확인하십시오. 이 주제로 상위에 노출되는 일부 가이드는 여전히 anthropic-ai와 Claude-Web을 언급하는데, Anthropic의 현재 페이지에는 나와 있지 않으며 Claude-SearchBot과 Claude-User는 빠져 있습니다. 낡은 목록은 아무도 보내지 않는 문자열을 차단합니다.

robots.txt가 할 수 있는 일과 할 수 없는 일

robots.txt는 예의 바른 봇에게 원하는 바를 알려 줄 수 있지만, 아무것도 막지는 못합니다. 이를 정의한 표준인 RFC 9309는 "이 규칙은 접근 권한 부여의 한 형태가 아니다"라고 말합니다. 이를 무시했을 때 법적 결과가 따르는지는 변호사에게 물어볼 문제입니다.

그래도 이것이 첫 번째 단계입니다. 이름이 알려진 대부분의 봇이 이 파일을 읽기 때문입니다. 아래 파일은 학습을 거부하고, AI 검색은 환영하며, Google 검색은 건드리지 않습니다.

GeoIPHub API
# Training: no User-agent: GPTBot User-agent: ClaudeBot User-agent: CCBot User-agent: Google-Extended User-agent: Applebot-Extended User-agent: Meta-ExternalAgent User-agent: Bytespider Disallow: / # AI search: yes User-agent: OAI-SearchBot User-agent: PerplexityBot Allow: / # Anthropic honours Crawl-delay (seconds between requests) User-agent: Claude-SearchBot Allow: / Crawl-delay: 2

이 파일이 생각한 대로 동작하는지는 네 가지 세부 사항이 좌우합니다.

  • Google-Extended와 Applebot-Extended는 크롤러가 아니라 토큰입니다. 2026년 10월 15일에 갱신된 Google의 일반 크롤러 페이지는 "Google-Extended에는 별도의 HTTP 요청 User-Agent 문자열이 없다"고, 그리고 이것이 "사이트의 Google 검색 포함 여부에 영향을 주지 않는다"고 밝힙니다. Google-Extended에서 오는 요청은 존재하지 않으므로 어떤 IP 규칙으로도 차단할 수 없습니다. Apple의 Applebot 페이지도 Applebot-Extended가 "웹페이지를 크롤링하지 않는다"고 밝힙니다.
  • AI 사용을 막으려고 Googlebot을 차단하면 검색에서 사라집니다. Disallow: /가 있는 User-agent: * 그룹도 마찬가지입니다.
  • 사용자 요청형 페처는 이 파일을 무시할 수 있습니다. OpenAI는 ChatGPT-User에 대해 "robots.txt 규칙이 적용되지 않을 수 있다"고 밝힙니다. Perplexity는 Perplexity-User가 "일반적으로 robots.txt 규칙을 무시한다"고 밝힙니다.
  • 변경은 즉시 반영되지 않습니다. OpenAI는 "사이트의 robots.txt 갱신 후 시스템이 조정되기까지 약 24시간이 걸릴 수 있다"고 밝힙니다.

llms.txt나 ai.txt 같은 새로운 파일은 제안일 뿐입니다. robots.txt처럼 요청만 할 뿐 집행하지 않습니다.

반대 방향의 실수는 검색 크롤러를 "AI 봇" 차단 목록에 넣는 것입니다. Contentstack의 AI 크롤러 차단 가이드에는 봇 목록에 gptbot, claudebot과 나란히 googlebot, bingbot이 들어 있고 모두에게 "Forbidden: AI crawlers are not allowed."로 응답하는 엣지 함수가 나옵니다. 그대로 복사하면 사이트가 Google과 Bing 검색에서 사라집니다. Googlebot을 사칭하는 쪽은 가짜 Googlebot이며, 이는 해법이 다른 별개의 문제입니다.

진짜 크롤러인가 위조인가: 검증은 이렇게 동작합니다

접속 IP를 해당 운영자가 공개한 정보와 대조하면 진짜 크롤러와 위조를 구별할 수 있습니다. 세 가지 방법 중 무엇이 통하는지는 운영자에 따라 다릅니다.

  • 범위 파일. OpenAI, Anthropic, Perplexity, Common Crawl, Apple, Google은 자사 프리픽스의 JSON 목록을 공개합니다. OpenAI와 Anthropic은 주소에 역방향 DNS가 없으므로 이것이 유일한 방법입니다.
  • 정방향 확인 역방향 DNS. Google의 검증 가이드는 IP를 이름으로 해석하고, 도메인을 확인한 다음, 그 이름을 다시 해석해 같은 IP가 나오는지 확인하라고 안내합니다. Apple과 Common Crawl도 같은 방법을 문서화합니다.
  • 서명된 요청(Web Bot Auth). 봇이 공개된 키로 각 요청에 서명합니다. 아직 초안 단계이며 아래 엣지 섹션에서 다룹니다.

IP로 AI 크롤러 검증하기 가이드가 세 가지를 모두 자세히 설명합니다. 2026년 10월 20일 04:37 UTC에 키 없이 크롤러 주소 네 개를 조회했습니다. 다음은 ClaudeBot 주소이며, 일부를 생략했지만 중첩 구조와 값은 수정하지 않았습니다.

GeoIPHub API
{ "ip": "216.73.216.10", "asn": { "asn": 16509, "org": "AMAZON-02 - Amazon.com, Inc." }, "threat": { "is_crawler": true, "crawler": { "verified": true, "name": "ClaudeBot", "operator": "Anthropic", "category": "ai", "verification_method": "ip_range_list" } }, "scoring": { "fraud_score": 0, "recommended_action": "allow", "detection_methods": ["datacenter_ip", "no_ptr_datacenter", "verified_crawler"] } }

같은 실행에서 나온 나머지 세 건입니다.

IPcrawler.name / 운영자 / 카테고리검증 여부, 방법점수, 조치detection_methodsASN
132.196.86.1GPTBot / OpenAI / aitrue, ip_range_list0, allowdatacenter_ip, no_ptr_datacenter, verified_crawler8075 MICROSOFT-CORP-MSN-AS-BLOCK
66.249.66.1Googlebot / Google / search_enginetrue, ip_range_list0, allowdatacenter_ip, verified_crawler15169 GOOGLE
107.178.192.1GoogleUserTriggeredFetcher / Google / fetchertrue, ip_range_list0, allowdatacenter_ip, rdns_hosting, verified_crawler396982 GOOGLE-CLOUD-PLATFORM

GPTBot 주소는 OpenAI의 gptbot.json에서 첫 번째 프리픽스인 132.196.86.0/24의 첫 번째 호스트입니다. 그다음 04:45 UTC에 DNS에 이름을 물었습니다.

GeoIPHub API
$ dig -x 216.73.216.10 +short # ClaudeBot: NXDOMAIN, no name $ dig -x 132.196.86.1 +short # GPTBot: NXDOMAIN, no name $ dig -x 66.249.66.1 +short crawl-66-249-66-1.googlebot.com. $ dig crawl-66-249-66-1.googlebot.com +short 66.249.66.1

대부분의 가이드가 놓치는 발견이 바로 이것입니다. ClaudeBot과 GPTBot에는 PTR 레코드가 없으므로 역방향 DNS로는 검증할 수 없습니다. 범위 파일이 유일한 확인 수단입니다. 두 경우 모두 no_ptr_datacenter가 작동하지만 점수는 0으로 유지됩니다. verified_crawler가 면책 신호이기 때문입니다. 이 운영자들에게는 PTR이 없는 것이 정상입니다. Googlebot은 양방향 모두 통과합니다. 이름이 같은 주소로 다시 해석됩니다.

두 가지 한계가 있습니다. category 필드는 학습용과 비학습용 AI 봇 모두에 "ai"라고 표시하므로, 정책 코드는 crawler.name을 기준으로 삼아야 합니다. 그리고 조회는 IP만 볼 뿐 User-Agent는 보지 않으므로 API가 위조를 표시하는 일은 없습니다. 주장한 이름과 증명된 이름의 비교는 사용자의 코드에서 이루어집니다. 같은 확인을 IP 조회 도구에서 직접 해 볼 수도 있습니다.

3단계 정책: 요청하고, 검증하고, 제한한다

견고한 정책은 세 단계로 이루어집니다. robots.txt로 요청하고, 모든 봇 주장을 IP와 대조해 검증하고, 남은 것은 속도를 제한합니다. 역할별로 차단하고, IP로 집행하며, Googlebot을 AI 차단 목록에 절대 넣지 마십시오.

1단계: 요청. robots.txt에 봇별 결정을 담고, 이를 준수하는 운영자에게는 Crawl-delay도 지정합니다.

2단계: 검증. User-Agent가 봇 이름을 내세우면 접속 IP를 조회해 이름을 비교합니다. IP가 그 봇의 것이 아니면 요청은 403을 받습니다. 맞다면 1단계의 결정을 적용하므로, Disallow를 무시하는 검증된 GPTBot도 거부됩니다.

3단계: 제한. 나머지는 모두 속도 제한을 받습니다. 어떤 봇도 주장하지 않는 요청, 검증할 수 없는 봇, 사람을 대신해 동작하는 페처가 여기에 해당합니다.

경우예시조치
허용한 검증된 봇66.249.66.1의 Googlebot정상 제공, 속도 제한 없음
거부했지만 계속 들어오는 검증된 봇GPTBot을 disallow한 상태에서 132.196.86.1의 GPTBot403
검증되지 않은 주장: UA는 봇 이름, IP는 불일치Anthropic 주소인 216.73.216.10에서 온 GPTBot UA403, 원한다면 챌린지
검증할 수 없는 봇Bytespider, Meta-ExternalAgent속도 제한. robots.txt가 유일한 수단
봇 주장 없음브라우저, 브라우저를 사칭하는 스크레이퍼IP별 속도 제한

세 번째 행이 가장 많은 것을 알려 줍니다. 216.73.216.10은 검증된 크롤러이지만 요청이 내세운 크롤러는 아니므로, "검증된 크롤러인가?"만 묻는 확인은 이를 통과시킬 것입니다. 이름을 비교하십시오. 마찬가지로 107.178.192.1은 검증된 Google 페처이지만, 이 주소에서 Googlebot이라고 주장하는 요청은 Googlebot이 아닙니다.

2단계 실패는 모두 기록하십시오. 공개된 범위 밖의 주소는 대개 위조를 뜻하지만, 운영자가 방금 추가한 프리픽스일 수도 있습니다. 우리의 범위 파일은 매일 다시 가져오고 조회 데이터베이스는 매일 재구축하므로, 새 프리픽스가 검증되기까지 최대 이틀 정도 걸릴 수 있습니다.

nginx와 Express로 집행하기, 위조 GPTBot 테스트 포함

집행은 조회를 수행하는 작은 Express 서비스 하나와, 봇을 자처하는 모든 요청에 대해 그 서비스에 묻는 nginx 설정으로 이루어집니다. 기본 nginx는 HTTP API를 인라인으로 호출할 수 없으므로, auth_request 모듈이 검증기에 서브리퀘스트를 보내고 nginx가 응답을 캐시합니다. nginx가 없다면 같은 파일만으로 Express 앱을 보호할 수 있습니다.

검증기는 User-Agent를 현재 봇 토큰과 대조하고, GET https://api.geoiphub.com/v1/lookup/{ip}와 x-api-key 헤더로 IP를 조회하고, 결과를 한 시간 동안 캐시하며, threat.crawler.name을 주장된 이름과 비교합니다. API가 일부 이름을 하이픈 없이 쓰기 때문에 이름 매핑이 필요합니다. OAISearchBot, ChatGPTUser, PerplexityUser가 그렇습니다. API 문서의 크롤러 필드를 참고하십시오.

GeoIPHub API
// bot-gate.js: tier 2 (verify the IP behind a bot claim) and tier 3 (rate-limit the rest). // Tested with Node 24.20.0, Express 5.3.0 and express-rate-limit 8.7.1. const express = require('express'); const { rateLimit } = require('express-rate-limit'); const { BlockList, isIP } = require('node:net'); const API_KEY = process.env.GEOIPHUB_KEY; // production: always set a key const CACHE_MS = 60 * 60 * 1000; // one hour per IP const cache = new Map(); // ip: { at, crawler } // Your policy: User-Agent tokens you refuse even when verified. Everything verified and not listed passes. const BLOCKED = new Set((process.env.BLOCKED_BOTS || 'GPTBot,ClaudeBot,CCBot').split(',')); // Maps each User-Agent token to the crawler.name our API returns for that operator's range file. // Anthropic publishes one file for all three of its bots, so a match comes back as ClaudeBot. const EXPECTED = { 'GPTBot': 'GPTBot', 'OAI-SearchBot': 'OAISearchBot', 'ChatGPT-User': 'ChatGPTUser', 'ClaudeBot': 'ClaudeBot', 'Claude-User': 'ClaudeBot', 'Claude-SearchBot': 'ClaudeBot', 'PerplexityBot': 'PerplexityBot', 'Perplexity-User': 'PerplexityUser', 'CCBot': 'CCBot', 'Googlebot': 'Googlebot', 'bingbot': 'Bingbot', 'Applebot': 'Applebot', }; // Longest tokens first, so "Claude-SearchBot" is not read as a shorter token. const TOKENS = Object.keys(EXPECTED).sort((a, b) => b.length - a.length); // A real crawler never connects from a private or loopback address. const privateNets = new BlockList(); for (const [net, bits] of [['10.0.0.0', 8], ['172.16.0.0', 12], ['192.168.0.0', 16], ['127.0.0.0', 8], ['100.64.0.0', 10]]) privateNets.addSubnet(net, bits, 'ipv4'); privateNets.addSubnet('fc00::', 7, 'ipv6'); privateNets.addAddress('::1', 'ipv6'); function claimedBot(ua = '') { const lower = ua.toLowerCase(); return TOKENS.find((t) => lower.includes(t.toLowerCase())) || null; } async function lookupCrawler(ip) { const hit = cache.get(ip); if (hit && Date.now() - hit.at < CACHE_MS) return hit.crawler; const res = await fetch(`https://api.geoiphub.com/v1/lookup/${ip}`, { headers: API_KEY ? { 'x-api-key': API_KEY } : {}, // keyless only for a local test signal: AbortSignal.timeout(1500), }); if (!res.ok) throw new Error(`lookup ${res.status}`); const data = await res.json(); cache.set(ip, { at: Date.now(), crawler: data.threat.crawler }); return data.threat.crawler; } async function verifyBot(req, res, next) { const claim = claimedBot(req.get('user-agent')); if (!claim) return next(); // no bot claim: tier 3 handles it const ip = req.ip.replace(/^::ffff:/, ''); const family = isIP(ip) === 6 ? 'ipv6' : 'ipv4'; if (privateNets.check(ip, family)) { return res.status(403).send(`unverified ${claim} claim\n`); } let crawler; try { crawler = await lookupCrawler(ip); } catch (err) { // Fail mode: never fail open to "verified". Mark it and let the rate limiter decide. req.botUnverified = true; return next(); } // The API never flags a spoof: it sees only the IP, not the User-Agent. // The spoof check is this comparison, in your code. const verified = crawler.verified === true && crawler.name === EXPECTED[claim]; if (!verified) return res.status(403).send(`unverified ${claim} claim\n`); if (BLOCKED.has(claim)) return res.status(403).send(`${claim} is blocked by policy\n`); req.verifiedBot = crawler.name; return next(); } // Tier 3: everyone without a verified bot name, keyed per IPv4 address or per IPv6 /64. // nginx auth_request understands only 2xx, 401 and 403, so the verifier endpoint answers 403. const limiter = rateLimit({ windowMs: 60 * 1000, limit: 120, ipv6Subnet: 64, skip: (req) => Boolean(req.verifiedBot), handler: (req, res) => res.set('Cache-Control', 'no-store') .status(req.path === '/_verify_bot' ? 403 : 429).send('rate limited\n'), }); const app = express(); app.set('trust proxy', 'loopback'); // only nginx on this host may set X-Forwarded-For // Endpoint for nginx auth_request: 204 lets the request through, 403 stops it. // After a failed lookup, no-store keeps nginx from caching the 204, so the limiter sees every request. app.get('/_verify_bot', verifyBot, limiter, (req, res) => { if (req.botUnverified) res.set('Cache-Control', 'no-store'); res.sendStatus(204); }); // Standalone use without nginx: protect every route. app.use(verifyBot, limiter); app.get('/', (req, res) => res.send('ok\n')); app.listen(3000, '127.0.0.1');

세 가지 선택은 의도적입니다. 조회가 실패하거나 1.5초를 넘기면 요청은 절대 검증된 것으로 취급되지 않고 속도 제한기로 넘어갑니다. nginx 뒤에서는 그 응답이 no-store와 함께 전송되므로 nginx가 캐시하지 않고, 검증기가 모든 요청을 같은 한도에 대해 집계합니다. 정책 목록은 User-Agent 토큰을 담고 있으므로, ClaudeBot을 거부해도 Claude-SearchBot은 거부되지 않습니다. 둘 다 조회 결과로는 ClaudeBot으로 돌아오더라도 그렇습니다. trust proxy는 loopback으로만 설정합니다. 그렇지 않으면 클라이언트가 자신의 X-Forwarded-For를 보내 확인 대상 주소를 직접 고를 수 있기 때문입니다. 그리고 봇 주장이 있는 요청만 조회하므로, 조회량은 페이지뷰가 아니라 봇 트래픽에 비례합니다. 무료 플랜은 키를 사용해 하루 1,500회 조회를 포함하며 모든 필드가 제공됩니다.

nginx 쪽은 User-Agent를 주장된 봇에 매핑하고, 주장이 없으면 서브리퀘스트를 건너뛰며, 검증기의 204 또는 403 응답을 주소와 봇별로 한 시간 동안 캐시합니다. 단, no-store로 표시된 응답은 제외합니다.

GeoIPHub API
# Goes in the http {} block. Tested with nginx 1.30.6 built with --with-http_auth_request_module. # Which bot does the User-Agent claim? Empty means no claim. map $http_user_agent $claimed_bot { default ""; "~*(GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-User|Claude-SearchBot|PerplexityBot|Perplexity-User|CCBot|Googlebot|bingbot|Applebot)" $1; } # Tier 3 key: rate-limit requests with no bot claim. Claims go to tier 2 instead. map $claimed_bot $limit_key { "" $binary_remote_addr; default ""; } limit_req_zone $limit_key zone=perip:10m rate=10r/s; # Cache the verifier's answer per address and claimed bot for one hour. proxy_cache_path /var/cache/nginx/botcheck keys_zone=botcheck:1m max_size=10m inactive=1h; server { listen 8080; location / { limit_req zone=perip burst=20 nodelay; limit_req_status 429; auth_request /_verify_bot; proxy_pass http://127.0.0.1:8000; # your app } location = /_verify_bot { internal; if ($claimed_bot = "") { return 204; } # no claim: no lookup proxy_pass http://127.0.0.1:3000; # bot-gate.js proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Forwarded-For $remote_addr; proxy_cache botcheck; proxy_cache_key $remote_addr$claimed_bot; proxy_cache_valid 204 403 1h; } }

이 설정은 nginx -t를 통과했으며, 2026년 10월 20일 05:05 UTC에 Express 파일 앞단에 두고 실행했습니다. 먼저 같은 머신에서 nginx를 거치는 위조 GPTBot 테스트입니다.

GeoIPHub API
$ curl -s -o /dev/null -w "%{http_code}\n" \ -A "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.4; +https://openai.com/gptbot" \ http://localhost:8080/ 403 $ curl -s -o /dev/null -w "%{http_code}\n" \ -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" \ http://localhost:8080/ 200

이 GPTBot 문자열은 OpenAI의 봇 페이지에 나온 현재 예시입니다. 사용자 자신의 머신에서 보낸 요청은 127.0.0.1에서 오며, 어떤 크롤러도 이 주소를 쓰지 않으므로 검증기는 조회 전에 요청을 거부합니다. 조회 과정을 보려면 nginx가 전달할 주소를 지정해 Express 파일을 직접 호출하십시오.

GeoIPHub API
$ GPT='Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.4; +https://openai.com/gptbot' $ GB='Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' $ curl -s -A "$GPT" -H "X-Forwarded-For: 216.73.216.10" http://127.0.0.1:3000/ unverified GPTBot claim $ curl -s -A "$GPT" -H "X-Forwarded-For: 132.196.86.1" http://127.0.0.1:3000/ GPTBot is blocked by policy $ curl -s -A "$GB" -H "X-Forwarded-For: 107.178.192.1" http://127.0.0.1:3000/ unverified Googlebot claim $ curl -s -A "$GB" -H "X-Forwarded-For: 66.249.66.1" http://127.0.0.1:3000/ ok

첫 줄을 처음부터 끝까지 따라가 보십시오. User-Agent가 GPTBot이라고 주장합니다. 검증기가 216.73.216.10을 조회하자 API는 ClaudeBot이라는 이름의 검증된 크롤러라고 답합니다. ClaudeBot은 GPTBot이 아니므로 요청은 403을 받습니다. 진짜 GPTBot 주소인 132.196.86.1은 검증을 통과하며, 이 정책이 학습용 봇을 거부하기 때문에 거부될 뿐입니다. BLOCKED_BOTS=CCBot으로 설정하면 200을 받았습니다. 216.73.216.10에서 온 Claude-SearchBot 주장은 200을 받았는데, 이 정책이 거부하는 것은 ClaudeBot 토큰이지 Anthropic의 검색 봇이 아니기 때문입니다. 같은 네 개의 주소를 전달한 테스트 전용 nginx 설정 사본은 403, 403, 403, 200을 반환했습니다. API에 의도적으로 접근할 수 없게 만든 상태에서 한 주소의 bingbot 주장 130건을 nginx로 보냈더니 상태 200 응답이 120건 나온 뒤 403이 이어졌습니다. 장애가 발생해도 봇 주장은 그냥 통과되는 대신 속도 제한을 받습니다.

키 없이 테스트한다면 짧게 하십시오. 익명 조회는 분당 제한이 있으며, 60초 창 안의 30번째 호출은 곧바로 429를 받고 IP가 두 시간 동안 차단됩니다.

검증할 수 없는 것은 차단하는 대신 속도를 제한하십시오

속도 제한은 어느 쪽으로도 증명할 수 없는 트래픽에 적합한 도구입니다. 검증할 수 없는 봇, 사용자 요청형 페처, 봇을 자처하지 않는 모든 것이 여기에 해당합니다. 어떤 봇이 범위 파일을 공개하는지 확인하려면 파일별 목록을 보십시오. 속도 제한은 스크레이퍼가 한나절에 사이트를 통째로 복사하는 것을 막으면서도, 페처 뒤에 있는 사람들을 차단하지는 않습니다.

두 코드 조각 모두 이미 이를 수행합니다. nginx의 limit_req는 봇 주장이 없는 요청에 대해 클라이언트 주소를 키로 삼아 초당 10건, 버스트 20으로 제한합니다. 우리 테스트에서는 연속 40건의 요청에 25건이 성공하고 15건이 상태 429로 응답했습니다. 수치를 조정하고, 한 페이지 로드가 수십 개를 가져온다면 정적 자산은 제외하십시오. Express 제한기는 IPv6 클라이언트를 /64 기준으로 키 설정합니다. 가입자 한 명이 보통 /64 전체를 제어하기 때문입니다. /56이나 /48로 넓혀야 하는 시점은 /64 프리픽스로 IPv6 속도 제한하기를 참고하십시오.

Crawl-delay는 이를 준수하는 봇을 위한 정중한 방식입니다. Anthropic의 크롤러 도움말 페이지는 "robots.txt의 비표준 Crawl-delay 확장을 지원한다"고 하고, Common Crawl의 FAQ는 "Crawl-delay 매개변수를 따른다"고 합니다. 그렇지 않은 곳도 있습니다. Google의 robots.txt 사양은 "crawl-delay 같은" 필드는 지원되지 않는다고 하고, Apple은 "Applebot은 crawl-delay를 따르지 않는다"고 합니다. OpenAI와 Perplexity의 페이지에는 언급이 없습니다.

2단계는 봇 이름을 내세우는 요청만 잡아냅니다. 봇을 자처하지 않는 AI 스크레이퍼를 차단하려면 3단계에 행동 신호를 더하십시오. 요청 빈도, 경로, 요청이 출발한 네트워크가 그것입니다. 로그에서 웹 스크레이핑 탐지하기 글에서 이러한 신호를 다룹니다.

엣지 옵션: Cloudflare의 AI 차단과 pay per crawl

Cloudflare를 사용한다면 코드 대신 설정으로 AI 크롤러를 차단하거나 요청당 요금을 부과할 수 있습니다. 어느 쪽도 상대하는 봇이 무엇인지 알아야 할 필요를 없애 주지는 않습니다.

Cloudflare는 2025년 7월 1일에 게시글(2026년 7월 15일 최종 수정)에서 "크리에이터에게 콘텐츠 대가를 지불하지 않는 한 AI 크롤러를 차단하도록 기본값을 변경한다"고 발표했습니다. 자체 규칙도 운영한다면 Cloudflare가 이미 영역에서 무엇을 차단하는지 확인해 두 계층이 일치하도록 하십시오.

pay per crawl에서는 가격이 매겨진 페이지를 요청한 크롤러가 "crawler-price 헤더가 포함된 402 Payment Required 응답"을 받고, 결제 의사를 담아 재시도할 수 있습니다. 크롤러는 Web Bot Auth 제안에 따라 요청에 서명하므로 누구도 다른 사람 행세로 결제할 수 없습니다. Cloudflare는 pay per crawl이 "현재 비공개 베타"라고 밝힙니다.

Web Bot Auth는 아직 표준이 아닙니다. 2026년 10월 20일 기준 IETF webbotauth 워킹그룹에는 2026년 9월 1일자 draft-ietf-webbotauth-httpsig-protocol-00 하나의 워킹그룹 문서가 있을 뿐 RFC는 없으며, 표준 트랙 사양에 대한 헌장상의 2026년 4월 마일스톤은 지나갔습니다. 개인 초안인 draft-illyes-webbotauth-jafar는 크롤러 범위 파일을 위한 공통 JSON 형식을 제안합니다. 이것들이 확정되기 전까지는 IP 범위와 역방향 DNS가 확인할 수 있는 수단입니다.

정책이 무엇을 바꿨는지 측정하기

모든 봇 요청에 검증 결과를 기록한 뒤 전후를 비교해 정책을 측정하십시오.

  • 검증된 봇별 비중. 봇을 자처한 각 요청과 함께 crawler.name과 crawler.verified를 기록합니다. 실패한 주장의 비중이 위조 비율입니다. 이름별 비중은 실제로 어떤 운영자가 크롤링하는지 보여 줍니다.
  • AI 유입. chatgpt.com, perplexity.ai, claude.ai, copilot.microsoft.com, gemini.google.com에서 유입된 방문 수를 집계합니다. 일부 어시스턴트 트래픽은 리퍼러 없이 도착하므로 이 수치는 하한선으로 보십시오.
  • 크롤링 볼륨. 변경 전후로 봇별 일일 요청 수를 비교합니다. Search Console의 크롤링 통계 보고서는 Google 크롤러만 다루므로 다른 봇은 자체 로그에서 가져와야 합니다.

AI 검색 봇을 차단한 뒤 인용이 줄어든다면, 그 봇에 대한 답은 이미 나온 것입니다.

AI 크롤러 정책을 다시 검토해야 하는 시점

운영자가 봇을 추가하거나, 범위 파일을 바꾸거나, 토큰을 폐기할 때마다, 그리고 최소한 분기마다 한 번 AI 크롤러 정책을 다시 검토하십시오. OpenAI의 봇 페이지에는 이제 자체 adsbot.json을 가진 OAI-AdsBot이 나와 있습니다. 우리 크롤러 피드에는 이 파일이 없고 위 코드도 이 이름을 다루지 않으므로, OAI-AdsBot 주장은 사용자가 결정할 때까지 3단계로 넘어갑니다.

이 구성이 할 수 없는 일을 분명히 해 두겠습니다.

  • 범위를 공개하는 봇만 증명할 수 있습니다. Bytespider와 Meta-ExternalAgent는 검증할 수 없고, 둘 중 하나에 대한 위조는 진짜와 똑같아 보입니다.
  • 새 프리픽스는 지연됩니다. 오늘 추가된 프리픽스가 검증되기까지 최대 이틀 정도 걸릴 수 있으므로, 진짜 봇이 잠시 2단계에서 실패할 수 있습니다.
  • IP 차단은 역효과를 낼 수 있습니다. Anthropic은 자사 IP를 차단하면 "robots.txt 파일을 읽을 수 없게 되어 옵트아웃이 올바르게 또는 지속적으로 보장되지 않을 수 있다"고 경고합니다. 2단계가 집행하더라도 robots.txt의 Disallow는 유지하십시오.
  • 조회는 User-Agent를 볼 수 없습니다. 위조 확인은 사용자 코드의 이름 비교입니다.
  • 2단계는 이름을 내세우지 않는 스크레이퍼를 놓칩니다. 그것은 3단계의 몫입니다.

자신의 로그에서 시작하십시오. 이번 주에 GPTBot이나 ClaudeBot이라고 주장한 주소 하나를 골라 조회하고, crawler.name을 그 주소가 주장한 이름과 비교해 보십시오.

자주 묻는 질문

AI 크롤러를 차단해야 합니까?

봇의 역할에 따라 다릅니다. 콘텐츠가 모델 학습에 쓰이는 것에 반대한다면 GPTBot, ClaudeBot 같은 학습용 크롤러를 차단하십시오. 인용되기를 원한다면 OAI-SearchBot 같은 AI 검색 봇은 허용하십시오. 사용자 요청으로 동작하는 페처는 robots.txt에 의존하지 말고 속도를 제한하십시오.

robots.txt가 AI 크롤러를 막아 줍니까?

그것을 따르기로 한 크롤러만 막습니다. RFC 9309는 규칙이 "접근 권한 부여의 한 형태가 아니다"라고 명시합니다. GPTBot User-Agent를 사용하는 스크레이퍼는 이를 무시하므로, 집행은 요청이 도착하는 지점에서 IP를 확인하는 방식이어야 합니다.

AI 크롤러를 차단하면 SEO에 해가 됩니까?

Google-Extended 토큰을 차단해도 Google 검색에는 영향이 없습니다. Google은 사이트의 검색 포함 여부에 "영향을 주지 않는다"고 밝힙니다. AI 사용을 막으려고 Googlebot을 차단하면 검색에서 사라집니다. OAI-SearchBot 같은 AI 검색 봇을 차단하면 해당 어시스턴트의 검색 답변에서 빠집니다.

IP로 Google-Extended를 차단할 수 있습니까?

아니요. Google-Extended는 자체 User-Agent가 없는 robots.txt 토큰입니다. 실제 수집은 Google의 기존 크롤러가 하므로, 유일한 통제 수단은 해당 토큰에 대한 robots.txt 규칙입니다. Applebot-Extended도 같은 방식입니다. Apple은 웹페이지를 크롤링하지 않는다고 밝힙니다.

GPTBot이라고 주장하는 요청이 진짜인지 어떻게 알 수 있습니까?

접속 IP가 OpenAI가 공개한 gptbot.json에 있는지 확인하십시오. 역방향 DNS는 도움이 되지 않습니다. 우리가 확인한 GPTBot과 ClaudeBot 주소에는 PTR 레코드가 없습니다. 조회하면 threat.crawler.verified와 봇 이름이 반환되며, 코드에서 그 이름을 User-Agent와 비교합니다.

모든 AI 크롤러를 IP로 검증할 수 있습니까?

아니요. Bytespider에 대한 ByteDance의 범위 파일은 찾지 못했고, Meta도 Meta-ExternalAgent용으로 공개한 것이 없어서 둘 다 IP로 검증할 수 없습니다. User-Agent를 믿는 대신 속도를 제한하십시오.

작성자

GeoIPHub 팀

GeoIPHub API를 만드는 팀

GeoIPHub IP 인텔리전스 API와 스캐너를 만드는 GeoIPHub 팀이 작성했습니다. 초안은 AI의 도움을 받아 준비하고 팀이 편집합니다. API 예시는 예시용으로 표시된 경우를 제외하고 라이브 API에 대한 실제 조회 결과입니다.

이 글 공유하기
더 많은 글

관련 글