Обход и технический аудит
Обход — это собственные доказательства инструмента. Он использует последовательный httpx-фетчер поверх стандартного HTML-парсера, соблюдает robots.txt, идёт по ссылкам того же origin в ширину, запускает все правила и сохраняет страницы и проблемы в SQLite, когда хранилище настроено.
См. также: Источники данных и квоты, Отчёты и диффы, Автоматизация и доставка, MCP и навыки агентов.
Запуск обхода
lv24-seo crawl --start https://example.com/ --max-urls 500 --json--start по умолчанию берётся из project.base_url. --max-urls по умолчанию равен [crawl] max_urls (500) и должен быть не меньше 1. У CLI нет отдельного верхнего лимита; MCP seo_crawl_site ограничивает запрос выше 5 000 URL и сообщает об этом предупреждением.
Обход последовательный и вежливый. concurrency в seo.toml зарезервирован для будущего пула воркеров и сегодня ни на что не влияет. Crawl-delay из robots.txt задаёт паузу между запросами.
Стартовый URL и каждый redirect-хоп проверяются на соответствие origin (схема, хост и порт) и robots.txt до отправки запроса. Чужой или запрещённый хоп никогда не запрашивается и сообщается как robots_blocked с заметкой о покрытии. Единственное исключение — сам robots.txt, его редиректы обрабатываются по RFC 9309, но всё равно под политикой SSRF и лимитом редиректов.
Лимиты и бюджеты
| Лимит | Значение |
|---|---|
| Тело обычной страницы | 5 МБ на страницу |
| Тело документа карты сайта | 50 МБ на документ |
| URL в карте сайта | Протокольный лимит 50 000 |
| Документов карты сайта | 20 на обход |
| Глубина индекса карт сайта | 2 |
| Редиректов на запрос | 5 |
| Бюджет запросов | 1 + max_urls × (max_redirects + 1) |
| Сохраняемый текст страницы | 20 000 символов |
| Список проблем MCP | 500 проблем на ответ |
Бюджет запросов отображается как summary.request_count и summary.request_budget. Когда он исчерпан, обход останавливается с заметкой, а не тратит запросы молча. Для 500 URL и 5 редиректов бюджет составляет 3 001 запрос.
Сохраняемый текст страницы ограничен 20 000 символов, но word_count вычисляется по полному видимому тексту и остаётся точным. Ограничение — это предел хранения, а не парсинга: извлечение ссылок, JSON-LD, hreflang и подсчёт слов по-прежнему используют полный документ. Ни одно текущее правило не читает сохранённый текст.
robots.txt и SSRF
robots.txt следует семантике RFC 9309 с самым длинным совпадением: побеждает наиболее конкретное правило, а при равенстве — Allow. Ответ 5xx или сетевая ошибка откладывают обход вместо fail-open. Отсутствующий robots.txt не считается ошибкой.
Адреса loopback, private и link-local отклоняются по умолчанию, а DNS повторно разрешается на каждом redirect-хопе, чтобы хост не мог перебиндиться в приватную сеть. Локальный аудит должен явно включить allow_private в фетчере; настройки по умолчанию, которая тихо разрешает приватные цели, нет.
Обнаружение карт сайта
Обнаружение начинается со строк Sitemap: в robots.txt плюс стандартного /sitemap.xml. Отсутствие стандартной карты сайта нормально и не считается ошибкой. Каждый кандидат, дочерний элемент индекса и редирект карты сайта должны оставаться на стартовом origin; чужой кандидат пропускается с заметкой.
Документы карты сайта получают бюджет тела 50 МБ и протокольный лимит 50 000 URL. Индекс карт может прочитать до 20 документов на глубину 2. URL, найденные только в карте сайта, добавляются во фронт обхода, поэтому обход может начаться с карты, даже если главная страница почти не ссылается на другие. Карта, которая не загрузилась, сообщает только тип исключения (HTTPError, TimeoutError, …), но не URL и не query-строку; URL с ?key=… не должен попадать в предупреждения или limitations[].
Покрытие карты сайта
crawl возвращает data.sitemap_coverage: что сайт заявляет, по сравнению с тем, что реально увидел обход. Шесть корзин:
| Корзина | Значение |
|---|---|
unlinked | Есть в карте сайта, получен успешно, но ни одна обойдённая страница на него не ссылается |
missing_from_sitemap | Обойдён, 200, индексируемый HTML, которого нет в карте сайта |
not_ok | Есть в карте сайта, но ответ не 200 или был редирект |
noindex | Есть в карте сайта, но помечен noindex |
blocked | Есть в карте сайта, но вне области обхода (robots или другой origin) |
unvisited | Есть в карте сайта, но не запрашивался; статус неизвестен, не «битый» |
counts точны. Каждый список buckets ограничен 50 URL. complete ложен, а limitations[] называет корзины, которые являются лишь кандидатами, если обход или инвентарь карты сайта усечён, карта не загрузилась или часть URL осталась необойдённой. Если карты сайта нет вовсе, дифф пуст и говорит «пусто, а не чисто», вместо того чтобы помечать каждую страницу как отсутствующую.
Сетевая ошибка при получении попадает в unvisited, а не в blocked и не в not_ok: статус не наблюдался, поэтому состояние URL неизвестно.
Граф ссылок
data.graph — детерминированная сводка внутреннего графа ссылок:
nodes— страницы, успешно полученные со статусом 200.edges— уникальные нормализованные внутренние пары источник-цель, без self-ссылок.orphans— узлы без входящих ссылок и без пути от стартового URL.broken— внутренние цели, которые были запрошены и ответили 4xx/5xx.failed— внутренние цели, которые запрашивались, но не дали статус.unvisited— внутренние цели, которые не запрашивались, например из-за исчерпания бюджета URL.
unvisited — неизвестно, а не «бито». Редирект, ведущий на здоровую страницу, является узлом, а не находкой. /a и /a/ остаются разными, пока запрос не докажет обратное, поэтому граф не сливает их до HTTP-проверки.
Правила
Правила — чистые функции контекста обхода: без сети, часов и файловой системы. Каждый модуль лежит в crawl/rules/rule_<slug>.py и экспортирует объект RULE; обнаружение автоматическое. Запускающий код проставляет id и серьёзность правила, поэтому ошибочное правило не может само себе приписать другой id или уровень.
| Серьёзность | Id правил |
|---|---|
critical | noindex-on-200, robots-blocked, server-error, sitemap-url-noindex |
high | broken-internal-link, canonical-mismatch, client-error, hreflang-not-reciprocal, hreflang-target-not-ok, jsonld-event-required, jsonld-required, mixed-content, redirect-chain, sitemap-url-not-ok, soft-404, title-duplicate, title-missing |
medium | canonical-missing, h1-missing, hreflang-missing-x-default, html-lang-missing, html-lang-mismatch, image-alt-missing, meta-description-duplicate, meta-description-missing, thin-content |
low | jsonld-organization-duplicate, h1-multiple, jsonld-organization, title-length |
Две эвристики стоит знать:
thin-contentпомечает URL, похожие на статьи, с менее чем 200 словами, если только на странице не больше 20 внутренних ссылок и она не выглядит листингом.soft-404помечает страницу 200, в заголовке которой есть «not found», «404» или русское «не найдена», либо неглавную HTML-страницу с менее чем 30 словами.
Правила сообщают то, что наблюдал обход. Они не объявляют необойдённый URL битым и не заменяют ручную проверку бизнес-смысла.
Сохранённые страницы и проблемы
lv24-seo init создаёт <config dir>/data и пишет относительный data_dir, привязанный к конфигу. Когда каталог существует, crawl открывает data/seo.sqlite3 и сохраняет страницы и проблемы под id свойства обхода. Недельный отчёт читает последние сохранённые проблемы для своего раздела; обход без хранилища всё равно возвращает полный JSON, но ничего не сохраняет.
Без --json CLI печатает короткую человекочитаемую сводку. С --json конверт содержит summary, issues, graph, errors, blocked и sitemap_coverage. MCP seo_crawl_site фильтрует проблемы по include_rules, ограничивает возвращаемый список 500 и сообщает issues_shown, issues_truncated и issues_filtered_by в meta.
Source: _source/engine-data-sources.md, _source/engine-cli.md, _source/engine-releases-0.5.2.md.