Автоматизация SEO-аудита: редиректы, canonical и hreflang как граф

В SEO-аудите недостаточно проверить, что страница отвечает кодом 200 и содержит заполненное поле canonical. Связи между URL могут быть нарушены: canonical способен вести на редирект или отсутствующую страницу, языковые версии — не ссылаться друг на друга, а старые адреса — образовывать цикл. По отдельности такие поля выглядят корректно, но вместе создают ошибочную структуру сайта.
Практичный способ автоматизировать проверку — представить результаты обхода в виде графа. В нём URL становятся узлами, а редиректы, canonical и hreflang — разными типами рёбер. Такой подход позволяет сохранять не только факт нарушения, но и его источник, цель и условие, при котором проблема считается исправленной.
Что именно проверяет графовый аудит
Для воспроизводимой проверки нужны три компонента: снимок наблюдений, набор правил и список диагностик. Снимок содержит запрошенный адрес, непосредственный HTTP-ответ и найденные в нём аннотации. Правила описывают ожидаемое поведение связей, а результат должен показывать исходный URL и проблемную цель.
Важно не смешивать наблюдения с выводами о поисковой выдаче. Код 200 подтверждает успешный HTTP-ответ, но не доказывает индексацию страницы. Аналогично, canonical является сигналом предпочтительной версии, а не гарантией того, что именно этот адрес выберет Google. Hreflang показывает связи локализованных страниц, но не подтверждает качество или полноту перевода.
До запуска аудита стоит определить назначение URL. Архивная страница может штатно возвращать 404, а адрес со старого домена — вести на новый через редирект. Поэтому полезно разделить индексируемые посадочные страницы, служебные URL, удалённые документы и адреса миграции. Ошибкой считается не само отклонение, а расхождение поведения с контрактом конкретного типа страницы.
Три типа связей и их проверки
Редиректы. В снимке необходимо сохранять каждый непосредственный переход: исходный адрес, код ответа и значение Location. Если краулер автоматически прошёл цепочку и записал только конечный 200, промежуточные узлы уже нельзя восстановить. Коды 301 и 308 имеют постоянный смысл, а 302 и 307 — временный; объединять их можно лишь для обхода графа, но не для оценки миграционной стратегии.
Цикл редиректов является безусловным дефектом маршрута. Цепочка из двух переходов — диагностический повод для упрощения, но сама по себе не доказывает потерю позиций. Для canonical проверяются наличие цели в снимке и её ответ 200. Отсутствие canonical не обязательно является ошибкой: требование self-canonical должно быть частью локального контракта шаблона.
Hreflang. Минимальный набор проверок включает самоссылку страницы, наличие цели в снимке, ответ 200 и обратную ссылку между языковыми версиями. Для x-default правило обратной языковой ссылки можно не применять. Отдельно полезно проверять повторяющиеся языковые коды, одинаковый набор альтернатив и согласованность canonical, HTML-разметки, HTTP Link и sitemap.
Контракт снимка и реализация на Python
Скрипт может принимать JSON-массив, где одна запись соответствует одному запрошенному URL и его непосредственному ответу до автоматического перехода по редиректу. Поле canonical хранит выбранную аннотацию, а hreflang — словарь «язык — адрес». Относительные значения можно нормализовать относительно исходной страницы и удалить фрагмент адреса, но не следует автоматически убирать query-параметры, менять регистр пути или склеивать варианты со слешем: такая агрессивная нормализация способна скрыть реальную ошибку.
Проверяющий скрипт на Python использует только стандартную библиотеку и не отправляет HTTP-запросы. Он строит набор страниц, проходит цепочки редиректов, выявляет циклы и отсутствующие Location, проверяет цели canonical и взаимность hreflang. Результаты сортируются, поэтому одинаковый снимок даёт одинаковый JSON-отчёт. Запуск выполняется командой вида python3 audit_graph.py snapshot.json > issues.json.
В синтетическом примере старый адрес проходит два редиректа, русская страница указывает canonical на отсутствующую цель, а английская версия не возвращает ссылку на русскую. В отчёте появятся три диагностики: REDIRECT_CHAIN, CANONICAL_TARGET_UNKNOWN и HREFLANG_NO_RETURN. Неизвестная цель не равна ответу 404: возможно, URL просто не попал в очередь обхода и его нужно дозагрузить. Если адрес есть в снимке и возвращает не 200, применяется отдельное правило CANONICAL_TARGET_NOT_200.
Ограничения и интеграция в релизный процесс
Такой валидатор не заменяет полноценный краулер. Он не рендерит JavaScript, не анализирует содержимое, не проверяет robots.txt, meta robots и X-Robots-Tag и не определяет факт индексации. Для динамических сайтов лучше хранить отдельно HTTP-снимок и результат рендеринга. Для нестабильных ответов нужны повторные наблюдения: единичный 503 во время релиза и устойчивый сбой — разные операционные ситуации.
В промышленной выгрузке стоит сохранять время запроса, User-Agent, окружение, исходные заголовки, все найденные аннотации и версию парсера. Нельзя сворачивать несколько canonical в одно поле без отдельной диагностики, а словарь hreflang может скрыть повторяющиеся языковые коды. На больших объёмах обход общих цепочек следует кэшировать, а циклы искать через компоненты связности, но сначала важнее обеспечить полноту снимка и корректность правил.
Блокировать каждый релиз по общему числу предупреждений не стоит: первый запуск почти наверняка обнаружит накопленные проблемы. Лучше сохранить исходный набор диагностик и отслеживать новые нарушения по ключу «правило, источник, цель». Релиз можно останавливать при новом цикле на критичном маршруте или недоступной цели у важного шаблона. Неизвестные адреса сначала следует дозагрузить, а лишние переходы — планировать к упрощению.
После публикации аудит нужно повторить на боевом сайте: CDN, edge-правила и серверная конфигурация могут отличаться от тестового окружения. LLM уместна уже после детерминированных проверок — для группировки сообщений, подготовки описания задачи или объяснения цепочки переходов. Но критерием закрытия остаётся новый проверяемый снимок: цель отвечает ожидаемым кодом, цикл исчез, а обратная языковая ссылка появилась.


