Генерация отчёта в Python: от DataFrame до готового Excel
Как собрать отчёт по продажам из CSV в готовый Excel-файл с несколькими листами, форматированием и автошириной колонок. Полный рабочий код на pandas и openpyxl.

К концу этого разбора у вас будет скрипт, который читает CSV с продажами, считает итоги по менеджерам и месяцам и складывает результат в Excel-файл с двумя листами: сводкой и детализацией. Заголовки будут выделены жирным, ширина колонок подстроится под содержимое, а суммы отформатируются как рубли. На всё уйдёт минут двадцать, если Python уже установлен.
Пример разбирается на pandas 2.x и openpyxl 3.1.x. Я не беру тяжёлые генераторы вроде отчётов по шаблону Word или PDF — это другая история. Здесь только табличный отчёт в Excel, потому что именно его чаще всего просят «сделать красиво и прислать бухгалтеру».
Что нужно до начала
Перед первой строкой кода убедитесь, что на месте всё из списка. Иначе половина шагов упадёт с ошибкой импорта или чтения файла.
- Python 3.10 или новее — проверьте командой
python --version. - Установленные библиотеки:
pip install pandas openpyxl. Pandas сам по себе умеет писать в Excel, но движок для.xlsxему даёт именно openpyxl. - Исходный файл
sales.csvв той же папке, что и скрипт. Кодировка — UTF-8, разделитель — запятая.
Для примера возьмём CSV с четырьмя колонками: date (дата в формате YYYY-MM-DD), manager (имя менеджера), product (товар), amount (сумма сделки, число). Если у вас точка с запятой вместо запятой — это частый случай выгрузок из русского Excel, — разделитель поменяем на шаге 2.
Шаг 1. Читаем данные и смотрим, что пришло
Первым делом загрузите CSV и посмотрите на него глазами. Половина проблем с отчётами — это не код, а грязные данные: пустые суммы, даты строками, лишние пробелы в именах.
# report.py — pandas 2.x
import pandas as pd
# читаем исходный файл
df = pd.read_csv("sales.csv")
# смотрим первые строки и типы колонок
print(df.head())
print(df.dtypes)Запустите python report.py. В консоли должны появиться пять строк таблицы и список колонок с типами. Убедитесь, что amount имеет тип int64 или float64, а не object — если тип object, значит, в суммах затесался текст или пустые значения.
Шаг 2. Готовим данные
Приведём дату к datetime, вычислим месяц и уберём строки без суммы. Если разделитель в вашем файле — точка с запятой, добавьте параметр sep=";" в read_csv.
import pandas as pd
df = pd.read_csv("sales.csv")
# дата из строки в настоящую дату; ошибочные значения станут NaT
df["date"] = pd.to_datetime(df["date"], errors="coerce")
# сумму приводим к числу; нечисловое станет NaN
df["amount"] = pd.to_numeric(df["amount"], errors="coerce")
# выкидываем строки без даты или без суммы
df = df.dropna(subset=["date", "amount"])
# колонка с месяцем в виде 2024-01
df["month"] = df["date"].dt.to_period("M").astype(str)
print(f"Строк после очистки: {len(df)}")После запуска в консоли появится число строк. Если оно резко меньше исходного — значит, много данных отсеялось из-за кривых дат или сумм. Это сигнал вернуться к исходнику, а не молча идти дальше.
Шаг 3. Считаем сводку
Соберём две сводные таблицы: сумма продаж по менеджерам и сумма по месяцам. Метод groupby группирует строки, agg считает по группам.
# сумма и количество сделок по каждому менеджеру
by_manager = (
df.groupby("manager")
.agg(total=("amount", "sum"), deals=("amount", "count"))
.reset_index()
.sort_values("total", ascending=False)
)
# сумма по месяцам
by_month = (
df.groupby("month")
.agg(total=("amount", "sum"))
.reset_index()
)
print(by_manager)
print(by_month)reset_index() здесь важен: без него имя менеджера станет индексом, а не обычной колонкой, и в Excel оно окажется не там, где вы ждёте. В консоли вы увидите обе таблицы — проверьте, что суммы выглядят правдоподобно.
Отчёт, который никто не перепроверил глазами перед отправкой, — это генератор ошибок с красивым форматированием. Сначала убедитесь, что цифры сходятся, и только потом наводите красоту.
Шаг 4. Пишем в Excel с двумя листами
Здесь пригодится ExcelWriter. Один контекст-менеджер — один файл, внутри можно писать несколько листов, указывая имя в sheet_name.
with pd.ExcelWriter("report.xlsx", engine="openpyxl") as writer:
by_manager.to_excel(writer, sheet_name="Сводка", index=False)
by_month.to_excel(writer, sheet_name="По месяцам", index=False)
# исходные очищенные данные на отдельный лист
df.to_excel(writer, sheet_name="Детализация", index=False)
print("Файл report.xlsx готов")Параметр index=False убирает лишнюю колонку с номерами строк — без него в первом столбце Excel появятся 0, 1, 2 и так далее. После запуска в папке появится report.xlsx. Откройте его: три листа, данные на местах, но всё выглядит уныло — колонки узкие, заголовки не выделены. Это чиним дальше.
Шаг 5. Форматируем: ширина колонок и жирные заголовки
Красоту наводим уже через openpyxl, обращаясь к книге после записи. pandas сам форматированием ячеек почти не занимается, поэтому берём объект книги из writer.
from openpyxl.styles import Font
from openpyxl.utils import get_column_letter
with pd.ExcelWriter("report.xlsx", engine="openpyxl") as writer:
by_manager.to_excel(writer, sheet_name="Сводка", index=False)
by_month.to_excel(writer, sheet_name="По месяцам", index=False)
df.to_excel(writer, sheet_name="Детализация", index=False)
# проходим по всем листам книги
for sheet in writer.book.worksheets:
# жирный шрифт для первой строки — заголовков
for cell in sheet[1]:
cell.font = Font(bold=True)
# автоширина по самому длинному значению в колонке
for column_cells in sheet.columns:
max_length = max(
len(str(cell.value)) if cell.value is not None else 0
for cell in column_cells
)
letter = get_column_letter(column_cells[0].column)
sheet.column_dimensions[letter].width = max_length + 2
print("Файл report.xlsx отформатирован")Обратите внимание: форматирование стоит внутри того же блока with, что и запись, — иначе изменения не попадут в файл. Откройте результат: заголовки жирные, колонки подстроены под содержимое. Плюс два символа в ширине — это запас, чтобы текст не упирался в границу ячейки.
Если не получилось
Пять ошибок, на которые чаще всего натыкаются при первом запуске.
- ModuleNotFoundError: No module named 'openpyxl' — движок для xlsx не установлен. Выполните
pip install openpyxl. Ошибка проpandasлечится так же. - UnicodeDecodeError при чтении CSV — файл не в UTF-8, а, например, в windows-1251. Добавьте
encoding="cp1251"вread_csv. - ValueError: could not convert string to float или суммы стали NaN — в колонке
amountчисла записаны с запятой как разделителем дробной части («1 500,50»). Замените запятую на точку и уберите пробелы доpd.to_numeric. - KeyError: 'manager' — колонки в вашем CSV называются иначе. Посмотрите вывод
df.columnsи подставьте реальные имена. - Отчёт пустой или строк почти не осталось —
dropnaвыкинул всё из-за формата дат. Проверьте, чтоdateдействительно в формате YYYY-MM-DD, либо задайте свой черезformat=вpd.to_datetime.
Что делать дальше
Готовый скрипт легко обернуть в функцию, которая принимает путь к CSV и имя выходного файла — тогда его можно запускать по расписанию через cron или планировщик задач. Следующий естественный шаг — числовой формат ячеек: чтобы суммы отображались как «1 500,00 ₽», задавайте cell.number_format у openpyxl (синтаксис форматов смотрите в документации openpyxl, раздел про number_format). Если отчётов станет много и понадобятся диаграммы прямо в файле — у openpyxl есть модуль chart, но это уже тема отдельного разбора.
Дефектный акт по фото: Осмотр по фотографиям: классификация дефектов, ссылки на нормы, требования к устранению. Готовый акт в Word.
Спросить за 80 ₽Источники: pandas.DataFrame.to_excel — официальная документация pandas, openpyxl — Working with styles
Частые вопросы
А если у меня pandas 1.x, код сработает?
Можно ли писать сразу в xls вместо xlsx?
Автоширина считает по количеству символов, а колонка всё равно узкая. Почему?
Как добавить в отчёт итоговую строку с общей суммой?
Файл открыт в Excel, а скрипт падает с PermissionError. Что делать?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.