Раздел 42 · Аналитика данных с нуля

Автоматический отчёт: скрипт с параметрами, CSV и Excel, расписание

middle~20 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Мира · маркетолог магазина настолок «Кубик»

Каждый понедельник я пишу тебе «пришли цифры за неделю». Ты присылаешь, но то в CSV, то в Excel, и столбцы каждый раз в другом порядке. Можно, чтобы отчёт приходил сам, в одно время и в одном виде?

В разведке «Кубика» мы один раз посмотрели на данные свежим взглядом. Еженедельный отчёт устроен наоборот: одно и то же каждую неделю, и главное в нём не открытия, а то, что он не ломается. В уроке про SQL из Python ты уже сохранил отчёт за период в CSV. Сегодня делаем из него скрипт, который сам проверяет выгрузку, пишет Excel на два листа и запускается по расписанию.

Из блокнота в скрипт

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

Скрипт собирает весь расчёт в одну функцию, а всё, что меняется от запуска к запуску, передаёт ей параметрами:

import argparse

import pandas as pd


def main():
    parser = argparse.ArgumentParser(description="Недельный отчёт «Кубика»")
    parser.add_argument("--week", required=True, help="понедельник недели, например 2026-06-22")
    parser.add_argument("--orders", default="orders.csv", help="выгрузка заказов")
    args = parser.parse_args()

    orders = pd.read_csv(args.orders)
    check_orders(orders, args.week)
    build_report(orders, args.week, f"report_{args.week}.xlsx")


if __name__ == "__main__":
    main()

Запуск из терминала: python report.py --week 2026-06-22. Модуль argparse из стандартной библиотеки разбирает аргументы командной строки и сам печатает подсказку по python report.py --help. Строка if __name__ == "__main__" значит «выполни main(), только если файл запустили, а не импортировали». Благодаря ей build_report можно импортировать в блокнот или в тест, и отчёт при этом не запустится сам.

Дальше по очереди наполняем три функции: проверку, расчёт и запись.

Неделя по Москве

В выгрузке created_at записан в UTC, как в базе. А неделя у Миры московская: с понедельника 00:00 по Москве до следующего понедельника. Заказ в воскресенье в 22:30 по UTC сделан в понедельник в 01:30 по Москве и относится уже к следующей неделе. Часовые пояса в pandas мы разбирали в уроке про даты:

TZ = "Europe/Moscow"


def week_orders(orders, week_start):
    """Заказы с понедельника week_start по воскресенье включительно, по московскому времени."""
    start = pd.Timestamp(week_start, tz=TZ)
    local = pd.to_datetime(orders["created_at"]).dt.tz_convert(TZ)
    in_week = (local >= start) & (local < start + pd.Timedelta(days=7))
    return orders[in_week].assign(created_at=local[in_week])

С апреля по июнь «Кубик» получил 47 заказов после 21:00 по UTC, то есть после полуночи по Москве. Каждый из них по UTC попадает во вчерашний день. За неделю с 22 июня по UTC выходит 91 заказ, по Москве 92.

Проверь вход до расчёта

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

REQUIRED = ["id", "status", "created_at", "promo_code", "items_rub"]


def check_orders(orders, week_start):
    missing = [column for column in REQUIRED if column not in orders.columns]
    assert not missing, f"в выгрузке нет столбцов: {', '.join(missing)}"
    assert pd.Timestamp(week_start).dayofweek == 0, f"{week_start} не понедельник"
    amounts = orders["items_rub"]
    assert amounts.notna().all() and (amounts >= 0).all(), "в items_rub пустые или отрицательные суммы"
    week = week_orders(orders, week_start)
    assert not week.empty, f"за неделю с {week_start} нет ни одного заказа"
    return week

assert мы ставили в конец блокнота в уроке про чистку, чтобы сверить итог. Здесь он стоит в начале и сторожит вход. Проверка схемы идёт первой: без created_at упадёт уже week_orders, и вместо «нет столбца created_at» ты увидишь невнятный KeyError.

У assert есть оговорка. Если запустить Python с флагом -O, все assert пропускаются. Для своего скрипта это редкость, но если отчёт будут запускать чужие руки, пиши if missing: raise ValueError(...): такую проверку флаг не отключит.

урок из курса

Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.