GEO-аудит сайта: чек-лист, который превращается в скрипт

Финал серии. К первой статье GEO vs SEO вы, вероятно, пришли с вопросом «ну и что мне конкретно делать с сайтом»; вторая дала словарь метрик (Метрики AI-видимости), третья — технику и приоритет гигиены (llms.txt и краулеры), четвёртая — автоматический счётчик того, что модель реально говорит (Трекинг бренда в LLM). Здесь всё это сводится в один прогон: аудит — это не ещё один дашборд, а чек-лист, который вы прогоняете скриптом и который заканчивается приоритизированным списком «что править первым».

Главный честный тезис всей статьи, и он же — вывод серии: «полезно модели» почти полностью совпадает с «полезно человеку и quality-SEO», но не тождественно ему. Аудит нужен ровно чтобы поймать те несколько пунктов, где они расходятся, — и не потратить вечер на правки, которые моделью всё равно не считываются. Ниже: трёхслойная рамка (доступность → извлекаемость → видимость), готовый PHP-скрипт, который проверяет первые два слоя автоматически и подхватывает третий из снимка четвёртой статьи, формат отчёта и правило приоритизации.

Все числа в примерах — иллюстрации на условных данных, домены условные (example.test); реальные значения зависят от вашей ниши и движков.


Часть I. Рамка: три слоя, и порядок проверок не произволен

GEO-аудит — это не чек-лист «включите schema, включите llms.txt». Это конвейер из трёх слоёв, каждый из которых — условие следующего. Проверять их не по порядку — типичная ошибка: мерить «почему меня не цитируют», когда вас в принципе не ползают.

СлойВопросЧем проверяетсяЭто статья
1. Доступность (crawlability)Доступен ли ваш контент ботам, из которых собирается ответrobots.txt, HTTP-статусы, server-render№3 + скрипт ниже
2. Извлекаемость (extractability)Можно ли из вашей страницы вытащить утверждение и указать источникзаголовки-тезисы, самодостаточные абзацы, schema, числа/даты№3 + №2
3. Видимость (visibility)Что модели в итоге говорят про вас и ссылаются литрекер: 4-way события, prominence, SoV№4

Слой 3 — это то, что вы не можете починить прямо из админки: модель может узнавать о вас из чужих обзоров (Часть IV первой статьи). Но слои 1 и 2 целиком на вашей стороне, и они детерминированы: если retrieval-бот закрыт или текст рендерится за JS, вас физически нет в candidate-пуле, и никакой «креативный контент-план» не поможет. Поэтому аудит и идёт снизу вверх: сначала убедиться, что вы можете быть процитированы, потом — хотят ли вас цитировать.

Формально слои соотносятся так: слой 1 — бинарный (открыт/закрыт), слой 2 — оценочный (насколько страница удобна для извлечения), слой 3 — статистический (доли и интервалы из метрик). Аудит склеивает их в один отчёт: «закрыто» → красный флаг; «плохо извлекается» → жёлтый; «не виден при открытом и извлекаемом» → это уже задача не к редактору, а к third-party-присутствию.

Часть II. Слой 1 — доступность: проверяем, что вас ползают

Здесь почти всё уже разобрано в третьей статье; для аудита важно не пересказывать, а превратить в проверяемые утверждения с бинарным исходом:

  • A1. Отдают ли серверы /robots.txt, /sitemap.xml, /llms.txt (последний — опционален, и отсутствие не красный флаг; красный — когда он есть и вводит в заблуждение).
  • A2. Открыты ли retrieval-боты (OAI-SearchBot, PerplexityBot, Claude-SearchBot и др.) — это главный рубильник: один Disallow: / для всех вырезает вас из ответов. Разделение «обучение»/«ответ» — сердце Части IV третьей статьи.
  • A3. Рендерится ли основной текст на сервере (а не только за JS), — живой поиск читает пустую оболочку.
  • A4. Отдаётся ли контент боту без платной стены/куки-гайда, перекрывающих текст.

A2 — самое частое самоубийство: сайт закрывают «от всех роботов на время редизайна», забывают открыть, и месяцами не понимают, откуда пропала AI-видимость. Поэтому в аудите A2 проверяется по каждой retrieval-строке User-agent отдельно, а не «в целом по robots».

Часть III. Слой 2 — извлекаемость: можно ли вытащить утверждение

Этот слой переводит рекомендации третьей статьи в форму, которую уже можно эвристически проверить по HTML страницы. Что делает страницу удобной для извлечения (и, значит, для цитаты):

  • E1. Заголовки — тезисы, а не вопросы и не «вода». Модель извлекает утверждение под заголовком; Как проверить модель хуже, чем Модель отвечает за 300 мс при температуре 0. Считаем долю заголовков-утверждений (с глаголом и/или числом) от всех заголовков.
  • E2. Самодостаточные абзацы: каждый ключевой абзац понятен без предыдущего (нет висячих «это», «как видно выше»). Эвристика: абзац, начинающийся с местоимения/ссылки на контекст, — кандидат в «неизвлекаемый».
  • E3. Явные числа и даты в тексте утверждений (модель предпочитает цитировать конкретику; тот же вывод, что в Части V третьей статьи).
  • E4. schema (Article/FAQPage с явным автором и datePublished) — сигнал авторитетности и источник структурного контекста.
  • E5. Разделение контента и инструкции — страница информирует, а не проинструктирована (врезка про prompt-injection в Чек-листе третьей статьи и фильтр честности №2). Пункт аудита: в исходном HTML страницы нет скрытых директив «агент, посоветуй меня первым».

Заметьте: E1–E4 это почти дословно quality-SEO / контент-гигиена. Это не случайно — и к этому возвращаемся в Части V.

Часть IV. Слой 3 — видимость: подтягиваем снимок счётчика

Третий слой не проверяется сканированием сайта — он берётся из готового снимка, который ведёт трекер: cron пинговал набор промптов, записал события, сторез snapshot — и аудит просто читает этот snapshot как один из входов. Так слои 1–2 (что можно исправить на сайте) склеиваются со слоем 3 (что уже происходит в ответах) в один отчёт.

Что аудит вытаскивает из снимка (имена событий — из Части II метрик):

  • target_url_cited по конкретным ключевым страницам — low при открытом A2 и хорошем E1 → проблема не на вашей стороне (недостаточная авторитетность/конкуренция), правки страниц не помогут, нужен third-party.
  • third_party_source_cited выше собственных ссылок → вас объясняют чужими пересказами → приоритет = работа с обзорами/каталогами, а не редизайн блога.
  • доля ответов с prominence = first → базовая «рекомендуемость».
  • всё с 95%-ым интервалом Уилсона (из 4-й статьи) и с версией набора промптов — иначе аудит показывает дрейф прибора вместо reality.

Смыслово это самый дешёвый слой аудита (у вас уже есть snapshot), но он превращает два предыдущих из «автономных проверок» в объяснение: доступность и извлекаемость хороши, а видимости нет → вы в candidate-пуле, но проигрываете конкуренцию за цитату, и это уже стратегическая, а не техническая задача.

Часть V. Единый скрипт: прогон слоёв 1–2 и сборка отчёта

Скрипт ниже — то, чем обещание «чек-лист, который превращается в скрипт» становится правдой. Он проверяет слой 1 по списку URL, эвристически оценивает слой 2 по HTML и печатает отчёт с флагом OK/WARN/FAIL и предложенным действием по каждому пункту. Слой 3 подключается строкой require вашего snapshot-хранилища из трекера (там уже есть таблица/опция снимка) — здесь показан интерфейс, а не повтор его кода.

PHP
<?php
// geo_audit.php — аудит доступности+извлекаемости; слой видимости из snapshot трекера.
// Запуск: php geo_audit.php https://example.test /путь/к/снимку.json
// Эвристики грубые: аудит даёт гипотезы и приоритет, не приговор.

$base = $argv[1] ?? 'https://example.test';
$snapPath = $argv[2] ?? null;
$pages = ['', '/blog', '/docs', '/about']; // ключевые шаблоны страниц

function fetch(string $url, int $timeout = 8): array {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_USERAGENT => 'GeoAudit/1.0',
    ]);
    $body = (string) curl_exec($ch);
    $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return [$code, $body];
}

$rows = [];

// --- Слой 1A: отдают ли инфраструктурные пути ---
foreach (['/robots.txt', '/sitemap.xml', '/llms.txt'] as $p) {
    [$code,] = fetch($base . $p);
    $ok = $code === 200;
    $rows[] = ['A1', $p, $ok ? 'OK' : ($code === 404 ? 'WARN' : 'FAIL'),
        $ok ? '' : ($p === '/llms.txt' ? 'факультативно' : 'проверить отдачу')];
}

// --- Слой 1A/1E: retrieval-боты открыты? ---
[$rc, $robots] = fetch($base . '/robots.txt');
$retrieval = ['OAI-SearchBot', 'PerplexityBot', 'Claude-SearchBot'];
foreach ($retrieval as $bot) {
    $blocked = false; $named = false;
    if ($rc === 200 && $robots) {
        foreach (preg_split('/\n\s*\n/', $robots) as $blk) {
            if (preg_match('/user-agent:\s*' . preg_quote($bot, '/') . '\b/i', $blk)) {
                $named = true;
                if (preg_match('/disallow:\s*\/\s*$/im', $blk)) { $blocked = true; }
            }
        }
    }
    $rows[] = ['A2', $bot,
        $blocked ? 'FAIL' : ($named ? 'OK' : 'WARN'),
        $blocked ? 'открыть в robots.txt — вырезает из ответов'
                 : ($named ? '' : 'нет явного блока — проверить микс директив')];
}

// --- Слой 1C/2: текст на сервере + извлекаемость по ключевым страницам ---
foreach ($pages as $pg) {
    [$code, $html] = fetch($base . $pg);
    if ($code !== 200 || $html === '') {
        $rows[] = ['A3', $pg, 'FAIL', 'страница не отдаётся боту'];
        continue;
    }
    // server-render proxy: есть ли substantial text в <body>, а не только каркас
    $textLen = 0;
    if (preg_match('~<body\b[^>]*>(.*?)</body>~is', $html, $m)) {
        $textLen = strlen(trim(strip_tags($m[1])));
    }
    $rendered = $textLen > 800 ? 'OK' : 'FAIL';

    // E1: доля заголовков-тезисов (есть глагол-маркер или число)
    preg_match_all('/<h[1-3][^>]*>(.*?)<\/h[1-3]>/is', $html, $hs);
    $hCount = count($hs[1]); $thesis = 0;
    foreach ($hs[1] as $h) {
        $t = trim(strip_tags($h));
        if ($t === '') continue;
        if (preg_match('/\d/', $t) || preg_match('/\b(есть|являет|работает|делает|стоит|нужно|важен|нравится|составляет|падает|растёт)/iu', $t)) {
            $thesis++;
        }
    }
    $e1 = $hCount ? sprintf('%.0f%%', 100 * $thesis / $hCount) : '—';

    // E4: schema
    $hasSchema = (stripos($html, 'application/ld+json') !== false
        || preg_match('/itemtype=[^>]*Article/i', $html) || preg_match('/FAQPage/i', $html));

    // E5: подозрение на скрытую инструкцию агенту
    $suspectInstruction = (bool) preg_match('/ignore\s+(previous|all)|системн\w+\s+под\w*каз|prompt\s*inject/iu', $html);

    $rows[] = ['A3', $pg, $rendered, $rendered === 'OK' ? '' : 'рендерить текст на сервере'];
    $rows[] = ['E1', $pg, ($hCount && $thesis / $hCount < 0.6) ? 'WARN' : 'OK', "доля тезисов: $e1"];
    $rows[] = ['E4', $pg, $hasSchema ? 'OK' : 'WARN', $hasSchema ? '' : 'добавить Article/FAQPage'];
    $rows[] = ['E5', $pg, $suspectInstruction ? 'FAIL' : 'OK',
        $suspectInstruction ? 'убрать директивы агенту — риск демупа и prompt-injection' : ''];
}

// --- Слой 3: снимок из трекера (интерфейс, не повтор кода) ---
if ($snapPath && is_readable($snapPath)) {
    $snap = json_decode((string) file_get_contents($snapPath), true);
    // ожидаем ключи из geo-treking: url_cited_pct, third_party_pct, prominence_first_pct, n
    if (is_array($snap)) {
        $rows[] = ['S3', 'url_cited', ($snap['url_cited_pct'] ?? 0) < 15 ? 'WARN' : 'OK',
            sprintf('deep-link %s%% из %d ответов', $snap['url_cited_pct'] ?? '?', $snap['n'] ?? 0)];
        $rows[] = ['S3', 'third_party', ($snap['third_party_pct'] ?? 0) > ($snap['url_cited_pct'] ?? 0) ? 'WARN' : 'OK',
            'вас чаще цитируют чужие — см. third-party'];
    }
}

// --- Отчёт, отсортированный по приоритету: FAIL > WARN > OK ---

$rank = ['FAIL' => 0, 'WARN' => 1, 'OK' => 2];
usort($rows, fn($a, $b) => $rank[$a[2]] <=> $rank[$b[2]]);
printf("GEO-аудит %s\n%s\n", $base, str_repeat('=', 60));
foreach ($rows as [$sec, $target, $flag, $note]) {
    printf("%-3s %-22s %-5s %s\n", $sec, $target, $flag, $note);
}
$fail = count(array_filter($rows, fn($r) => $r[2] === 'FAIL'));
$warn = count(array_filter($rows, fn($r) => $r[2] === 'WARN'));
printf("\nИтог: FAIL %d · WARN %d · OK %d. Начинать сверху списка (FAIL по слоям 1–2).\n",
    $fail, $warn, count($rows) - $fail - $warn);

Скрипт нарочито эвристический и честен в этом: реальная оценка server-render и «самодостаточности абзаца» сложнее одной регулярки, как и разбор robots.txt. Это не «приговор сайту», а генератор приоритетного списка гипотез: он за минуту отделяет «вас физически не ползают» (красный A2 — чинится одним редактором robots.txt) от «вас читают, но неудобно извлекать» (жёлтый E1/E4) и от «вас читают и цитируют чужие» (жёлтый S3/third_party — это уже не скрипт правит). Прогоняйте его по своим ключевым шаблонам страниц; pages подставьте реальные пути, а в retrieval добавьте ботов, актуальных на момент прогона (списки меняются — сверяйте по источнику, как в №3).

Как выглядит отчёт (условный вывод, иллюстрация формата):

GEO-аудит https://example.test
============================================================
A2  PerplexityBot          FAIL  открыть в robots.txt — вырезает из ответов
A3  /docs                  FAIL  страница не отдаётся боту
E5  /                      FAIL  убрать директивы агенту — риск демупа и prompt-injection
A1  /sitemap.xml           WARN  проверить отдачу
E1  /blog                  WARN  доля тезисов: 41%
E4  /about                 WARN  добавить Article/FAQPage
S3  third_party            WARN  вас чаще цитируют чужие — см. third-party
A2  OAI-SearchBot          OK
...
Итог: FAIL 3 · WARN 4 · OK 6. Начинать сверху списка (FAIL по слоям 1–2).

Верх списка и есть «что править первым»: сначала FAIL по доступности (дешевле всего, сильнее всего бьёт), потом FAIL по честности (E5), затем WARN по извлекаемости, и только в конце — стратегические WARN слоя 3, которые скриптом не чинятся.

Часть VI. Где GEO совпадает с quality-SEO, а где расходится — и приоритет

Тезис серии, до которого мы дошли: почти весь аудит — это то, что вы и так делаете в хорошем SEO, только теперь это читает ещё и синтезирующий движок. Полезно не «для ИИ», полезно человеку, который через ИИ; механика извлечения просто награждает ровно те качества, что награждали и люди.

Пункт аудитаВ SEOВ GEOРасхождение
Интент, качество, авторитетдато женет
schema Article/FAQPageполезнополезно (структурный контекст)нет
Заголовки-тезисы, самодостаточные абзацыполезно людямкритично для извлеченияусилено
Числа и даты в утвержденияхполезномодель предпочитает конкретикуусилено
Server-render основного текстаполезнообязательно (живой поиск читает HTML)уникально
Разделение retrieval/training-ботовнет аналогаглавный рубильникуникально
Директивы агенту в тексте (E5)неактуальнориск prompt-injection / демупыуникально и опасно

Практический вывод: не нужно осваивать отдельную «GEO-профессию» — аудит в основном обслуживает то, что уже умеет SEO-специалист. Уникальны ровно три вещи: открыть retrieval-ботов, убедиться, что текст рендерится на сервере, и не засовывать в страницу инструкции агента. Всё остальное — quality-SEO, которое теперь ещё и цитируется.

Правило приоритизации: влияние × выполнимость, а не «всё сразу»:

  1. FAIL по A2 (закрыт retrieval-бот) — огромное влияние, правка за минуту. Делать первым, всегда.
  2. FAIL по A3/E5 — влияние высокое, выполнимость средняя (редизайн рендера/очистка текста). Вторым.
  3. WARN по E1/E4 (извлекаемость) — влияние среднее, выполнимость растянутая (переписывать статьи неделями). Идти точечно по топ-страницам, не по всему блогу.
  4. WARN по S3 (видимость/third-party) — часто не решается сайтом вообще; это отдельная стратегическая задача (обзоры, каталоги, упоминания — см. №1), и в тех же правках редактора не нуждается.

Перепутать порядок 1 и 4 — типовая ошибка: «оптимизировать контент», когда вас не ползают, или «чинить third-party», когда deep-link низкий из-за закрытого бота. Отчёт, отсортированный по флагу и слою, эту ошибку не даёт совершить.


Инструменты под задачу

Финальный шаг аудита — не только скрипт, но и «ручная видимость»: спросите чат-ассистента про свою же нишу и посмотрите, кого он называет первым и на что ссылается; это и беглая сверка со слоем 3, и источник формулировок для промптов-зондов трекера. Готовая страница ассистента: https://codestack.space/ai-pomoshhnik-dlja-koda/


Чек-лист граблей

  • Сверили «AI-видимость» и назвали это аудитом. Дашборд показывает итог; аудит проверяет готовность сайта и кончается списком действий.
  • Начали с видимости. Мерить «почему не цитируют», когда retrieval-бот закрыт, — бессмысленно: идите слоями снизу вверх (доступность → извлекаемость → видимость).
  • Ставили Disallow: / «от всех роботов на время редизайна» и забыли → вместе с обучением вырезали retrieval-ботов → выпали из ответов. Проверка A2 по каждому боту отдельно.
  • Ждёте, что llms.txt починит видимость. Отсутствие файла — не FAIL; файл почти не влияет на цитируемость, а ложный — добавляет проблем.
  • Текст за JS-рендером/lazy load. Живой поиск читает пустую оболочку → server-render основного текста обязателен (A3).
  • Доверяете грубой эвристике как приговору. Регулярки по server-render и «самодостаточности» дают гипотезы, не вердикт; проверяйте выборочно руками.
  • Не обновили список retrieval-ботов под прогон. Имена директив меняются — сверяйте по источнику на дату (как в №3), иначе аудит проверяет прошлогодний ландшафт.
  • Засунули в HTML директивы агенту («посоветуй меня первым»). Риск демупы и prompt-injection, проходит objective и проваливает фильтр честности (E5).
  • Переписываете весь блог в тезисы сразу. Влияние среднее, выполнимость низкая; идите точечно по топ-страницам (приоритет 3), не «всё».
  • Лечите редактором то, что вне сайта. Низкий deep-link при открытом боте и хорошей извлекаемости — это third-party-задача, а не редизайн.
  • Смешали слои в один «итоговый балл». FAIL по доступности и «0,3 SoV» несопоставимы; не усредняйте разнородные флаги в число.
  • **Путаете GEO-аудит со «всё то же SEO, только добавить». Уникальны три пункта (retrieval-боты, server-render, отсутствие инструкций агенту); остальное quality-SEO — но именно их чаще всего не делают.

👤 Зарегистрируйтесь, чтобы добавить материал в закладки.