Десктоп-генератор промо-лендингов: бриф или ссылка → готовая страница
Собственный инструмент, написанный под свой же поток работы. Промо-страницы делались копипастой предыдущих — с ручной правкой мета-тегов, текстов, слотов изображений и палитры. Собрал десктоп-приложение, которое собирает страницу из шаблона и брифа (либо снимает контент с существующей страницы по ссылке) и выгружает готовый архив на хостинг по API.
Каждая новая страница — это копипаста предыдущей
Промо-страница под новый товар или новый язык делалась одинаково: продублировать папку предыдущей,
пройтись руками по всей разметке и переписать то, что относится к старому проекту. Заголовок,
meta description,
весь блок og:*,
фавикон, конфиг с названием магазина и ценами, вводные тексты, вопросы блока опроса, отзывы,
блок предложения — и только потом изображения.
Изображения — отдельный источник ошибок. У шаблона есть фиксированные слоты: логотип на первом экране, логотип в шапке, фавикон, фон страницы, фото товара, картинка для соцсетей. Файлы из источника приходят вперемешку с декоративными иконками, аватарами и бейджами — и в слот логотипа регулярно залетало не то. Плюс никто не следит за весом: исходник на несколько мегабайт уезжает на прод как есть.
Финальный шаг тоже ручной: собрать папку в архив, зайти в панель хостинга, создать группу, загрузить zip, разобраться с конфликтом имён, если такая страница уже была. Один цикл — терпимо. Когда страниц несколько в неделю и на каждую есть варианты по языку и палитре — это уже не работа, а обслуживание собственной копипасты.
Десктоп-приложение, которое читает источник и собирает страницу
Инструмент — обычное окно на tkinter с вкладками: проект, контент, палитра, изображения, опрос, отзывы, публикация, лог. Никакого сервера и сборки: python-файл запускается двойным кликом (обёртки под macOS и Windows лежат в репозитории). Логика одно-направленная — источник разбирается, шаблон заполняется, результат уезжает на хостинг.
Папка с материалами и брифом — либо ссылка: встроенный загрузчик снимает HTML и ассеты
Заголовки, мета, названия, цены, тексты, вопросы, отзывы, палитра из CSS
Авто-определение логотипа, фавикона, фона, товара по разметке; ручная правка в GUI
Шаблон + контент → выходная папка, изображения пережимаются, разметка самопроверяется
ZIP в память → Admin API хостинга: группа, имя по шаблону, конфликт → перезапись
Два семейства шаблонов — один интерфейс
В репозитории живут два шаблона:
template_1
на Bootstrap и
template_2
— статический экспорт Astro-сборки с атомарными утилитами в духе Tailwind. Разметка,
имена файлов и набор слотов у них разные, поэтому при переключении пресета приложение
перестраивает вкладки: свой набор слотов изображений, свой набор цветовых ключей, свои
правила подстановки. Наружу — один и тот же сценарий работы.
Ссылка вместо папки: встроенный загрузчик
Источником контента может быть не только локальная папка, но и URL уже существующей страницы. Загрузчик написан на стандартной библиотеке и ведёт себя как аккуратный браузер:
- учитывает
<base href>при разрешении относительных путей - собирает ассеты из
img/link/script/source/srcsetи изurl(...)в инлайн-стилях - рекурсивно обходит CSS:
url()и@import— шрифты и фоны не теряются - качает только тот же origin — внешние CDN остаются ссылками, а не тянут за собой пол-интернета
- перед разбором вырезает тела
<script>, иначе строки внутри инлайн-JS ловятся какsrc=
Логотип, фавикон и фон определяются по разметке, а не по имени файла
Первая версия раскладывала картинки по слотам эвристиками «имя + размер файла» — и именно она
подставляла в шапку случайный мусор. Сейчас приоритет отдан разметке: фавикон берётся из
<link rel="icon">,
логотип — из шапки и из alt/class
со словами logo/brand, фон — из
background-image: url(...)
у body/html,
картинки отзывов — из соответствующих блоков. Эвристики по имени и весу остались только запасным
вариантом, а декоративные иконки, аватарки и бейджи вынесены в чёрный список — они больше никогда
не претендуют на слот логотипа. Всё, что автомат не смог решить, остаётся видимым в интерфейсе и
правится вручную.
Палитра снимается с источника, а не подбирается на глаз
Приложение читает HTML вместе с подключёнными CSS и достаёт цвета из переменных темы. Берётся
последнее объявление — тема должна перекрывать базовые стили, а не наоборот. Токены нормализуются
из hex,
rgb()
и oklch(),
серое и почти белое отбрасывается как неинформативное, а оставшееся раскладывается по семантическим
ключам: кнопка, текст кнопки, шапка, фон страницы, карточка, акцент. Дальше палитра доступна
в интерфейсе как обычные поля — правится точечно.
Проект — это JSON, а не папка-снежок
Состояние сборки сохраняется в файл проекта: выбранный шаблон, пути, весь контент, палитра, маппинг слотов, вопросы, отзывы. Повтор или вариант делается открытием проекта и правкой двух полей, а не копированием каталога. Побочный эффект — сборка воспроизводима и версионируется наравне с кодом.
Изображения пережимаются на выходе
Каждый файл, попадающий в слот, проходит через Pillow: ресайз по большей стороне, сохранение прозрачности для PNG/WebP, разумное качество на выходе. Если Pillow недоступен — тихий откат на обычное копирование, генерация не падает. Это одна из тех мелочей, которые невозможно дисциплинированно делать руками на каждой странице.
Самопроверка разметки перед публикацией
У готовой страницы есть обязательные элементы, которые легко потерять при правке шаблона: скрытые плейсхолдеры, которые хостинг подставляет при выдаче, и единый идентификатор события для браузерного и серверного контуров аналитики. Генератор проверяет их наличие на выходе, при пропаже восстанавливает недостающее и пишет предупреждение в лог. Мотив простой: правка шаблона не должна тихо ломать учёт событий — молчаливые нули обнаруживаются слишком поздно.
Публикация — одно действие, включая конфликт имени
Готовая папка пакуется в ZIP прямо в памяти (системный мусор вроде
.DS_Store
отсеивается), кодируется в base64 и уходит в Admin API хостинга. Список существующих групп
подтягивается в выпадающий список, имя страницы собирается по шаблону вида
{store} - {product} - {template} - {lang}.
Ответ 422
«имя занято» не роняет процесс: приложение находит существующую запись и спрашивает, перезаписать ли
её — при согласии уходит PUT
по найденному id. Ключ доступа хранится в локальном конфиге с правами
600
вне репозитория.
Дизайн-система рядом с кодом
В репозитории лежат три брифа: общие основания (типографика, сетка, состояния, мобильная адаптация, бюджет производительности) и по одному документу на каждое семейство шаблонов — с разбором селектор за селектором, списками того, что удалить и что добавить, и чек-листом проверки. Смысл в том, что генератор гарантирует структуру, а брифы — визуальный уровень: иначе автоматизация просто быстрее тиражирует посредственную вёрстку.
Десктоп без сборки — запускается двойным кликом
Локальное приложение без сервера и без шага сборки
Разбор исходной страницы: мета, тексты, цены, вопросы, отзывы
Загрузчик страницы по URL и клиент Admin API хостинга
Превью в интерфейсе и пережатие изображений на выходе
Упаковка результата в памяти и выгрузка архива по API
Всё состояние сборки в файле — воспроизводимость и варианты
Первое семейство шаблонов со своим набором слотов
Второе семейство: статическая сборка на атомарных утилитах
Дизайн-система: общие основания + бриф на каждое семейство
Обёртки запуска — конечному пользователю Python не нужен
Страница собирается из проекта, а не из памяти
ручная правка разметки заменена одним запуском генерации
архив, группа, имя и конфликт имени — внутри инструмента
коммитов — каждая правка пришла из реальной сборки, а не из плана
Ушёл целый класс ошибок, а не просто время
Забытый og:image,
фавикон от прошлого проекта, логотип не в том слоте, картинка на несколько мегабайт, потерянный при
переносе фон — всё это раньше ловилось глазами уже на живой странице. Теперь эти вещи либо выведены
в интерфейс как явные поля, либо проверяются автоматически перед выгрузкой.
История коммитов — это список пойманных граблей
Инструмент дожимался на реальных сборках, и почти каждый коммит — конкретный дефект: логотип, растущий по субпикселю из-за анимации; относительные пути, ломающиеся при переносе; конфликт имён при повторной выгрузке, который раньше просто ронял процесс; мелкие иконки, которые эвристика принимала за логотип. Такие вещи не проектируются заранее — они находятся в работе, и поэтому инструмент под свой процесс имеет смысл писать самому.
Честные границы
Это внутренний инструмент, а не продукт: интерфейс не покрыт UI-тестами, а качество разбора напрямую зависит от аккуратности разметки источника. На нестандартном источнике автомат заполняет часть полей мимо — поэтому всё, что он определил, остаётся редактируемым, а лог генерации показывает каждый шаг. Такой контур честнее «магии»: пользователь всегда видит, что именно подставлено и откуда.
Где ещё ложится та же методология
Это кейс не про лендинги. Это типовая задача «шаблон + структурированный контент → готовый статический артефакт → автопубликация». Контур переносится почти без изменений везде, где однотипные страницы делаются руками:
- → Мультиязычные и мультибрендовые страницы — одна сборка, разные проекты: язык, палитра, логотип, тексты
- → Посадочные под каждый SKU из товарного фида — генерация партиями вместо ручной вёрстки карточек
- → Тиражирование сайтов сети или франшизы — общий шаблон, свои контакты, цвета и логотип на каждую точку
- → Перенос существующей страницы на новый шаблон — загрузчик снимает исходник, парсер вытаскивает контент и палитру
- → Пакетная подготовка вариантов для сравнительных тестов — те же тексты, другая палитра и слоты, публикация одной очередью
- → Тот же код без интерфейса — как CLI-шаг в CI: проект в JSON на входе, опубликованная страница на выходе
- Загрузчик статической страницы: рекурсивный обход CSS, ограничение по origin, сохранение структуры путей
- Слой определения ролей изображений: сначала разметка, потом эвристика, всегда — ручная правка в интерфейсе
- Снятие палитры из CSS-переменных: hex / rgb / oklch, приоритет темы над базой, отсев серого
- Формат проекта в JSON — сборка воспроизводима, версионируется и переиспользуется как основа варианта
- Самопроверка обязательных маркеров разметки перед публикацией — с восстановлением и предупреждением в логе
- Публикация в хостинг/CMS по API: ZIP в памяти, base64, обработка конфликта имени через перезапись
Если страницы делаются копипастой предыдущей — это превращается в генератор
Инструмент под свой процесс окупается там, где однотипная страница делается не раз в квартал, а еженедельно. Начинать имеет смысл с одного шаблона, одного формата источника и одной точки публикации — остальное дорастает из реальных сборок.
Аудит за 5 000 ₽ — с конкретным отчётом и сметой
Расскажу что внедрить в вашем бизнесе в первую очередь, какая будет окупаемость, и нужен ли вообще AI для вашей задачи (иногда — нет).
Или просто напишите свой вопрос — отвечу в течение 2 часов