Как автоматизировать отчёт в Python

Зачем автоматизировать

Если каждый понедельник вы открываете SQL, копируете результат, оформляете в Excel и отправляете руководству — это пустая трата времени. Час в неделю — 50 часов в год. За это время вы могли бы сделать реальный анализ.

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

Простейший скрипт

Типичная структура регулярного отчёта:

import pandas as pd
from sqlalchemy import create_engine

def generate_weekly_report():
    engine = create_engine('postgresql://user:pass@host/db')

    # 1. Выгружаем данные
    query = """
        SELECT
            DATE_TRUNC('week', created_at)::date AS week,
            SUM(amount) AS revenue,
            COUNT(DISTINCT user_id) AS active_users
        FROM orders
        WHERE created_at >= CURRENT_DATE - INTERVAL '8 week'
        GROUP BY 1
        ORDER BY 1
    """

    df = pd.read_sql(query, engine)

    # 2. Базовые вычисления
    df['revenue_change_pct'] = df['revenue'].pct_change() * 100

    # 3. Формируем вывод
    latest = df.iloc[-1]
    summary = (
        f"Weekly Report ({latest['week']}):\n"
        f"Revenue: {latest['revenue']:,.0f} ₽\n"
        f"Change vs prev week: {latest['revenue_change_pct']:+.1f}%\n"
        f"Active users: {latest['active_users']:,}"
    )

    return summary, df

if __name__ == '__main__':
    summary, df = generate_weekly_report()
    print(summary)

Запуск: python weekly_report.py. Вывод — готовый текст для копирования куда угодно.

Оформление в Excel

Если нужен именно Excel-файл:

import openpyxl

def save_to_excel(df, filename):
    with pd.ExcelWriter(filename, engine='openpyxl') as writer:
        df.to_excel(writer, sheet_name='Metrics', index=False)

        # Получаем объект workbook для стилизации
        workbook = writer.book
        worksheet = writer.sheets['Metrics']

        # Жирный заголовок
        from openpyxl.styles import Font
        for cell in worksheet['1:1']:
            cell.font = Font(bold=True)

save_to_excel(df, 'weekly_report.xlsx')

Отправка в Slack

Для команды Slack часто удобнее Excel:

import requests

def send_to_slack(message, webhook_url):
    payload = {'text': message}
    response = requests.post(webhook_url, json=payload)
    return response.status_code == 200

SLACK_WEBHOOK = os.environ['SLACK_WEBHOOK_URL']
summary, df = generate_weekly_report()
send_to_slack(summary, SLACK_WEBHOOK)

Slack webhook URL создаётся в настройках Slack App. Хранить в env var, не в коде.

Для более rich messages используется Block Kit API:

def send_rich_slack(data, webhook):
    blocks = [
        {
            "type": "header",
            "text": {"type": "plain_text", "text": "Weekly Report"}
        },
        {
            "type": "section",
            "fields": [
                {"type": "mrkdwn", "text": f"*Revenue:*\n{data['revenue']:,.0f} ₽"},
                {"type": "mrkdwn", "text": f"*Change:*\n{data['change']:+.1f}%"}
            ]
        }
    ]

    payload = {"blocks": blocks}
    requests.post(webhook, json=payload)

Результат — красивое сообщение с секциями, полями, header. Намного лучше простого текста.

Если хочется сразу закрепить тему на практике — открой тренажёр в Telegram. 10 минут в день — и синтаксис в пальцах.

Отправка email

Для формальных отчётов руководству:

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication

def send_email(subject, body, to_email, attachment_path=None):
    msg = MIMEMultipart()
    msg['Subject'] = subject
    msg['From'] = 'reports@company.com'
    msg['To'] = to_email

    msg.attach(MIMEText(body, 'plain'))

    if attachment_path:
        with open(attachment_path, 'rb') as f:
            part = MIMEApplication(f.read())
            part.add_header('Content-Disposition', 'attachment', filename=attachment_path)
            msg.attach(part)

    with smtplib.SMTP('smtp.gmail.com', 587) as server:
        server.starttls()
        server.login(os.environ['EMAIL_USER'], os.environ['EMAIL_PASSWORD'])
        server.send_message(msg)

Gmail / Outlook требуют app password (не обычный пароль) для SMTP. Настраивается в account security.

Визуализация в отчёте

Для отправки графиков в Slack:

import matplotlib.pyplot as plt
import requests

def plot_and_upload(df, slack_bot_token, channel):
    fig, ax = plt.subplots(figsize=(10, 6))
    ax.plot(df['week'], df['revenue'])
    ax.set_title('Weekly Revenue')
    plt.tight_layout()

    image_path = '/tmp/weekly_revenue.png'
    plt.savefig(image_path)

    response = requests.post(
        'https://slack.com/api/files.upload',
        headers={'Authorization': f'Bearer {slack_bot_token}'},
        files={'file': open(image_path, 'rb')},
        data={'channels': channel, 'title': 'Weekly Revenue'}
    )
    return response.json()

Для Slack Bot нужно создать приложение с scope files:write.

Расписание

Чтобы скрипт запускался автоматически:

cron на Linux/Mac:

crontab -e
# Добавить строку:
0 9 * * MON /usr/bin/python3 /path/to/weekly_report.py

Запускает скрипт каждый понедельник в 9 утра.

Windows Task Scheduler — GUI для того же самого.

Airflow — для serious production use. Дороже, но с monitoring, retries, dependencies.

Error handling

Production-отчёт должен handling errors:

import logging
import traceback

logging.basicConfig(
    filename='/var/log/reports.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

def safe_generate_report():
    try:
        summary, df = generate_weekly_report()
        send_to_slack(summary, SLACK_WEBHOOK)
        logging.info('Report sent successfully')
    except Exception as e:
        error_msg = f'Report failed: {str(e)}\n{traceback.format_exc()}'
        logging.error(error_msg)

        # Отправить алерт в отдельный channel
        send_to_slack(f'❌ Weekly report failed:\n{error_msg}', ALERT_WEBHOOK)

Ошибка без alert — хуже отсутствия отчёта. Команда будет ждать report, а его нет.

Чтобы не только читать теорию, но и решать реальные задачи — загляните в бот Карьерника. Там по каждой теме подборка вопросов с разборами.

Git для скриптов

Храните скрипт в репозитории. Без версионирования легко потерять изменения или испортить работающий скрипт.

Структура:

scripts/
├── weekly_report/
│   ├── report.py
│   ├── queries/
│   │   └── weekly.sql
│   └── README.md
├── daily_metrics/
│   └── ...
└── requirements.txt

README описывает: что делает скрипт, как настроить, какие зависимости.

Типичные ошибки

Hardcode credentials. Пароли в env vars, не в коде.

Забыть timezone. Скрипт запускается в 9 UTC, отчёт должен быть за «вчера» по московскому — разница имеет значение.

Нет idempotency. Скрипт запускается дважды, отчёт отправляется дважды. Проверяйте флаг «уже запущено».

Не логировать. Когда в понедельник отчёт не пришёл, вы не знаете, почему. Логи спасают.

Слишком сложный скрипт. Начинайте с MVP — скрипт на 50 строк. Усложняйте по мере необходимости.

Читайте также

FAQ

Cron или Airflow?

Cron — для простых скриптов. Airflow — для pipeline с dependencies, retries, monitoring.

Где запускать?

Локально — для dev. VM, Docker контейнер или lambda — для production.

Что если скрипт упал в production?

Логирование + alerts в Slack. Чтобы знать сразу, не в понедельник утром.

Как поддерживать 10+ отчётов?

Airflow с модульной структурой. Каждый отчёт — отдельный DAG.