Перейти к содержимому

Проблемы сканирования — реестр кодов

Каждая проблема сканирования Pro — стабильный код из единого реестра Rankbeam\Seo\Pro\Scanning\IssueRegistry. Сканеры не придумывают коды на месте: каждая проблема создаётся через IssueRegistry::make(), который берёт серьёзность и поле из реестра и отклоняет любой неопределённый код. Поэтому каталог ниже — контракт для ваших интеграций: панели, экспорт и оценка Pro читают коды, а не разбирают сообщения. Бесплатный seo:audit использует собственный реестр метаданных ядра с более узким охватом и некоторыми отличающимися кодами hreflang.

Каждый код содержит:

  • id — стабильную строку, сохранённую как seo_scan_issues.issue_type.
  • severitycritical, warning или notice; фиксирована для кода (мы разделяем коды, а не меняем серьёзность одного).
  • field — соответствующее поле seo_meta либо page для проблем уровня страницы.
  • execution class — что требуется для обнаружения (ниже).
  • evidence — ключи массива context проблемы.

Классы выполнения

Каждая проверка относится ровно к одному из трёх классов по требованиям выполнения:

КлассТребуетКто выполняет
metadataМодель и резолвер ядра, без загрузки страницыСканирование модели (PageScanner); бесплатный seo:audit покрывает часть проверок метаданных
renderedОтдаваемый HTML страницы (запрос к ядру приложения внутри процесса или внешняя загрузка)Сканирование URL (UrlScanner)
networkИсходящий запрос для проверки отдельного адреса назначения (canonical на другой URL)Сканирование URL, всегда через SsrfGuard

Поэтому бесплатный аудит внутри процесса не может быть эквивалентен полному сканированию Pro: только коды metadata вычисляются без рендеринга страницы, и только конвейер Pro загружает отрендеренный HTML и проверяет канонические адреса по сети. Фильтруйте реестр по классу через IssueRegistry::byClass(IssueRegistry::EXEC_METADATA).

Коды метаданных

Обнаруживаются по модели и резолверу через PageScanner. (missing_title, missing_description и коды длины также выдаёт сканирование отрендеренного URL, измеряя отдаваемый <head>; коды и смысл одинаковы.)

КодСерьёзностьПолеДанныеЗначение
missing_titlecriticaltitleНет заголовка и вычисляемого резервного значения.
missing_descriptionwarningdescriptionНет метаописания и вычисляемого резервного значения.
missing_og_imagenoticeog_imageНет изображения Open Graph и вычисляемого резервного значения.
missing_focus_keywordnoticefocus_keywordsНе задано фокусное ключевое слово.
duplicate_titlewarningtitletitle, duplicate_urlsЗаголовок повторяется на других страницах той же локали.
duplicate_descriptionwarningdescriptiondescription, duplicate_urlsОписание повторяется на других страницах той же локали.
title_too_longwarningtitlelength, max, scriptИтоговый заголовок превышает рекомендацию для письменности (60 для латиницы, около 30 для CJK).
title_too_shortnoticetitlelength, min, scriptИтоговый заголовок короче нижней границы для письменности (30 для латиницы, около 15 для CJK).
description_too_longwarningdescriptionlength, max, scriptИтоговое описание превышает рекомендацию для письменности (160 / около 80).
description_too_shortnoticedescriptionlength, min, scriptИтоговое описание короче нижней границы для письменности (70 / около 35).
robots_conflict_indexingcriticalrobotsrobotsДиректива robots содержит одновременно index и noindex.
robots_conflict_followingwarningrobotsrobotsДиректива robots содержит одновременно follow и nofollow.
noindex_warningwarningrobotsrobots, canonical, page_url, shipping_signalСтраница с canonical на себя имеет noindex: эвристика для проверки, а не доказательство необходимости индексации. Выдаётся при сканировании модели и отрендеренного URL.
invalid_canonicalcriticalcanonicalcanonicalКаноническое значение не является допустимым URL.
cross_domain_canonicalwarningcanonicalcanonical, page_urlКанонический URL указывает на другой хост.
shared_canonicalnoticecanonicalcanonicalНесколько страниц объявляют одинаковый канонический URL.
insecure_canonicalwarningcanonicalcanonicalКанонический URL http:// на сайте https.
hreflang_invalid_codewarningalternatesinvalid_codesАльтернативная версия hreflang использует значение, которое не является x-default или допустимым языковым кодом BCP-47.
hreflang_missing_self_referencewarningalternateslocale, page_urlАльтернативные версии объявлены, но ни одна не ссылается на локаль самой страницы (hreflang на себя).
hreflang_duplicate_codewarningalternatesduplicate_codesОдин код hreflang соответствует нескольким URL (неоднозначная группа).
hreflang_missing_x_defaultnoticealternateslanguagesВ многоязычной группе hreflang нет резервной версии x-default.
aeo_missing_authornoticeschemaУ статьи в структурированных данных страницы нет сущности автора (авторство / происхождение явно не указано в разметке).
aeo_article_missing_datenoticeschemaУ статьи в структурированных данных нет даты публикации/изменения (временная история явно не указана в разметке).

Пороги длины берутся из политики длины ядра с учётом письменности (Pro 2.33): 60/160 для латиницы, около 30/80 для CJK, в графемах, поэтому сканирование не противоречит счётчикам редактора. Нижние границы (30 для заголовка, 70 для описания на латинице, примерно вдвое меньше для CJK) — нижний порог недостаточной оптимизации в сканировании, а ключ контекста script называет применённую группу. Длина измеряется по итоговым заголовку/описанию — реально выводимым значениям, включая резервные и суффикс заголовка.

Коды hreflang_* проверяют объявленные альтернативные версии hreflang страницы из alternates резолвера: неверные/повторяющиеся коды, отсутствие ссылки на себя и x-default в многоязычной группе. Они выполняются только при объявленных альтернативных версиях. Межстраничную взаимность («обратные теги») эти проверки метаданных не проверяют; необязательная сетевая проверка ниже загружает другую страницу.

Коды aeo_* — сигналы готовности к ответам (AEO): представлено ли содержимое статьи в структурированных данных? Они читают итоговый граф JSON-LD и срабатывают только для объявленной разметки статей (Article, BlogPosting, NewsArticle, …) без сущности author (явное авторство / происхождение) или datePublished / dateModified (явная временная история). Страница без статьи никогда не отмечается. Проверки управляются seo-pro.scan.checks.aeo (включено по умолчанию) и соответствуют бесплатному seo:audit.

missing_focus_keyword зависит от настройки

Замечание об отсутствии фокусного ключевого слова появляется только при включённом процессе ключевых слов ядра (seo.keywords.enabled, по умолчанию false). При отключении сканирование не отмечает страницу за отсутствие ключевого слова. Бесплатная команда seo:audit и редактор Filament читают тот же флаг ядра, поэтому сканирование, аудит и напоминание редактора всегда согласованы: переключатель один.

Коды отрендеренной страницы

Обнаруживаются UrlScanner по отдаваемому HTML: для целей того же хоста — запросом к ядру приложения внутри процесса без исходящего трафика, для внешних — защищённой загрузкой.

КодСерьёзностьПолеДанныеЗначение
http_errorcriticalpagestatusURL ответил статусом 4xx/5xx.
empty_responsecriticalpageURL вернул пустое тело.
missing_canonicalnoticecanonicalВ отрендеренном head нет <link rel="canonical">.
noindex_pagenoticerobotsrobotsОтрендеренная страница имеет noindex (информация). Для страницы noindex с canonical на себя вместо этого выдаётся учитываемый в оценке noindex_warning.
missing_h1noticepageНет заголовка <h1>.
multiple_h1noticepagecountБолее одного <h1> (информация).
missing_image_altwarningpagecount, total, sampleУ изображений контента нет атрибута alt (явный alt="" означает декоративное изображение и не отмечается).
thin_contentnoticepageword_count, threshold, segmenterТекст страницы короче настроенного числа слов. Их считает токенизатор чек-листа: пробелы для письменностей с разделителями, словарная сегментация ICU (segmenter: intl, требует ext-intl) для китайского, японского и тайского — японская статья из 400 слов не считается одним «словом».
mixed_contentwarningpagecount, sampleПодресурсы http:// на странице https.
html_lang_missingnoticepageНет <html lang> или он пуст. Вспомогательные технологии могут выбрать неподходящий голос.
html_lang_invalidnoticepagedeclaredЗначение lang не является тегом BCP-47 (english, en_US с подчёркиванием, jp).
html_lang_mismatchwarningpagedeclared, declared_script, detected_scriptВидимый текст написан письменностью, не соответствующей объявленному языку: lang="en" на японской странице, lang="ru" на латинице. Только уровень письменности (угадать неверный язык среди языков латиницы нельзя, и сканер не гадает); требуется ≥40 букв текста.

Сетевые коды

Обнаруживаются UrlScanner только при включении соответствующего флага: seo-pro.scan.url_checks.check_canonical_target для канонической цели, check_hreflang_reciprocity для альтернативных версий hreflang. Каждая цель загружается через SsrfGuard (список разрешённых схем, область хостов, отклонение частных IP, бюджеты перенаправлений/времени/размера), без следования перенаправлениям, чтобы перенаправляющий канонический URL был виден. Canonical или альтернативная ссылка на себя пропускается: сама страница только что загружена.

КодСерьёзностьПолеДанныеЗначение
blocked_urlnoticepagereasonЦель отклонена SsrfGuard до любого HTTP-запроса.
canonical_target_brokencriticalcanonicalcanonical, statusCanonical указывает на страницу с HTTP-ошибкой.
canonical_target_redirectwarningcanonicalcanonical, status, locationCanonical указывает на перенаправляющую страницу; укажите конечный URL.
canonical_target_noindexwarningcanonicalcanonicalCanonical указывает на страницу, которая сама имеет noindex.
canonical_target_blockednoticecanonicalcanonical, reasonКаноническую цель не удалось проверить (отказ защиты / недоступность).
hreflang_not_reciprocalwarningalternateshreflang, href, statusОбъявленная альтернативная версия не ссылается на исходную страницу. Пара hreflang может игнорироваться; само по себе это не запрещает индексацию перевода.
hreflang_target_unverifiednoticealternateshreflang, href, reasonАльтернативную версию не удалось загрузить (отказ защиты, ошибочный статус, перенаправление, превышение размера), поэтому взаимность не проверялась. Нет доказательств, а не найден дефект.

Проверка взаимности загружает максимум hreflang_max_alternates (по умолчанию 10) целей на страницу, включая x-default, пропуская повторы и саму страницу. Коды метаданных hreflang_* уровня модели выше проверяют объявленный список; только эта проверка требует другую страницу.

Все сетевые пути используют общий SsrfGuard; модель угроз и остаточный риск TOCTOU описаны в SECURITY.md.

Как коды влияют на оценку

SEO-оценка Pro100 − сумма фиксированных штрафов за каждую учитываемую проблему с весом по серьёзности выше. Большинство кодов учитывается; некоторые намеренно исключены: missing_focus_keyword (рекомендация), noindex_page и multiple_h1 (информация), blocked_url / canonical_target_blocked / hreflang_target_unverified («не удалось проверить» ≠ дефект) и коды hreflang_*, html_lang_* и aeo_* (рекомендательные сигналы, пока исключённые из оценки). Полный список и штраф каждого кода приведены на странице оценки.

Жизненный цикл проблемы

Проблема — не просто строка, существующая до исправления. У неё есть жизненный цикл: сканирование согласует проблемы цели, а не стирает и создаёт заново. У каждой стабильная идентичность: цель (scannable_type + scannable_id для модели или url для маршрута/карты сайта) плюс issue_type. Каждый код выдаётся максимум один раз на цель за сканирование: коды с несколькими нарушителями (missing_image_alt, mixed_content, hreflang_*, …) объединяют случаи в одну строку с count / sample. Поэтому идентичность уникальна.

При каждом сканировании для каждой цели:

  • находка без существующей строки создаётся как open с отметкой detected_at;
  • находка, совпавшая с открытой строкой, обновляет данные и сохраняет исходный detected_at — стабильное первое обнаружение, которое больше не сбрасывается при каждом сканировании;
  • открытая проблема, которую завершённая проверка больше не находит, отмечается fixed с resolved_at; строка сохраняется, а не удаляется, чтобы зафиксировать настоящее исправление;
  • проблема fixed, которая вернулась, открывается повторно в той же строке (регрессия), обновляя detected_at;
  • проблема, отмеченная пользователем как ignored в панели, не изменяется.
СтатусЗначениеКто задаёт
openПрисутствует сейчас.Сканирование (новая или повторно найденная)
fixedПрисутствовала, больше не находится.Сканирование автоматически, при следующем запуске, который её не находит
ignoredОтключена пользователем; исключена из открытых счётчиков и оценки.Действие игнорирования в панели

Поскольку исправления сохраняются, а не отбрасываются, отчёт под своим брендом может показывать настоящие количества исправленных/новых проблем за период вместо разницы снимков отчётов. Все потребители количества открытых проблем — панель, команда seo-pro:scan-status, оценка — фильтруют по open, поэтому сохранённые строки fixed не увеличивают показатели. Исправленные строки относятся к устранившему их запуску и удаляются по обычному сроку хранения запусков.

Конфигурация

php
// config/seo-pro.php → 'scan'
'url_checks' => [
    'enabled' => true,
    'crawl_external' => false,             // fetch external URL targets (guarded)
    'check_canonical_target' => false,     // EXEC_NETWORK canonical validation (guarded)
    'check_hreflang_reciprocity' => false, // EXEC_NETWORK hreflang link-back crawl (guarded)
    'hreflang_max_alternates' => 10,       // targets fetched per page by that crawl
],
'checks' => [
    'length' => true,            // title/description length (metadata + rendered)
    'rendered_content' => true,  // H1 / alt / thin content / mixed content / html lang
],
'content' => [
    'min_word_count' => 200,     // thin_content threshold
    'evidence_sample' => 5,      // max example URLs stored per issue
],

Бюджет размера ответа защищённых запросов — seo-pro.http.max_response_bytes (по умолчанию 2 МБ); сканирование того же хоста внутри процесса не ограничено.

Совместимость (переименование кода)

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

Старый кодНовый кодСерьёзность
robots_conflict (index + noindex)robots_conflict_indexingcritical
robots_conflict (follow + nofollow)robots_conflict_followingwarning

Если вы сохраняли robots_conflict или фильтровали по нему, перейдите на два новых кода.

rankbeam/laravel-seo распространяется под лицензией MIT.