Проверить доступность сайта для ИИ ботов через curl за 5 минут

от автора

Сайт открывается в браузере, отдаёт 200 OK, страница рендерится без единой ошибки. При этом ни ChatGPT, ни Claude, ни Perplexity ни разу не сослались на ваш контент. Первое, на что обычно грешат в этой ситуации, — качество текста или его SEO-разметку. На практике причина чаще лежит на уровень ниже: боты этих систем физически не получают ту страницу, которую видите вы.

Особенно часто это касается проектов на shared-хостинге или за CDN. Антибот-защита там нередко включена по умолчанию — провайдером или хостинг-панелью, а не осознанным решением владельца сайта. Cloudflare, WAF хостинга или модуль защиты от парсинга можно годами не замечать: обычным посетителям и поисковым ботам Google он не мешает, а вот новые краулеры вроде GPTBot или ClaudeBot под раздачу попадают в первую очередь — их либо ещё не занесли в белый список, либо режут по репутации IP-подсети или по самому User-Agent. Итог один: администратор сайта искренне не подозревает, что его контент невидим для ИИ-систем, потому что в браузере и в Google Search Console всё выглядит идеально.

Проверить это на глаз нельзя — нужно посмотреть на сайт глазами конкретного бота. Ниже — рабочий способ сделать это одной командой терминала, список ботов, которых стоит проверять поимённо, и Python-скрипт, автоматизирующий всю проверку целиком.

Ловушка HEAD-запроса

Первое, на чём спотыкается почти любая ручная проверка: curl -I вашего домена.

curl -I https://example.com

Сервер честно отвечает HTTP/2 200. Логичный вывод — «всё открыто». Вывод неверный. -I отправляет HEAD-запрос: сервер возвращает заголовки и не выполняет тело обработки страницы. А фильтрующий скрипт — Cloudflare, WAF, антибот-модуль — чаще всего висит именно на GET, там, где отдаётся тело ответа.

Проверка ничего не стоит, если она сама себя обманывает. Правильный тест — полноценный GET с выводом заголовков и телом:

curl -sS -L -A "Mozilla/5.0 (compatible; GPTBot/1.0; +https://openai.com/gptbot)" \
-D - -o /tmp/response.html https://example.com

Здесь:

  • -A — подмена User-Agent (представляемся конкретным ботом);

  • -L — следование редиректам, иначе легко принять 301 на страницу проверки за целевую страницу;

  • -D — — заголовки ответа в stdout;

  • -sS — тихий режим, но с показом ошибок;

  • -o — тело ответа сохраняется отдельно, чтобы его можно было прогрепать.

Кого проверять поимённо

У каждой ИИ-системы свой краулер, и они ведут себя по-разному — часть сайтов блокирует одних ботов и пропускает других. Проверять «в целом доступность сайта» бессмысленно: нужно пройтись по каждому.

Бот

Кто использует

GPTBot

обучение моделей OpenAI

ChatGPT-User

live-запросы ChatGPT по ссылке пользователя

ClaudeBot

обучение моделей Anthropic

Claude-SearchBot

поиск и цитирование Claude

PerplexityBot

индексация Perplexity

Google-Extended

использование контента в Gemini / AI Overviews

Applebot-Extended

Apple Intelligence

CCBot

Common Crawl — датасет, на котором учится половина моделей

Amazonbot

ассистенты Amazon

bingbot

Bing + Copilot

meta-externalagent

Meta AI

Если сайт закрыл CCBot два года назад «на всякий случай» и забыл — вот и объяснение, почему модели о вас не знают.

Что означает каждый ответ

После прогона GET-запроса с нужным User-Agent результат укладывается в три сценария:

  1. 200 и чистый HTML. Тело ответа содержит реальный контент страницы. Бот видит то же, что и человек. Это цель.

  2. 200, но с проверкой. Статус успешный, а в теле — форма капчи или JS-челлендж Cloudflare. Формально сайт «доступен», фактически бот получает пустышку. Проверяется так:

grep -Ei 'captcha|challenge|cloudflare|verify you are human' /tmp/response.html

Совпадение — сигнал, что WAF отфильтровал именно этого клиента.

  1. 403 или 503. Прямой запрет. Часто — правило файрвола по User-Agent или по подсети, из которой ходит краулер.

Каждый сценарий требует разного исправления: правило в robots.txt не решает проблему №2, а настройка Cloudflare bot-fight mode не решает проблему №3.

Скрипт: автоматизация curl для всех ботов сразу

Ручной прогон одиннадцати команд ради одной проверки не масштабируется. Ниже — скрипт, который оборачивает curl в цикл по всем User-Agent, парсит финальный статус после редиректов и классифицирует ответ.

#!/usr/bin/env python3"""Проверка доступности сайта для AI-ботов: curl с разными User-Agent."""import reimport subprocessimport sysUSER_AGENTS = {    "GPTBot": "Mozilla/5.0 (compatible; GPTBot/1.0; +https://openai.com/gptbot)",    "ChatGPT-User": "Mozilla/5.0 (compatible; ChatGPT-User/1.0; +https://openai.com/bot)",    "ClaudeBot": "Mozilla/5.0 (compatible; ClaudeBot/1.0; +claudebot@anthropic.com)",    "Claude-SearchBot": "Mozilla/5.0 (compatible; Claude-SearchBot/1.0; +claudebot@anthropic.com)",    "PerplexityBot": "Mozilla/5.0 (compatible; PerplexityBot/1.0; +https://perplexity.ai/perplexitybot)",    "Google-Extended": "Mozilla/5.0 (compatible; Google-Extended/1.0)",    "Applebot-Extended": "Mozilla/5.0 (compatible; Applebot-Extended/1.0)",    "CCBot": "CCBot/2.0 (+https://commoncrawl.org/faq/)",    "Amazonbot": "Mozilla/5.0 (compatible; Amazonbot/0.1; +https://developer.amazon.com/support/amazonbot)",    "bingbot": "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)",    "meta-externalagent": "meta-externalagent/1.1",}CHALLENGE_MARKERS = re.compile(r"captcha|challenge|cloudflare|verify you are human", re.I)def fetch(url: str, user_agent: str) -> tuple[int, str]:    """Выполняет полноценный GET через curl и возвращает (финальный статус, тело)."""    result = subprocess.run(        ["curl", "-sS", "-L", "-A", user_agent, "-D", "-", "-o", "-",         "--max-time", "15", url],        capture_output=True, text=True, check=False,    )    # -L может вернуть несколько блоков заголовков (по одному на редирект),    # разделённых пустой строкой — тело всегда в последнем куске.    parts = result.stdout.split("\r\n\r\n")    body = parts[-1]    headers = "\r\n\r\n".join(parts[:-1])    statuses = re.findall(r"HTTP/\S+\s+(\d+)", headers)    status = int(statuses[-1]) if statuses else 0    return status, bodydef classify(status: int, body: str) -> str:    if status in (403, 503):        return "ЗАБЛОКИРОВАН"    if status == 200 and CHALLENGE_MARKERS.search(body):        return "ПРОВЕРКА (WAF/Cloudflare)"    if status == 200:        return "ДОСТУПЕН"    return f"НЕИЗВЕСТНО ({status})"def run_report(url: str) -> None:    print(f"{'Бот':<20} {'HTTP':<6} Результат")    print("-" * 50)    for name, ua in USER_AGENTS.items():        status, body = fetch(url, ua)        print(f"{name:<20} {status:<6} {classify(status, body)}")def _demo() -> None:    """Самопроверка логики классификации без сетевых запросов."""    assert classify(403, "") == "ЗАБЛОКИРОВАН"    assert classify(200, "please verify you are human") == "ПРОВЕРКА (WAF/Cloudflare)"    assert classify(200, "<html>ok</html>") == "ДОСТУПЕН"    assert classify(500, "") == "НЕИЗВЕСТНО (500)"    print("self-check OK\n")if __name__ == "__main__":    _demo()    if len(sys.argv) > 1:        run_report(sys.argv[1])    else:        print("Использование: python check_ai_bots.py https://example.com")

Запуск:

python3 check_ai_bots.py https://example.com

Вывод — таблица из одиннадцати строк: бот, HTTP-статус, вердикт. Ничего лишнего — то, что нужно, чтобы сразу понять, для кого конкретно сайт закрыт.

Скрипт зависит только от установленного curl и стандартной библиотеки Python — ничего ставить не нужно.

Что делать с результатом

  • Статус 403/503 у конкретного бота — ищите правило файрвола или CDN по имени этого User-Agent и снимайте его точечно, не отключая защиту целиком.

  • 200 с челленджем — исключите User-Agent’ы ботов из правил bot-fight/JS-challenge в настройках Cloudflare или WAF.

  • 200 и чисто — сайт для этого бота открыт; если цитирований всё равно нет, причина уже не в доступности, а в структуре и качестве контента.

Если не хочется собирать это руками — весь процесс проверки доступности сайта для ИИ-ботов уже реализован в телеграм-боте @AvigroupAI_bot: можно прислать ссылку и получить готовый отчёт без терминала.

ссылка на оригинал статьи https://habr.com/ru/articles/1074550/