Задача: одна таблица данных, полторы тысячи страниц
На одном из контентных порталов, которые я веду, был раздел «Афиша концертов». Идея простая: человек ищет в Яндексе или Google «концерты в Казани» — и находит страницу именно про концерты в Казани, а не общий раздел «афиша» с фильтром по городу. То же самое с артистами: страница конкретного исполнителя, а не карточка в общем списке.
Проблема в том, что городов, под которые есть смысл делать отдельную страницу, — сотни. Плюс артисты, у каждого из которых свои концерты в разных городах. В сумме получилось около 1455 страниц. Написать это руками нереально: даже по 5 минут на страницу — это больше 120 часов чистого времени, и всё равно контент устареет через неделю, потому что концерты появляются и отменяются постоянно.
Значит, страницы нужно генерировать программно. Но у программной генерации есть репутация: поисковики её не любят, если это выливается в тысячи клонов с подставленным названием города. Тут и была основная развилка — как получить масштаб, не попав под фильтр за тонкий контент.
Паттерн «сущность = страница = запрос»
Первое решение, которое определило всю архитектуру: не делать одну универсальную страницу «Афиша» с фильтрами, а разложить контент на сущности, и каждой сущности дать свой URL.
- Город — отдельная страница, заточенная под запрос вида «концерты в <город>».
- Артист — тоже отдельная сущность со своей страницей, а не строчка в таблице.
Это звучит очевидно на словах, но на практике многие программатик-SEO проекты выбирают более лёгкий путь — одну страницу с параметрами в query string или с JS-фильтром поверх общего списка. Она удобнее для разработки, но бесполезна для SEO: у неё один <title>, один H1, и поисковик просто не понимает, что это тысяча разных ответов на тысячу разных запросов.
Как только сущность становится страницей, у неё появляется собственный URL, собственный заголовок, собственный набор данных — и с этого момента её вообще можно ранжировать по узкому запросу. Это и есть суть программатик-SEO: не «одна страница с фильтрами», а «генератор, который печатает уникальные ответы под уникальные запросы».
Как это устроено технически
Технически всё держится на одном источнике правды — JSON-файле с данными о концертах (город, артист, дата, площадка). Генератор на Python читает этот файл и рендерит страницы по шаблону: для каждого города — свою, для каждого артиста — свою.
- Данные — все концерты живут в одном JSON-файле: город, артист, дата, площадка. Это единственный источник правды, который потом расходится на 1455 страниц.
- Шаблонизатор — Python-скрипт проходит по JSON и рендерит HTML для каждой страницы города и каждой страницы артиста по общему шаблону.
- Обогащение — поверх шаблона на каждую страницу добавляется уникальный текстовый блок и, для артистов, персональное фото — так шаблон перестаёт быть клоном.
- Монетизация — партнёрский виджет авиабилетов и CTA на партнёрский оффер вшиты прямо в шаблон, так что появляются сразу на всех 1455 страницах без ручной расстановки.
- Автосверка — раз в неделю отдельный скрипт сверяет JSON с актуальными данными и перегенерирует страницы, чтобы отменённые концерты не висели месяцами.
Уникализация на масштабе: без этого шаблон схлопнется
Сам по себе шаблон — это ещё не контент. Если просто подставить название города в один и тот же текст, поисковик увидит 1455 версий одной страницы и рано или поздно перестанет их индексировать: классический «thin content» на масштабе.
Поэтому уникализация была не опцией, а обязательным условием запуска. Я задал себе порог: на каждой странице должно быть достаточно уникального тела текста, которое не пересекается с соседними страницами того же типа. Для страниц городов это отдельный вводный блок под конкретный город — не просто «Концерты в X» с подстановкой, а текст, который меняет структуру и акценты в зависимости от того, что реально известно про концертную жизнь этого города. Для страниц артистов — собственное фото исполнителя на каждой странице, а не одна и та же заглушка.
Уникальность на масштабе — это не «написать 1455 разных текстов руками». Это спроектировать шаблон так, чтобы уникальные куски собирались из комбинации данных: город + список ближайших концертов + доступные медиа (фото артиста) дают достаточно разного материала, чтобы каждая страница читалась как отдельный документ, а не как клон соседней. Порог простой: если убрать название города, страница должна остаться узнаваемой сама по себе.
Отдельно важно, что это решение принималось не постфактум, как «патч от фильтра», а закладывалось в архитектуру генератора с самого начала. Дешевле спроектировать шаблон с точками уникализации сразу, чем потом разбирать 1455 страниц и добавлять текст туда, где движок этого не предусматривал.
Почему данные обязаны обновляться сами
Концертная афиша — живой организм: даты переносятся, площадки меняются, часть концертов отменяется в последний момент. Программатик-SEO страницы, построенные на таких данных, обязаны обновляться сами, иначе результат обратный ожидаемому.
Здесь есть довольно неочевидная для новичков в программатик-SEO ловушка: страница, которая когда-то была полезной, со временем превращается в источник недоверия. Пользователь заходит на страницу «концерты в Казани», видит там дату, которая прошла три месяца назад, и уходит — а вместе с ним уходит и поведенческий сигнал, на который смотрит поисковик. Массовая генерация страниц без механизма актуализации данных не масштабирует пользу, она масштабирует протухший контент.
Решение здесь тоже простое по формулировке и требует дисциплины по исполнению: отдельный скрипт раз в неделю сверяет JSON с актуальным состоянием афиши и перегенерирует страницы. Программатик-SEO без автообновления данных — это не система, а фотография системы в момент запуска.
Монетизация встроена в генератор, а не добавлена сверху
Ещё одно решение, которое сэкономило время: партнёрский виджет авиабилетов и CTA на партнёрский оффер были вшиты сразу в шаблон генератора, а не расставлялись вручную по готовым страницам.
Логика подсказки очевидна из самой сущности страницы: человек, который ищет «концерты артиста X в городе Y», скорее всего живёт не в этом городе — иначе он бы искал просто «концерты в своём городе». Значит, ему нужны билеты на дорогу. Виджет авиабилетов на такой странице — не случайная реклама, а ответ на следующий шаг в намерении пользователя.
Так как виджет и CTA живут в шаблоне, а не в отдельных правках, монетизация автоматически появляется на всех новых страницах, которые генератор создаёт при очередной сверке данных. Это тот случай, когда программатик-подход экономит не только время на контент, но и время на внедрение рекламы.
Результат и что я вынес для себя
Главный переносимый приём здесь не про концерты и даже не про Python — про принцип. Программатик-SEO работает, если у вас есть три вещи одновременно: данные, которые естественно распадаются на сущности (город, артист, товар, модель — что угодно), шаблон, который умеет добавлять уникальный кусок текста на каждую сущность, а не просто подставлять переменную, и механизм, который держит эти данные живыми без ручного вмешательства.
Если убрать любую из трёх частей, схема ломается: без разбиения на сущности вы получаете один сложный фильтр вместо тысячи целевых страниц; без уникализации — фильтр за тонкий контент; без автообновления — красивый, но протухший каталог, который теряет и трафик, и доверие пользователей. А когда все три части на месте, монетизацию можно встроить в тот же шаблон один раз — и она масштабируется вместе с контентом, без ручной работы на каждую новую страницу.