xslx на входе, pptx на выходе: зачем я написал генератор презентаций на Python и как он устроен

понедельник, сент. 28, 2026 | 8 минут чтения

Большинство моих небольших проектов начинаются не с желания что-нибудь запрограммировать, а с повторяющейся рабочей задачи. В случае с deck-builder такой задачей была ежемесячная презентация книжных новинок, которую мы собирали вручную.

Я решил её автоматизировать. В этом посте расскажу, зачем понадобился deck-builder, как он вырос из скрипта в Google Colab в сервис для команды и почему сейчас его архитектура устроена именно так.

Раз в месяц моя команда готовит дайджест новинок — презентацию с книгами, которые выходят в следующем месяце.

Раньше её вручную собирал младший редактор. Сначала нужно было выгрузить из 1С издательский план. Потом найти для каждой книги обложку в базе, перенести информацию в PowerPoint, расставить всё по шаблону и собрать презентацию. После этого показать её редакторам, ответственным за книги, получить исправления и снова вручную внести их в файл. В следующем месяце — повторить.

Сам по себе этот процесс не был особенно сложным. В этом как раз и заключалась проблема: большая часть работы представляла собой механическое преобразование уже существующих структурированных данных в другой формат.

В плане могло быть около 90 новинок. Собрать презентацию такого размера вручную означало потратить на неё слишком много времени, поэтому в реальности в дайджест попадала лишь небольшая часть книг.

К этому добавлялись обычные последствия ручной работы: ошибки при переносе данных и постепенно расползающаяся вёрстка. Мне же хотелось получить прямо противоположный результат: полный дайджест, собираемый быстро и всегда оформленный в одном брендированном стиле.

Так появился deck-builder.

Что именно я хотел автоматизировать

Если убрать PowerPoint, задача оказалась довольно простой.

На входе есть таблица, в которой одна строка соответствует одной книге. В ней уже есть ISBN, название, авторы, аннотация, серия, формат, количество страниц, тираж и другие характеристики. По коду номенклатуры можно получить обложку из CDN.

На выходе нужна презентация:

  • титульный слайд;
  • по слайду на каждую строку — обложка, информация о книге и пара дополнительных визуальных элементов;
  • все книжные слайды одинаковые по структуре.

То есть в упрощённом виде весь pipeline можно представить так:

Excel
  ↓
проверка и нормализация данных
  ↓
данные книги + обложка
  ↓
layout слайда
  ↓
PowerPoint

Здесь нет творческих решений, которые человек должен принимать при создании каждого конкретного слайда. Раз результат определяется набором формальных правил, это хороший кандидат на автоматизацию.

Первая версия

Первый вариант я сделал в Google Colab. Это был именно MVP: мне хотелось проверить, смогу ли я вообще взять Excel и с помощью python-pptx получить нужную мне презентацию.

Получилось, но proof of concept и инструмент, которому можно доверить 90 реальных книг, — разные вещи.

Например, первая версия не умела нормально работать с длинным текстом. Название или аннотация могли выйти за предназначенную для них область. Обложки тоже оказались интереснее, чем я ожидал: у книг разные пропорции, поэтому нельзя просто задать каждой картинке одинаковые width и height — изображение будет искажено.

Так появились проверка входного Excel, обработка отсутствующих обложек, конфигурация слайда, экспорт.

Я быстро понял, что код в ноутбуке Colab в моём случае не лучше ручной работы. Во-первых, младший редактор не умеет запускать код из ноутбука, а делать это самому значило бы добавить себе работы. Во-вторых, есть ещё несколько типичных презентаций в брендированном дизайне, которые делает моя редакция, — а значит, и их тоже можно автоматизировать в будущем.

Как сейчас устроен deck-builder

В проекте я использую четыре основные библиотеки:

polars + fastexcel   → Excel и данные
Pillow               → изображения
python-pptx          → PowerPoint
Typer                → CLI

Самое важное для меня — разделение ответственности между частями приложения. Архитектуру можно примерно представить так:

                     ┌──────────────┐
                     │     CLI      │
                     └──────┬───────┘
                            │
                            ▼
                     ┌──────────────┐
                     │ TitlesDigest │
                     └──────┬───────┘
                            │
             ┌──────────────┼──────────────┐
             │              │              │
             ▼              ▼              ▼
        ┌─────────┐   ┌──────────┐   ┌──────────┐
        │  Excel  │   │  Config  │   │  Assets  │
        │ loading │   │ / layout │   │ / images │
        └────┬────┘   └─────┬────┘   └────┬─────┘
             │              │              │
             └──────────────┼──────────────┘
                            ▼
                     ┌──────────────┐
                     │    Build     │
                     │ python-pptx  │
                     └──────┬───────┘
                            │
                            ▼
                     ┌──────────────┐
                     │    Export    │
                     │    .pptx     │
                     └──────────────┘

1. Excel — это входной контракт

Генератор презентаций не должен гадать, что имелось в виду во входных данных, поэтому Excel здесь фактически выступает API. У него есть определённая схема: обязательные колонки и ожидаемые типы данных.

При загрузке deck-builder проверяет наличие нужных полей и пытается строго привести их к нужным типам. Дополнительные колонки при этом можно оставить, генератор их проигнорирует. Например:

Код ном-ры                → str
ISBN                      → str
Наименование на обложку   → str
Авторы на обложку         → str
Кол-во стр                → Int16
Тираж                     → Int64

Это одна из самых важных частей: чем раньше система может отказаться работать с неправильными данными, тем проще понять, что произошло.

2. TitlesDigest собирает процесс вместе

Сейчас центральный объект проекта — TitlesDigest. На уровне Python API сценарий выглядит примерно так:

digest = TitlesDigest.from_excel("books.xlsx")

if digest.df is None:
    raise ValueError("Некорректная таблица")

digest.build(
    title_text="Дайджест новинок",
    suptitle_text="Октябрь 2026",
).to_pptx("digest.pptx")

Мне нравится эта часть архитектуры, потому что здесь довольно хорошо читается сам процесс: from_excel() создаёт дайджест из исходных данных, build() строит презентацию, to_pptx() её сохраняет.

При этом сам TitlesDigest не знает, как вычислить размер картинки или каким именно вызовом python-pptx создаётся конкретный элемент. Для этого есть более низкие уровни.

3. Дизайн — это данные

В MVP я довольно быстро столкнулся с классической проблемой в программировании — магическими числами.

PowerPoint работает с координатами. Выражения вроде этих работают до первого обновления дизайна:

left = Mm(31)
top = Mm(42)
width = Mm(76)

Если такие значения разбросаны по функциям, любое изменение превращается в поиск чисел по всему проекту.

Сейчас параметры оформления я вынес в отдельную конфигурацию. PresentationConfig объединяет размер слайда, поля, типографику, цвета, ресурсы и позиции элементов разных типов слайдов. Конфигурационные объекты сделаны через dataclass и неизменяемы.

За счёт этого код, который строит презентацию, не должен одновременно определять её дизайн. И если я захочу поменять, например, шрифт, поля или расположение обложки, мне не нужно трогать сам алгоритм создания слайда.

4. Builder занимается композицией

Следующий слой — собственно построение презентации.

TitlesDigest создаёт python-pptx Presentation, устанавливает нужный размер слайда, добавляет титульный слайд, а затем проходит по строкам DataFrame и строит книжные слайды. Здесь я тоже старался не складывать всю работу в одну большую функцию.

Повторяющиеся операции вроде добавления изображения, создания текстового блока или изменения порядка shapes лучше держать отдельно. Одна из причин довольно практическая: именно такие маленькие функции проще всего тестировать.

5. Работа с изображениями

Обложка загружается с CDN по коду номенклатуры. Если изображение недоступно, генератор может вместо него поставить заглушку. Скачанные результаты кэшируются в памяти, чтобы не обращаться к CDN несколько раз за одной и той же картинкой.

После загрузки нужно решить ещё одну задачу: вписать изображение в предусмотренную дизайном область, но не изменить его пропорции. Для книжных обложек это особенно заметно: форматы изданий сильно отличаются, и растянутая обложка делает автоматически созданную презентацию похожей на автоматически созданную презентацию. Мне хотелось обратного.

6. Экспорт тоже имеет свою границу

Когда презентация построена, отдельный слой отвечает за запись .pptx. Он проверяет расширение, создаёт необходимые директории и только после этого передаёт презентацию python-pptx.

В моём коде это очень маленький модуль — и именно такие маленькие модули мне сейчас нравятся больше всего.

Чем дальше я работал над проектом, тем меньше хотелось иметь функции вида:

make_everything(...)

Из набора небольших частей, каждая из которых делает одну довольно скучную вещь, мне будет проще в дальнейшем собирать новые пайплайны.

CLI как первый интерфейс

Поверх этого появился небольшой CLI на Typer.

Локально генератор можно вызвать примерно так:

uv run deck-builder \
    input.xlsx \
    output.pptx \
    --title "Книжные новинки" \
    --suptitle "Октябрь 2026"

CLI для меня был важным промежуточным этапом: он отделил приложение от конкретного способа его запуска. Генератор не должен знать, пришли ли данные из терминала, из чата или откуда-то ещё. И это пригодилось на следующем этапе.

От CLI к сервису

Конечным пользователем проекта должен был быть не я. Однако, если для экономии времени я прошу редактора установить Python, uv, зависимости проекта, открыть терминал и научиться запускать CLI, я автоматизировал не ту часть процесса.

Сейчас команда вообще не взаимодействует непосредственно с deck-builder. Генератор задеплоен в Yandex Cloud, а пользовательский интерфейс для него — корпоративный чат в Loop.

Внутри остаётся довольно обычное Python-приложение, которое умеет превратить один файл в другой. Снаружи появляется небольшой слой интеграции, который принимает запрос из Loop, передаёт файл генератору и возвращает результат пользователю.

Мне кажется, это правильная граница для такого проекта: я могу менять способ взаимодействия с приложением, не переписывая генератор презентаций. И наоборот: могу менять внутреннюю архитектуру deck-builder, не заставляя пользователей учиться работать с новым инструментом.

Зачем здесь тесты

Самой сложной частью проекта для меня неожиданно оказался не PowerPoint, а тесты.

До этого я никогда не изучал их достаточно системно, поэтому здесь я использовал искусственный интеллект: просил Codex помогать писать тесты, разбирать фикстуры, искать недостаточно полезные проверки и объяснять, почему тот или иной тест устроен именно так.

И тесты довольно сильно повлияли на саму архитектуру. Проверить функцию calculate_image_fit(...) сильно проще, чем функцию build_the_entire_presentation_and_do_everything(...). Необходимость тестировать код сама подталкивает разбивать его на меньшие части с чёткими входами и выходами.

Итог

Если смотреть на deck-builder только с точки зрения количества кода, это довольно небольшой проект: он читает Excel и делает PowerPoint. Но мне нравится использовать его как пример немного другого способа смотреть на внутренние рабочие инструменты.

Всё началось не с идеи написать программу. Был процесс, в котором сотрудник регулярно брал структурированные данные, переносил их руками, применял один и тот же набор правил и получал структурированный результат — это главный признак возможной автоматизации.

Первая версия доказала, что процесс можно автоматизировать. Следующая задача была уже инженерной: отделить данные от представления, представление от конфигурации, генерацию от интерфейса, а сам инструмент — от среды, в которой им пользуются.

В результате сейчас у проекта есть несколько довольно чётких границ:

Excel          → входные данные
loader         → проверка и преобразование
config         → правила оформления
builder        → сборка презентации
core           → повторно используемые низкоуровневые операции
export         → готовый PPTX

────────────────────────────────────

Loop.ru        → интерфейс пользователя
Yandex Cloud   → выполнение
deck-builder   → предметная логика

Именно этой частью проекта я доволен больше, чем самой генерацией дайджестов. Я не разработчик, моя основная работа — управление продуктами и командами в издательстве. Но код даёт мне дополнительный способ решать управленческие задачи.

Иногда оптимизация — это новый процесс. Иногда — изменение ответственности. А иногда полезнее написать программу и сделать так, чтобы процесс, который мы собирались оптимизировать, больше вообще не был нужен.

© 2022 - 2026 Кирилл Игнатьев

🌱 Powered by Hugo with theme Dream.

Обо мне

Больше десяти лет работаю в издательском бизнесе. За это время прошел путь от редактора до руководителя структурного подразделения.

Сейчас отвечаю за стратегическое и операционное управление бизнес-юнитом: развиваю продуктовый портфель, запускаю новые продуктовые линейки и масштабирую существующие категории.

Мне особенно интересны задачи на пересечении продукта, бизнеса и данных. Я работаю с большими ассортиментными портфелями и пишу для этого собственные инструменты на Python.

Опыт работы

В настоящее время руковожу одним из подразделений издательства АСТ с полной ответственностью за P&L, продуктовую стратегию и развитие портфеля.

В управлении более 5 000 SKU, команда из 28 человек и бизнес с оборотом более 800 млн рублей.

В основе моей работы — ассортиментная стратегия, анализ спроса и оборачиваемости, unit-экономика и управление маржинальностью. Также занимаюсь развитием международных лицензий и партнерств в Азии, США и Европе.

За 2021–2025 годы выручка подразделения выросла на 146 %, прибыль — на 168 %, рентабельность продаж — на 8 п. п. Доля рынка в одной из ключевых категорий выросла с 26 % до 55 %.

Образование
  • MBA Стратегический менеджмент
    Финансовый университет при правительстве РФ
  • Управление книжным бизнесом
    Московский государственный университет печати им. И. Федорова
  • Магистр лингвистики
    Московский городской педагогический университет
  • Иностранный язык, специалист
    Московский городской педагогический университет
Курсы
  • Applied Data Science Lab
    WorldQuant University
  • Data Analysis with Python
    University of Helsinki, Department of Computer Science
  • Advanced Course in Python Programming
    University of Helsinki, Department of Computer Science
  • Introduction to Python Programming
    University of Helsinki, Department of Computer Science
Выступления и обмен опытом

LTI Korea Translation Academy (Сеул, Республика Корея), ментор русскоязычных переводчиков, 2021—2026

KPIPA Overseas Publishing Market Report 2021 (Сеул, Республика Корея), автор отчета о российском книжном рынке, 2022

KPIPA Overseas Publishing Market Report 2018 (Сеул, Республика Корея), автор отчета о российском книжном рынке, 2019

Korean Literature Showcase 2019 (Сеул, Республика Корея), спикер, 2019

International Translation & Publication Workshop (Сеул, Республика Корея), ментор русскоязычных переводчиков, 2019

Контакты