Как автоматизировать отчёт в 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.txtREADME описывает: что делает скрипт, как настроить, какие зависимости.
Типичные ошибки
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.