Блог AST-SoftPro
Генерация PDF-отчётов из данных: pandas + WeasyPrint для бизнеса
Автоматическая генерация PDF-отчётов
PDF-отчёты — стандарт для бизнес-документации. Клиенты expect PDF, а не Excel. Python позволяет генерировать красивые отчёты с таблицами, графиками и логотипами автоматически из данных.
WeasyPrint конвертирует HTML+CSS в PDF. Это значит, что вы можете использовать знакомые HTML-шаблоны и CSS-стили для создания профессиональных документов.
Установка
pip install pandas weasyprint jinja2 matplotlib
Генерация отчёта из данных
import pandas as pd
from jinja2 import Template
from weasyprint import HTML
from datetime import datetime
# Данные для отчёта
sales_data = pd.DataFrame({
'Месяц': ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн'],
'Продажи': [120000, 150000, 180000, 165000, 210000, 240000],
'Расходы': [80000, 90000, 95000, 100000, 110000, 120000],
})
# Вычисления
sales_data['Прибыль'] = sales_data['Продажи'] - sales_data['Расходы']
total_sales = sales_data['Продажи'].sum()
total_profit = sales_data['Прибыль'].sum()
# HTML-шаблон отчёта
REPORT_TEMPLATE = '''
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<style>
@page {
size: A4;
margin: 2cm;
@bottom-center { content: "Страница " counter(page) " из " counter(pages); }
}
body { font-family: 'Helvetica', Arial, sans-serif; color: #333; }
.header { text-align: center; margin-bottom: 30px; border-bottom: 2px solid #2c3e50; padding-bottom: 15px; }
.company-name { font-size: 24px; font-weight: bold; color: #2c3e50; }
.report-title { font-size: 18px; color: #7f8c8d; margin-top: 5px; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th { background: #3498db; color: white; padding: 10px; text-align: left; }
td { padding: 8px; border-bottom: 1px solid #ddd; }
.total-row { font-weight: bold; background: #ecf0f1; }
.summary { display: flex; justify-content: space-between; margin: 20px 0; }
.summary-box { background: #f8f9fa; padding: 15px; border-radius: 5px; width: 30%; text-align: center; }
.summary-box h3 { margin: 0; color: #2c3e50; }
.summary-box p { font-size: 24px; font-weight: bold; margin: 10px 0 0; }
.positive { color: #27ae60; }
.negative { color: #e74c3c; }
.footer { text-align: center; font-size: 10px; color: #95a5a6; margin-top: 40px; }
</style>
</head>
<body>
<div class="header">
<div class="company-name">{{ company_name }}</div>
<div class="report-title">Финансовый отчёт за {{ period }}</div>
</div>
<div class="summary">
<div class="summary-box">
<h3>Продажи</h3>
<p class="positive">{{ "{:,}".format(total_sales) }} ₽</p>
</div>
<div class="summary-box">
<h3>Расходы</h3>
<p class="negative">{{ "{:,}".format(total_expenses) }} ₽</p>
</div>
<div class="summary-box">
<h3>Прибыль</h3>
<p class="positive">{{ "{:,}".format(total_profit) }} ₽</p>
</div>
</div>
<h2>Детализация по месяцам</h2>
<table>
<thead>
<tr>
<th>Месяц</th>
<th>Продажи</th>
<th>Расходы</th>
<th>Прибыль</th>
</tr>
</thead>
<tbody>
{% for row in data %}
<tr>
<td>{{ row['Месяц'] }}</td>
<td>{{ "{:,}".format(row["Продажи"]) }} ₽</td>
<td>{{ "{:,}".format(row["Расходы"]) }} ₽</td>
<td class="{{ 'positive' if row['Прибыль'] > 0 else 'negative' }}">
{{ "{:,}".format(row["Прибыль"]) }} ₽
</td>
</tr>
{% endfor %}
<tr class="total-row">
<td>ИТОГО</td>
<td>{{ "{:,}".format(total_sales) }} ₽</td>
<td>{{ "{:,}".format(total_expenses) }} ₽</td>
<td>{{ "{:,}".format(total_profit) }} ₽</td>
</tr>
</tbody>
</table>
<div class="footer">
Сгенерировано автоматически {{ generated_at }} | {{ company_name }}
</div>
</body>
</html>
'''
def generate_pdf_report(output_path='report.pdf'):
template = Template(REPORT_TEMPLATE)
html_content = template.render(
company_name='ООО "Аналитика"',
period='I полугодие 2026',
total_sales=total_sales,
total_expenses=sales_data['Расходы'].sum(),
total_profit=total_profit,
data=sales_data.to_dict('records'),
generated_at=datetime.now().strftime('%d.%m.%Y %H:%M'),
)
HTML(string=html_content).write_pdf(output_path)
print(f"✅ Отчёт сохранён: {output_path}")
generate_pdf_report()
Добавление графиков в отчёт
import matplotlib.pyplot as plt
import base64
from io import BytesIO
def create_chart_image():
"""Создание графика и возвращение base64 для вставки в HTML."""
fig, ax = plt.subplots(figsize=(10, 5))
ax.bar(sales_data['Месяц'], sales_data['Продажи'], label='Продажи', color='#3498db')
ax.bar(sales_data['Месяц'], sales_data['Расходы'], label='Расходы', color='#e74c3c')
ax.plot(sales_data['Месяц'], sales_data['Прибыль'], label='Прибыль', color='#27ae60', marker='o')
ax.legend()
ax.set_title('Финансовые показатели по месяцам')
# Сохранение в base64 для вставки в HTML
buf = BytesIO()
plt.savefig(buf, format='png', dpi=150, bbox_inches='tight')
buf.seek(0)
img_base64 = base64.b64encode(buf.getvalue()).decode()
plt.close()
return img_base64
# В HTML-шаблоне добавить:
# <img src="data:image/png;base64,{{ chart_image }}" style="width:100%; margin: 20px 0;">
Автоматическая рассылка отчётов
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
def send_report_email(recipient_email, pdf_path):
"""Отправка PDF-отчёта по email."""
msg = MIMEMultipart()
msg['Subject'] = f'Финансовый отчёт за {datetime.now().strftime("%B %Y")}'
msg['From'] = 'reports@company.ru'
msg['To'] = recipient_email
body = f"""
Уважаемый(ая) коллега!
Направляем финансовый отчёт за {datetime.now().strftime('%B %Y')}.
С уважением,
Система автоматической отчётности
"""
msg.attach(MIMEText(body, 'plain'))
# Прикрепление PDF
with open(pdf_path, 'rb') as f:
attachment = MIMEBase('application', 'octet-stream')
attachment.set_payload(f.read())
encoders.encode_base64(attachment)
attachment.add_header('Content-Disposition', f'attachment; filename=report_{datetime.now().strftime("%Y%m%d")}.pdf')
msg.attach(attachment)
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login('your_email@gmail.com', 'app_password')
server.send_message(msg)
server.quit()
print(f"✅ Отчёт отправлен на {recipient_email}")
Сравнение библиотек для генерации PDF
| Библиотека | Подход | Преимущества |
|---|---|---|
| WeasyPrint | HTML → PDF | CSS-стили, красивый дизайн, Unicode |
| ReportLab | Программный | Полный контроль, графики, подписи |
| pdfkit | HTML → PDF (WebKit) | Простой API, но требует Qt |
| FPDF | Программный | Лёгкая, но ограниченная |
WeasyPrint — лучший выбор для бизнес-отчётов: вы пишете HTML/CSS, получаете PDF.
Заключение
Автоматическая генерация PDF-отчётов экономит часы ручной работы. Ключевые компоненты:
-
pandas для обработки данных и вычислений;
-
Jinja2 для HTML-шаблонов отчётов;
-
WeasyPrint для конвертации HTML в PDF;
-
matplotlib для графиков (опционально);
-
SMTP для автоматической рассылки.
Настройте расписание (APScheduler) и отчёты будут генерироваться и отправляться автоматически.