4 сентября 2026 я прогнал свой сайт на пригодность к цитированию ИИ-поисковиками, тогда он был статическим HTML. 27 сентября, через сутки после переезда на WordPress с покупной темой, прогнал ещё раз. Во втором прогоне инструмент вернул Timeout (10s) на всех 25 адресах из карты сайта и средний балл ноль, хотя одиночная страница отдавалась за 0,3-1,8 секунды.
С этого и начну. Сводные баллы по своей шкале у меня тоже есть, 56 и 52, но весов этой шкалы у вас нет, а один её блок я между прогонами правил: как дельту сайта эти два числа читать нельзя, корректный интервал второго замера 48,5-52. Воспроизводимая цифра здесь другая, средний per-page балл инструмента по 25 страницам: 74,4. Она получается командой geo audit --sitemap из следующего раздела.
Масштаб, чтобы дальше было к чему привязываться: 1088 адресов в карте сайта, 501 страница в SEO-обходе, 918 записей блога и 682 из них с перезаписанной датой, 69 дочерних страниц услуг по одному каркасу, 25 адресов в нагрузочном прогоне. В замере цитируемости 9 страниц и 98 текстовых блоков, два процента сайта, и всё дальнейшее справедливо для этих двух процентов.
Чем мерил
geo-optimizer, CLI 4.18.2. Пакет сторонний и ко мне никак не относится: geo-optimizer-skill в PyPI, автор Juan Camilo Auriti, лицензия MIT, репозиторий auriti-labs/geo-optimizer-skill на GitHub. Версию фиксирую, потому что константы и регулярки ниже привязаны именно к 4.18.2, в следующих строки поедут. Установленные файлы лежат в venv утилиты, путь показывает uv tool dir. Вывод английский, хотя --lang по умолчанию it: флаг живёт на верхнем уровне, не у подкоманды, поэтому geo audit --lang en падает с «No such option», а geo --lang en audit --url ... работает.
uv tool install "geo-optimizer-skill[mcp]" geo audit --url https://example.com --format json geo audit --sitemap https://example.com/sitemap.xml --max-urls 25 --concurrency 1 --format json
Балл инструмента считается на страницу, из восьми категорий, сумма ровно 100. Веса в models/config.py, константа CATEGORY_MAX: robots 18, llms 18, schema 16, meta 14, content 12, brand_entity 10, signals 6, ai_discovery 6. Плюс отрицательный negative_penalty, которого в самой константе нет. Прогон главной 30.09.2026:
{ "score": 73, "band": "good", "score_breakdown": { "robots": 15, "llms": 14, "schema": 10, "meta": 14, "content": 12, "signals": 5, "ai_discovery": 0, "brand_entity": 6, "negative_penalty": -3 } }
Отсюда ответ на первый вопрос всех, кто запустит инструмент у себя: почему средний балл по 25 страницам 74,4, а сводный по сайту 52. 74,4 это среднее per-page баллов из разбивки выше. 52 считает моя сводная шкала поверх инструмента, со своими весами, которые я между прогонами ещё и правил. Свести их арифметически нельзя, и это моя недоделка: инструмент считает как заявлено. Сопоставимая для читателя цифра per-page.
И ни одна из восьми категорий не считает нарезку страницы на фрагменты: rag_chunk и context_window инструмент печатает, но в 100 баллов они не входят. Поэтому главная набирает 73 при нулевой пригодности к цитированию по нарезке.
«Timeout (10s)» это не таймаут одного запроса
Прогон по карте сайта при --concurrency 5 вернул 25 отказов Timeout (10s) из 25 адресов и средний балл ноль. Тот же прогон при --concurrency 1 прошёл целиком, средний 74,4. Десять секунд отведены не на один запрос, а на весь аудит одного адреса:
# core/batch_audit.py, сокращено semaphore = asyncio.Semaphore(concurrency) async def _worker(url): async with semaphore: try: return await asyncio.wait_for(_audit_single_url(url, ...), timeout=AUDIT_TIMEOUT_SECONDS) # = 10 except asyncio.TimeoutError: return ... error=f"Timeout ({AUDIT_TIMEOUT_SECONDS}s)"
Сборку результата из врезки я выкинул, важны семафор, wait_for и источник строки в логе.
Внутри _audit_single_url не один запрос, а пачка: страница, robots.txt, llms.txt, llms-full.txt, /.well-known/ai.txt, /ai/summary.json, /ai/faq.json, /ai/service.json, проверка блокировки ботов, которая тянет ту же страницу ещё шесть раз под шестью User-Agent, плюс браузерный контроль. Перечисленного набирается пятнадцать запросов. Одиночный аудит главной 30.09: audit_duration_ms: 2944 при ответе страницы за 0,16 с. Значит --concurrency 5 это пять параллельных пачек по пятнадцать, а не пять запросов, и в полёте держится порядка семидесяти. Порог отказа лежит между 1 и 5, специально я его не искал.
Дальше это ловится без инструмента, только сравнивать надо медиану с медианой: средняя прячет выбросы, а роботу важен хвост. Оговорка, без которой числа ниже не читаются: они сняты на 25 одновременных запросах, а отказы пришли на --concurrency 5, где одновременных адресов пять. Прямо сопоставить режимы нельзя, 25-поточный прогон это самая близкая доступная мне аналогия по числу запросов в полёте.
#!/usr/bin/env bash # load.sh https://example.com 25 # второй аргумент это потоки, адресов всегда 25 SITE="$1"; N="${2:-5}"; UA="Mozilla/5.0 (compatible; load-probe)" locs() { curl -s "$1" | grep -oE '<loc>[^<]+' | sed 's/<loc>//'; } # sitemap.xml часто индекс: если внутри одни .xml, спускаемся на уровень ниже locs "$SITE/sitemap.xml" > lvl.txt if ! grep -qvE '\.xml($|\?)' lvl.txt; then : > lvl2.txt; while read -r s; do locs "$s" >> lvl2.txt; done < lvl.txt mv lvl2.txt lvl.txt fi sort -u lvl.txt | head -25 > urls.txt mstat() { sort -n | awk '{t[NR]=$1} END{ m=(NR%2)?t[(NR+1)/2]:(t[NR/2]+t[NR/2+1])/2 printf " n=%d медиана %.2f s макс %.2f s\n", NR, m, t[NR]}'; } probe() { curl -s -o /dev/null -A "$UA" --max-time 30 -w '%{time_total}\n' "$1"; } export -f probe; export UA while read -r u; do probe "$u"; done < urls.txt | mstat # по одному xargs -P "$N" -I{} bash -c 'probe "$@"' _ {} < urls.txt | mstat # N потоков
по одному: n=25 медиана 0.51 s макс 0.60 s те же 25 параллельно: n=25 медиана 2.57 s макс 4.22 s
Первая версия этого скрипта молча мерила сам sitemap-индекс: у WordPress по /sitemap.xml лежит список карт, а не список страниц, и в urls.txt попадали четыре XML-файла вместо 25 HTML-страниц. Если n у вас меньше ожидаемого, вы измерили XML, а не рендер.
Числа сняты уже после включения кэша. При -P 5 деградации нет: медиана 0,51 с, максимум 1,38 с, как при последовательном проходе. При 25 потоках отклик растёт впятеро, и запас до десятисекундного бюджета меньше, чем кажется по одиночному замеру. Разброс между прогонами есть: тот же скрипт в другой момент дня дал 0,36 и 2,46 с по медиане, то есть разрыв между режимами пять-семь раз.
Условия замера, которые определяют тайминги: одна машина, одно подключение, одна точка присутствия, --max-time 30, подменено только имя робота, один прогон каждого режима. Последовательный проход идёт первым и прогревает кэш, поэтому параллельные числа занижены.
Кэш, доступ ботов и чего я не доказал
Заголовки снимаются одной командой: curl -sSI плюс grep -iE '^(cache-control|vary|age|x-cache|cf-cache-status|etag)'. max-age=0 означает полный рендер на каждый запрос, Cookie внутри vary означает, что кэш не переиспользуется между визитами, age и x-cache означают, что перед сайтом стоит разделяемый кэш и он отвечает.
Было: max-age=0, vary: Cookie. Стало: public, max-age=300, vary: Accept-Encoding, server: Apache, ответ за 0,163 с. Ни age, ни x-cache, ни cf-*, то есть CDN нет. Оговорка ко всему сравнению: заголовков сентябрьской статики не сохранилось, поэтому утверждать, что обе установки жили на одной машине, я строго не могу, и часть дельты по таймингам может быть сравнением площадок, а не CMS.
Открыть доступ ИИ-обходчикам в robots.txt и получить доступ это разные факты: у Cloudflare есть тумблер блокировки ИИ-скраперов, отдающий 403 независимо от файла. У меня в файле отдельная группа с Allow: / на десять агентов: GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-Web, Claude-User, Claude-SearchBot, PerplexityBot, Perplexity-User, Google-Extended. Инструмент проверяет доступ под своим набором из шести имён: GPTBot, OAI-SearchBot, PerplexityBot, Claude-SearchBot, Googlebot, Applebot. Наборы пересекаются в четырёх, Googlebot и Applebot у меня попадают под User-agent: *. Это первое, что стоит сверить у себя: обходчик и поисковый агент у одного вендора это разные имена, группа на ClaudeBot не покрывает Claude-SearchBot, и такие пары легко пропустить. curl -A под всеми шестью именами инструмента даёт code=200 и одинаковый объём ответа, 267877 байт; блок cdn_check на тех же шести отдаёт blocked: false и challenge_detected: false. Список ботов в документации OpenAI, синтаксис файла в спецификации robots.txt. Подмена User-Agent остаётся синтетикой: настоящий GPTBot ходит из своих диапазонов.
Отсутствие кэша объясняет, почему каждый ответ это рендер, но не объясняет, почему под параллельной нагрузкой рендер растёт с 1,8 до 7,6 секунды. Так же ведут себя нехватка воркеров PHP-FPM (pm.max_children), выключенный OPcache, одно ядро, rate limit на прокси или WAF. Ни одну версию я не проверял: ни pm.status, ни php -i | grep opcache.enable, ни slow log под нагрузкой, ни прогон без Cookie. Вывод «лечится настройкой кэша за полдня» из данных не следует, он следует из того, что после настройки числа стали другими. Причину я не изолировал.
Что стоит в разметке до первого H1
Дальше всё на html.parser из стандартной библиотеки. Обёртка выдаёт поток событий документа: заголовки с уровнем и текстовые блоки с признаком «внутри landmark». Порядок событий это порядок в разметке, а не на экране. За landmark инструмент считает любой из трёх: тег main, тег article, атрибут role="main". У меня article в этот набор не входит, а закрытие role="main" не отслеживается; на моих страницах все article лежат внутри main, поэтому на числах это не сказалось.
# page.py: поток событий документа, только стандартная библиотека import re, urllib.request from html.parser import HTMLParser SKIP = {"script", "style", "noscript", "template", "svg", "head"} HEAD = {"h1", "h2", "h3", "h4", "h5", "h6"} WORD = re.compile(r"[0-9A-Za-zА-Яа-яЁё]+(?:-[0-9A-Za-zА-Яа-яЁё]+)*") def fetch(url, timeout=20): req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (compatible; audit)"}) with urllib.request.urlopen(req, timeout=timeout) as r: raw = r.read() enc = r.headers.get_content_charset() or "utf-8" return raw.decode(enc, "replace") class Doc(HTMLParser): """События: ('h', уровень, текст) и ('t', текст, внутри_landmark).""" def __init__(self): super().__init__(convert_charrefs=True) self.events, self.has_main = [], False self._skip, self._main, self._h = 0, 0, None def handle_starttag(self, tag, attrs): if tag in SKIP: self._skip += 1 elif tag == "main" or dict(attrs).get("role") == "main": self.has_main = True self._main += 1 elif tag in HEAD: self._h = [int(tag[1]), ""] def handle_endtag(self, tag): if tag in SKIP: self._skip = max(0, self._skip - 1) elif tag == "main": self._main = max(0, self._main - 1) elif tag in HEAD and self._h: self.events.append(("h", self._h[0], " ".join(self._h[1].split()))) self._h = None def handle_data(self, data): if self._skip or not data.strip(): return if self._h is not None: self._h[1] += " " + data else: self.events.append(("t", " ".join(data.split()), self._main > 0)) def parse(url): d = Doc() d.feed(fetch(url)) return d def words(s): return WORD.findall(s.lower())
Идём по потоку до первого H1 и считаем слова:
https://neurounit.ai/ тег main: есть H1: <оффер главной, 5 слов> слов до H1: 252 (на статике 04.09 было 354) первые 15 слов потока: пункты меню, «услуги» дважды подряд, дальше названия услуг docs.python.org/3/library/html.parser.html landmark: role="main", тега main на странице нет слов до H1: 66
Текст заголовков в выводах я заменяю описаниями: длины, порядок и числа настоящие, а рекламную обвязку своего сайта цитировать читателю незачем.
Три оговорки, без которых измерение превращается в жалобу.
Парсится сырой HTML, а не отрендеренный DOM, скрытые узлы считаются. Мега-меню отрендерено четыре раза: desktop, mobile, sticky и offcanvas, из них три закрыты display:none. Значит 252 слова, доля шаблона и длина секций частично артефакт метода. Контраргумент: извлекатель контента, который сам не считает CSS, видит ровно этот поток. Честная проверка это прогон через независимый экстрактор вроде trafilatura, я его не делал.
Тега main не было ни на одной из 501 страницы в обходе 26 сентября, то есть уже на WordPress и за день до второго замера. Не «на всех страницах сайта»: в карте 1088 адресов, а обход дал 501, потому что у краулера потолок 500 страниц плюс стартовый адрес. Надо было проверить потолок краулера. Я посмотрел в карту сайта. Вторая оговорка к тому же замеру: проверялось наличие тега, а не landmark. Отдавала бы тема role="main", извлекатель нашёл бы содержание и без тега, а мой же скрипт зачёл бы его как main.
Публичных подтверждений, что отсутствие main влияет на извлечение контента у Google, OpenAI или Anthropic, нет, а боевые экстракторы работают на плотности текста и ссылок. «Машине неоткуда узнать, где начинается содержание» это не факт, а пересказ эвристики инструмента. На эвристику main влияет прямо, и вот как.
Доля шаблона: 0,99 из ветки без main и 0,47 из ветки с ним
Сначала у меня рядом стояли два несовместимых утверждения: «на главной меню занимает треть всего текста» и «доля шаблонного текста там 0,99». Если меню треть, доля шаблона не 0,99. Формула объясняет вторую цифру:
# core/audit_negative.py, сокращено main_content = (text_soup.find("main") or text_soup.find("article") or text_soup.find(attrs={"role": "main"})) total_text_len = len(text) if main_content and total_text_len > 0: main_text_len = len(main_content.get_text(separator=" ", strip=True)) result.boilerplate_ratio = round(1.0 - (main_text_len / total_text_len), 2) elif total_text_len > 0: nav_footer_len = 0 for tag in text_soup.find_all(["nav", "footer", "header"]): nav_footer_len += len(tag.get_text(separator=" ", strip=True)) if nav_footer_len > 0: result.boilerplate_ratio = round(nav_footer_len / total_text_len, 2)
Две ветки. При наличии landmark считается доля символов вне него. Без landmark метрика уходит в оценку по nav, footer и header, а find_all возвращает и вложенные теги, поэтому <nav> внутри <header> попадает в сумму дважды. Значение при этом может превысить единицу:
mini = ("<html><body><header><nav>Услуги Кейсы Блог Контакты</nav></header>" "<p>Текст.</p><footer><nav>Услуги Кейсы Блог Контакты</nav></footer></body></html>") # nav_footer_len = 104, total_text_len = 60, boilerplate_ratio = 1.73
Проверка на живой странице: убрал landmark из текущей главной и прогнал ту же формулу, получил 0,66 вместо 0,47. Сентябрьские 0,99 это двойной счёт вложенных тегов, а не доля меню. Фразу «99 процентов главной для машины это обвязка» снимаю целиком.
Проверять такое надо двумя независимыми способами. Первый честен, если landmark есть: всё вне него обвязка. Второй работает и без него: взять три и больше страниц и посчитать через Counter текстовые блоки, дословно повторяющиеся на всех.
# boiler.py, способ б: блоки, которые есть на каждой странице выборки seen = Counter() for d in docs.values(): seen.update({e[1] for e in d.events if e[0] == "t" and len(page.words(e[1])) > 1}) shared = {b for b, n in seen.items() if n == len(docs)}
На главной первый способ даёт 522 слова обвязки из 1158, второй 526, инструмент на той же странице печатает boilerplate_ratio: 0.47 против моих 0,45 (он считает в символах, я в словах). Три оценки легли в 0,45-0,47. Полной сходимости тут нет: независимая реализация того же подхода, написанная по описанию, дала 0,53, то есть порядок один, а вторая цифра после запятой уже от реализации.
Обвязка везде одна, 522 слова, поэтому её доля обратно пропорциональна объёму текста: 0,45 на короткой главной с 1158 словами и 0,19-0,20 на страницах услуг, где по тому же счётчику 2600-2700. Абсолютные слова у разных счётчиков расходятся, сравнивать надо доли. Лечится не сокращением меню, а появлением текста на главной.
Ноль секций из 33, и почему порог не 130-170 слов
Сначала я записал, что идеальный кусок для цитаты это 130-170 слов. Это неверно даже относительно инструмента, на который я ссылался: _OPTIMAL_MIN_WORDS = 100, _OPTIMAL_MAX_WORDS = 150. И это порог одного инструмента со ссылкой на одно исследование, а не свойство моделей: нарезка у систем разная, по токенам, с перекрытием, семантическая. Привязка к словам для русского вдвойне условна, слов на токен у русского меньше, чем у английского.
В том же файле лежит вещь посерьёзнее. Две метрики пригодности к цитате заданы регулярками:
# core/audit_rag.py import re _DEFINITION_RE = re.compile( r"^[A-Z][^.]{5,60}\b(?:is|are|refers?\s+to|means?|describes?|represents?)\b", re.MULTILINE) _ANCHOR_RE = re.compile(r"(?<=[.!?]\s)[A-Z][^.!?]{30,200}[.!?]")
В [A-Z] попадает только латиница, русская заглавная не матчится ни в одной из двух. Проверка на двух смысловых копиях одного абзаца:
# запускать вместе с блоком выше: регулярки берутся оттуда RU = ("Поведенческие факторы это сигналы, которые поисковик снимает с поведения посетителей. " "Яндекс учитывает их с 2011 года и считает долю отказов ключевой. " "Доля отказов выше 65 процентов роняет позиции в среднем на 4 пункта за 3 месяца.") EN = ("Behavioral factors are signals that a search engine collects from visitor behavior. " "Yandex has used them since 2011 and treats bounce rate as the key one. " "A bounce rate above 65 percent drops positions by 4 points in 3 months on average.") for name, t in (("RU", RU), ("EN", EN)): print(name, bool(_DEFINITION_RE.search(t[:150])), len(_ANCHOR_RE.findall(t))) # RU False 0 # EN True 2
Срез [:150] не мой, так инструмент и вызывает _DEFINITION_RE: определение ищется только в первых 150 символах текста. Число anchors зависит от количества предложений в абзаце, важно не оно, а то, что для русского оба счётчика стоят на нуле при любом тексте. На главной инструмент отдаёт has_definition_opening: false и anchor_sentences: 2 при своих 1271 слове, и двойка набежала с латинских фрагментов. Мой скрипт на той же странице считает 1158 слов: токенизация разная, сравнивать эти два счётчика между собой нельзя.
Длина секций меряется корректно: идём по потоку событий, на каждом заголовке закрываем секцию, текст внутри landmark прибавляем в счётчик слов текущей.
# sections.py, ядро счётчика LO, HI = 100, 150 d = page.parse(url) secs, cur, title = [], None, None for e in d.events: if e[0] == "h": if cur is not None: secs.append((title, cur)) title, cur = e[2], 0 elif e[0] == "t" and cur is not None and (e[2] or not d.has_main): cur += len(page.words(e[1])) if cur is not None: secs.append((title, cur)) fit = [s for s in secs if LO <= s[1] <= HI]
главная, прогон 30.09 секций: 33 средняя 19.2 медиана 14 макс 95 в диапазоне 100-150: 0 из 33 = 0% пустых (0 слов): 6 19 <H1: оффер главной> 0 <первая половина фразы из блока «как это работает»,> 30 <вторая половина той же фразы.> 1 <подзаголовок блока с ценой>
Вот механизм в четырёх строках вывода. Одно предложение конструктор разложил на два заголовка, потому что дизайнеру нужен был перенос строки. Для машины это две секции, пустая и на 30 слов. Туда же ушли декоративные бейджи «24/7», «10+» и «4», попавшие в H2 и H3 наравне со смысловыми.
Инструмент на той же странице сегодня печатает total_sections: 18 и avg_section_words: 10.5, мой скрипт 33 и 19,2: он режет иначе и по другому тексту. Совпадение с сентябрьскими «0 из 18» случайное, в сентябре средняя была 10,2 слова при той же нулевой доле попаданий. Сходится диагностическое: ноль попаданий, медиана 14 при средней 19.
Страница услуги: 49 секций, средняя 43,1, медиана 14, в диапазоне 8 из 49. Отсюда снимаю ещё одну свою фразу, «страница на 2400 слов не даёт ни одного блока, который модель может вынуть целиком»: блоков в диапазоне восемь. Мешают им не только декоративные заголовки, но и семь прыжков уровня на той же странице. Контроль на технической документации, страница html.parser из документации Python: 1 из 14, средняя 121,1, медиана 8, максимум 746. То есть высокая доля попаданий сама по себе не цель, признак это разрыв между средней и медианой.
Два ограничения. Декоративный заголовок посреди абзаца режет секцию надвое, отличить его от смыслового скрипт не умеет. И картина «первые сто слов это меню» в сентябре держалась на всех девяти проверенных страницах, а сейчас перемерена только главная: остальные восемь я заново не считал.
69 страниц по одному каркасу: шингли против вывода о дублях
За день до замера цитируемости SEO-обход показал, что 69 дочерних страниц услуг построены по одному каркасу: совпадают технические параметры, одиннадцать подзаголовков, сорок две картинки. Вывод напрашивался: масштабированный контент без добавленной ценности, переписывать тексты.
На следующий день я посчитал пересечение. Коэффициент Жаккара по шинглам длины шесть, рядом containment:
# множество кортежей из n подряд идущих слов, взятых только из landmark shingles = lambda w, n=6: {tuple(w[i:i + n]) for i in range(len(w) - n + 1)} jaccard = lambda a, b: len(a & b) / len(a | b) if a | b else 0.0 containment = lambda a, b: len(a & b) / min(len(a), len(b)) if a and b else 0.0
Токенизация: регулярка [0-9A-Za-zА-Яа-яЁё]+(?:-[0-9A-Za-zА-Яа-яЁё]+)*, то есть дефис внутри слова не режет и «ИИ-агенты» остаётся одним токеном; регистр к нижнему, словоформы не нормализуются, каркас вырезается фильтром по landmark. Шесть слов потому, что случайные совпадения на такой длине уже редки, а перефраз ещё ловится. Если считать дефис разделителем, те же страницы дают 0,073-0,077 вместо 0,072-0,076, на вывод это не влияет.
main_only = True (2251, 2276, 2273 шингла) J=0.072 C=0.136 pod-klyuch / ii-rpa J=0.072 C=0.134 pod-klyuch / vnedrenie J=0.076 C=0.141 ii-rpa / vnedrenie контроль: с главной того же сайта J=0.039, с docs.python.org J=0.000
Три поправки. Первая: ни 1 минус Жаккар, ни 1 минус containment не дают доли уникального текста. Жаккар считает пересечение к объединению, containment к меньшему из множеств, и при почти равных объёмах (2251, 2276 и 2273 шингла) разрыв между ними двукратный по определению, а не из-за перекоса размеров: при равных n получается C = x/n против J = x/(2n-x). Для вопроса «переписан ли текст» берут containment, на больших объёмах minhash с simhash. Измерено ровно одно: до 14 процентов шестиграмм меньшей страницы встречаются на большей. Цифру «93 процента прозы уникальны» снимаю и никакого другого процента уникальности вместо неё не ставлю, потому что перефраз и смену порядка слов шинглы не ловят.
Вторая, про 354 слова общего меню: попади меню в выборку, 0,07 было бы невозможно. Оно и не попало, фильтр по landmark его вырезает. Без фильтра на том же тексте J=0.115 C=0.208, то есть Жаккар вырос на шестьдесят процентов на одинаковых шинглах обвязки. Это и есть способ отличить общий каркас от дублей: посчитать дважды, с фильтром и без.
Третья, к вердикту: измерение опровергло гипотезу про дубли, но вопрос о ценности не снимает. У Google scaled content abuse это про способ производства и добавленную ценность, а не про лексическое пересечение, и 69 страниц по одному каркасу с уникальной прозой остаются кандидатом в doorway. Лечат такое разные данные, цены, кейсы и разный intent; шингли тут не инструмент. Разница в стоимости сохраняется: переписать 69 текстов это недели, править шаблон дни.
Что просело и выросло в моей сводной шкале
Весов этой шкалы у вас нет, а один её блок между прогонами менялся. Поэтому таблица дана как список наблюдений, а не как арифметика сводного балла.
Направление |
04.09 |
27.09 |
Что за этим |
|---|---|---|---|
Цитируемость и доступ ИИ |
71 |
54 |
вес около четверти итога, отказы под параллельным обходом |
Техническая база |
85 |
70 |
HTML 259-269 КБ на главной, доля полезного текста 3,3% |
Контент |
56 |
47 |
682 записи из 918 получили дату «сентябрь 2026» |
Структурированные данные |
73 |
66 |
логотип и превью в разметке остались от старого сайта, 404 |
Готовность под платформы |
42 |
51 |
разметка FAQ, словарь терминов, услуги с ценой, авторство |
Авторитет бренда |
13 |
33 |
из них 16 баллов это правка самой шкалы |
Про бренд: я добавил блок, которого в шкале не было, машинное описание компании, и в разметке появились юрлицо, ИНН, ОГРН, адрес, основатели и профили. Но по прежней шкале, сопоставимой с сентябрьскими 13, выходит 17, а не 33. Чистый прирост за 23 дня 4 балла, остальные 16 это уточнение линейки. Отсюда и разброс 48,5-52.
Минус 15 по технической базе дал почти целиком вес HTML. Порог я взял у инструмента, page_weight_bytes > 200_000 в core/agent_access.py, и в своей шкале не переопределял, и это перекос: 260 КБ для темы на конструкторе норма, а модели работают с текстом после извлечения. Про даты аккуратнее: у одной записи дата изменения оказалась раньше публикации, и я назвал это прямым сигналом недостоверности для машины. Публично это не установлено ни для одной модели, это эвристика инструмента. Реальный вред другой: сбитая сортировка, каннибализация свежести и 682 записи, выглядящие как публикация за один месяц. Починил через wp-cli пакетным обновлением post_date.
Опорной точки для шкалы у меня не было вовсе. Чтобы понять практический разброс, прогнал тем же geo audit --url главные страницы четырёх сайтов того же профиля, по одному прогону 30.09: 45, 33, 51 и 54. Рабочий коридор лежит в районе 30-55, а моя главная даёт 73 и в коридор не попадает. Это повод не радоваться, а посмотреть, что шкала считает: нарезку на секции она не считает вообще, а по ней у меня ноль попаданий. Домены не называю: одиночный прогон чужой главной ничего о ней не доказывает, и о замере эти сайты не просили.
Что перемерил после починки, а что нет
Замер |
27.09, суточный WordPress |
30.09, после починки |
|---|---|---|
Кэш главной |
|
|
25 адресов параллельно |
медиана 7,61 с, макс 9 с; аудит при |
медиана 2,5 с, макс 4,2 с, таймаутов нет |
Тег |
нет ни на одной из 501 обойдённой |
есть |
Слов до H1 |
354 |
252 |
Доля шаблона, главная |
0,99 (ветка без landmark) |
0,47 инструмент, 0,45 свой скрипт |
Секции в диапазоне, главная |
0 из 18 (счётчик инструмента) |
0 из 33 (свой скрипт) |
Жаккар между услугами |
0,068-0,070 (инструмент) |
0,072-0,076 (своя реализация) |
В двух последних строках сменился не сайт, а измеритель: секции инструмент режет по своей логике, шингли считает своим токенизатором. Сегодняшний прогон инструмента даёт те же 18 секций, что и 27.09, а расхождение по Жаккару лежит в пределах разницы токенизаторов. Секции остались нулём: разбор декоративных заголовков в шаблоне я не сделал. Третьего сводного прогона нет, и цифру 54,4, которую я ставил в первой версии, снимаю: она посчитана без замера, по двум починенным дефектам.
Главное ограничение сравнения: второй замер сделан через сутки после переезда, а это худшая точка. Сравнивается вылежавшаяся статика с суточным WordPress, у которого кэш не настроен, редиректы не устаканены, карта сайта перегенерируется. Часть минуса объясняется возрастом установки, а не CMS. Правильный замер это те же команды через две-четыре недели.
Чего я не проверял
Реальных цитирований в ChatGPT, Perplexity и Яндекс Нейро не снимал, серверных логов не смотрел, поэтому с какой параллельностью и с каким своим таймаутом ходят настоящие обходчики, не знаю. Фразу «роботы ходят параллельно, и под их нагрузкой сайт перестаёт укладываться в тайминги» снимаю: она не измерена, а Googlebot вообще снижает темп при росте времени ответа. Измерено ровно одно: инструмент с бюджетом 10 секунд на адрес не проходит при --concurrency 5. Оговорки по кэшу, отрендеренному DOM и независимому экстрактору стоят по месту выше, здесь не повторяю.
Вклад отдельных событий в дельту не разделён: между замерами было минимум четыре события, переезд, покупная тема, слияние баз с перезаписью дат и правка собственной методики, плюс две починки по ходу. Механизма разделения у меня нет, поэтому фразу «понимаем, что вызвано переездом, а что нашей работой» тоже снимаю.
Что поменял у себя
Не «что делать при любом переезде»: выборка это 9 страниц из 501 и один прогон.
Меряю параллельным обходом и держу бюджет инструмента отдельно от времени ответа: --concurrency 1 как точка отсчёта, потом --concurrency 5, между ними порог отказа. В кэше для WordPress с залогиненными сессиями Cookie из vary убран, а разделение по сессиям делается исключением из кэша по конкретным cookie залогиненного пользователя. Шингли считаю дважды, с фильтром по landmark и без: разница между 0,072 и 0,115 и есть ответ, каркас общий или текст. Смотрю на разрыв между средней и медианой длины секции: средняя 19 при медиане 14 и нуле попаданий, так выглядит проблема вёрстки, а не контента.
И перед тем как верить баллу, читаю веса инструмента и его регулярки. У меня две метрики пригодности к цитате для русского текста не работали вообще, и без чтения исходников это не видно.
Итог
Шесть утверждений первой версии этого разбора я снял, и снялись они не новыми замерами, а чтением того, чем мерил: константы, две ветки одной формулы и две регулярки на латинице. Порядок оказался обратным привычному: сначала исходники инструмента, потом его числа, потом выводы про сайт. Пятнадцать минут на чтение весов и регулярок экономят неделю работы по ложному вердикту.
Александр Ремишевский. Другие разборы по SEO, GEO и ИИ-поиску.