Карта сайта для магазина с миллионом карточек это не файл, а маленькая инфраструктура: индекс, десятки тематических частей, генерация на стороне сервера и дисциплина вокруг даты последнего изменения. Ошибка в архитектуре карты видна не сразу: поисковик просто начинает узнавать о новых товарах с опозданием в недели, а отдел маркетинга ищет виноватых в падении продаж. Разберём рабочую структуру, генерацию, ловушки поля lastmod и контроль, который удерживает карту в порядке годами.
Лимиты протокола на один файл и арифметика карты на миллион адресов
Протокол ограничивает один файл карты пятьюдесятью тысячами адресов и пятьюдесятью мегабайтами несжатого XML. Для миллиона карточек минимальная арифметика даёт двадцать файлов, но на практике частей делают больше. Комфортный размер отдельной части: от десяти до тридцати тысяч адресов. Небольшие части быстрее генерируются, проще проверяются и легче отлаживаются: сбой в одной части не рушит всю карту целиком, а перегенерация затрагивает только затронутый раздел каталога.
Сверху стоит файл индекса, который ссылается на все части. По протоколу индекс содержит до пятидесяти тысяч ссылок, поэтому миллион адресов свободно помещается в двухуровневую структуру. Третий уровень запрещён: индекс не может ссылаться на другой индекс, вложенность не строят. Все части обязаны лежать на том же хосте, что и сам индекс, а в панели поисковика структуру удобно контролировать списком частей с числом адресов в каждой.
Даже уложившись в лимит адресов, легко превысить лимит мегабайт: длинные адреса карточек, разметка hreflang для нескольких языков и пробелы между тегами съедают объём быстро. При генерации контролируйте оба лимита сразу, иначе поисковик обработает файл лишь частично, и хвост списка останется вне обхода. Пробелы и переносы внутри XML допустимы, но на миллионе адресов файлы собирают компактно, без красивой разметки.
Разделение карты по типам страниц и разделам каталога
Логика деления двухступенчатая: сначала по типам страниц, потом по крупным разделам каталога. Товары, категории, статьи и служебные страницы живут в отдельных частях. Товарная часть режется по разделам: обувь, одежда, электроника, спорт. Имена файлов в стиле sitemap-products-shoes.xml читаются без раскрытия и упрощают разговоры в чате разработки: сразу ясно, о какой части речь и кого звать, если в ней пусто.
Выгода деления проявляется в панелях вебмастеров: статистика по каждой части сразу показывает, где проблема с индексацией. Если часть обуви наполовину вне индекса, а часть электроники практически целиком в выдаче, вопрос не к карте, а к разделу: там и ищут тонкий контент, битые ссылки и конфликты канонических адресов. Вторая выгода это частичная перегенерация: за ночь обновляется только раздел, где были правки, а не вся карта. Третья это мониторинг: счётчик адресов в части сверяется с базой автоматически, и расхождение ловит баг генерации раньше, чем его заметит поисковик.
Карта крупного магазина на практике выглядит скромно: один индекс, пара частей категорий, часть статей и десятки товарных частей по разделам. Такое деление живёт годами: новый раздел каталога получает свою часть, старый уходит удалением строки из индекса.
Отдельная тема крупных магазинов это языковые версии. У мультиязычной витрины части делят ещё и по языкам, а связь вариантов описывают прямо в карте разметкой hreflang: она экономит дублирование тегов на каждой странице, но требует аккуратности генератора, потому что забыть один вариант из пары легко, и поисковик начнёт жаловаться на неполные наборы ссылок.
<url>
<loc>https://example.com/catalog/shoes/100234/</loc>
<xhtml:link rel="alternate" hreflang="ru" href="https://example.com/catalog/shoes/100234/"/>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/catalog/shoes/100234/"/>
<lastmod>2026-04-18</lastmod>
</url>
Разметка hreflang требует объявления пространства имён xhtml в корневом теге urlset, иначе файл не пройдёт проверку.
Генерация карты на стороне сервера вместо ручного файла
Ручная карта на миллион адресов не существует в природе: файлы собирает код. Два рабочих подхода: статическая сборка по расписанию и динамическая отдача по запросу. Статика надёжнее: обходчик генерации пишет файлы на диск, веб-сервер отдаёт их как обычные статические файлы с кэшированием и сжатием. Динамика гибче: адрес части обрабатывается контроллером, который собирает XML из базы с кэшем на несколько часов. Правило одно: тяжёлый запрос к базе нельзя выполнять на каждый заход робота, иначе карта станет самым дорогим адресом магазина.
Частота генерации подбирается под скорость изменения каталога. Магазин с ежедневной выгрузкой поставок обновляет товарные части раз в сутки, категории раз в неделю, статьи раз в месяц. Момент публикации тоже подбирают: свежие части появляются рано утром, чтобы генерация не грузила базу в пиковые часы, а индекс пересобирается после сборки всех частей.
Пример индекса и фрагмент товарной части:
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://example.com/sitemap/products-shoes.xml</loc>
<lastmod>2026-04-18</lastmod>
</sitemap>
</sitemapindex>
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/catalog/shoes/100234/</loc>
<lastmod>2026-04-18T09:14:00+03:00</lastmod>
</url>
</urlset>
В реальном файле десятки тысяч блоков url, здесь один для наглядности. Дата в индексе обновляется при изменении части: поисковик использует её, чтобы решить, стоит ли перекачивать файл целиком. Специальные символы в адресах кодируются, амперсанд экранируется, адреса в блоках указываются полностью, с протоколом и доменом.
Ловушки поля lastmod и причины потери доверия поисковиков к дате
Поле lastmod выглядит безобидно, но именно от него зависит, полезна карта или бесполезна. Google прямо предупреждает: дата используется, только если она точно и стабильно отражает изменения содержания. Иначе значение игнорируется, и карта превращается в простой список адресов без приоритетов. Главная ловушка номер один: проставлять lastmod равным дате сборки карты. Тогда дата у всего файла меняется каждую ночь, и сигнал теряет смысл целиком.
Ловушка вторая: дергать дату при любой мелочи, например при колебании остатка на складе, хотя видимое содержание страницы не поменялось. Ловушка третья: дата в будущем из-за ошибки часового пояса. Ловушка четвёртая: страница изменилась, а дата осталась старой, например потому, что lastmod берётся из технического поля, которое обновляется только при правках администратора.
Формат даты описан стандартом: от короткого вида до полной записи с часами, минутами и часовым поясом. Яндекс по документации читает lastmod как время последнего изменения и учитывает его при планировании повторного обхода, поэтому точная запись с зоной полезна в обеих системах. Правило честности звучит просто: дата меняется тогда, когда меняется видимое для пользователя содержание страницы: описание, фотографии, цена, характеристики. Внутренние перерасчёты дату не двигают.
Проверить себя несложно: выберите двадцать случайных адресов со свежей датой и убедитесь, что на страницах действительно что-то изменилось с прошлой отметки. Такая выборочная проверка раз в месяц удерживает карту в статусе достоверного источника и спасает главный ресурс: доверие поисковика к вашим сигналам.
Автоматизировать честность можно прямо в генераторе. Дата берётся из поля, которое обновляется триггером при изменении содержательных полей карточки, а не из даты записи в таблице. В лог генерации печатается число адресов с изменившейся датой: если за ночь дата прыгнула у половины каталога, сборка останавливается с уведомлением. Такие предохранители превращают честность lastmod из вопроса дисциплины в свойство системы.
Чего не должно быть в карте на миллион адресов
Карта это список рекомендованных адресов, и мусор в ней обходится дорого: каждый битый адрес тратит бюджет обхода и размывает доверие к остальному списку. Внутри не место адресам с кодами перенаправления: робот следует за редиректом и теряет запрос. Не место страницам с ошибками 404 и 410, адресам, закрытым в robots.txt, потому что робот физически не сможет их прочитать, а панель покажет ошибку. Не место страницам с метатегом noindex и неканоническим вариантам: адрес с параметром, у которого канонический тег указывает на чистую версию, только путает алгоритм.
Отдельный класс мусора это дубли одного адреса в разных частях. Карточка, попавшая в товарную часть и в часть категорий, создаёт лишнюю работу и подозрение в неряшливости. Убирается это на уровне генерации: источник адресов один, это основная таблица каталога, а не несколько выборок из разных модулей.
Контроль мусора на миллионе адресов автоматизируют: раз в ночь выборка из каждой части прогоняется по кодам ответа и содержанию метатегов, отчёт с отклонениями уходит разработчикам утром. Полный прогон всей карты еженедельно избыточен, по частям за ночь он незаметен для сервера.
Вопрос полноты звучит на каждом проекте: включать ли в карту адреса, которым не место в выдаче. Ответ для крупного магазина один: карта содержит только адреса, полезные в поиске. Служебные страницы, результаты внутреннего поиска и случайные комбинации фильтров в карту не попадают, и это единственный случай, когда меньшее количество в карте означает лучший результат.
Сжатие и отдача больших карт по HTTP с контролем версий
Файлы сжимают gzip: на типичной карте сжатие снижает объём в несколько раз, отдача ускоряется. Заголовки кэширования дают понять роботу, как часто перекачивать файл. Заголовки ETag и Last-Modified включают условный запрос: поисковик спрашивает, изменился ли файл, и получает пустой ответ с кодом 304 вместо повторной перекачки гигабайт. Для статики это умеет nginx из коробки, для динамических частей контроллер обязан поддерживать условные запросы сам, иначе каждая загрузка карты тянет из базы миллион строк.
Техническая мелочь, которая регулярно всплывает на переездах: адрес самой карты и её частей не должен отвечать перенаправлением. Робот заходит по адресу из robots.txt, получает редирект и в лучшем случае тратит лишний запрос, в худшем отмечает ошибку в панели. При переименовании раздела каталога адреса частей меняются одновременно с индексом, а старые адреса продолжают отвечать кодом 200 хотя бы месяц.
Второй пласт это версионирование. Каждая генерация пишется в хранилище с меткой времени и счётчиками адресов, индекс обновляется атомарно, целиком, а не по частям. Предыдущая версия хранится для быстрого отката: если генератор выдал файл с битыми адресами, откат занимает минуты, а не вечер. Счётчики адресов по частям выводятся в мониторинг с уведомлением при отклонении: часть стала короче на десять процентов, и это случилось в ночь релиза, значит релиз и есть причина.
Подключение карты к панелям вебмастеров и регулярный мониторинг частей
Строка Sitemap в robots.txt сообщает о карте всем роботам сразу, но её дублируют явной отправкой в панелях Google Search Console и Яндекс.Вебмастера: там появляются отчёты по каждой части, число обработанных адресов и ошибки чтения. Панели же дают главный контрольный сигнал: сколько адресов из карты реально попало в выдачу, а сколько осталось вне индекса и по каким причинам.
Минимальный ежемесячный контроль карты выглядит так:
1. сверить число адресов в каждой части с числом карточек в базе данных;
2. прогнать адреса по кодам ответа и каноническим тегам на выборке из каждой части;
3. проверить, что lastmod менялся только у действительно изменённых страниц;
4. сравнить статистику индексации по каждой части в панелях вебмастеров;
5. зафиксировать версию карты и сохранить копию для быстрого отката.
Отчёты панелей читают вместе, а не по очереди: расхождение статистики по одной части подсказывает, что вопрос в конкретной системе, а расхождение по всем частям сразу указывает на проблему самой карты.
Главное в карте на миллион адресов помнить её роль: она не заменяет чистую структуру сайта и не лечит дубли, но честно собранная и разделённая на части карта экономит недели ожидания, когда речь заходит о скорости индексации новых карточек. Полнота важна, но честность важнее: лучше отдать пятьсот тысяч адресов с правдивыми датами, чем миллион с шумом вместо сигнала.