Files
ewoooc/services/exporter.py
ogt 1b4f3a7bbe
Some checks failed
CD Pipeline / deploy (push) Failing after 59s
feat: EwoooC 初始化 — 完整專案推版至 Gitea
- 建立 Gitea Actions CD pipeline (.gitea/workflows/cd.yaml)
- 部署模式: rsync Python 檔案至 188 → docker restart (volume mount)
- Dockerfile/requirements 變動時自動重建 Docker image
- 部署通知: Telegram (開始/成功/失敗)
- 健康檢查: https://mo.wooo.work/health (最多 5 次重試)
- 同步最新 CLAUDE.md / ADR-008 / memory (2026-04-19)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 01:21:13 +08:00

137 lines
6.1 KiB
Python

import os
import csv
import pandas as pd
from datetime import datetime
from config import EXCEL_EXPORT_DIR
class Exporter:
"""
報表匯出管理器
負責將數據轉換為 CSV/Excel 格式並儲存至 exports 目錄
"""
def __init__(self):
self.export_dir = EXCEL_EXPORT_DIR
if not os.path.exists(self.export_dir):
try:
os.makedirs(self.export_dir)
except OSError:
pass
def _write_csv(self, filename, headers, rows):
"""內部方法:寫入 CSV 檔案 (含 BOM 以支援 Excel 中文顯示)"""
filepath = os.path.join(self.export_dir, filename)
try:
with open(filepath, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
writer.writerow(headers)
writer.writerows(rows)
return filepath
except Exception as e:
print(f"❌ Export error: {e}")
return None
def generate_all_categories_report(self):
"""匯出所有分類商品快照"""
# 由於 app.py 呼叫此方法時未傳入數據,需自行查詢資料庫
from database.manager import DatabaseManager
from database.models import PriceRecord
from sqlalchemy import func
db = DatabaseManager()
session = db.get_session()
try:
# 查詢每個商品最新價格
subq = session.query(func.max(PriceRecord.id).label('max_id')).group_by(PriceRecord.product_id).subquery()
records = session.query(PriceRecord).join(subq, PriceRecord.id == subq.c.max_id).all()
rows = []
for r in records:
rows.append([r.product.category, r.product.name, r.price, r.product.url, r.timestamp.strftime('%Y-%m-%d %H:%M')])
filename = f"All_Products_{datetime.now().strftime('%Y%m%d_%H%M')}.csv"
return self._write_csv(filename, ['Category', 'Name', 'Price', 'URL', 'Last Update'], rows)
finally:
session.close()
def generate_price_change_report(self, items):
"""匯出價格異動商品"""
rows = []
for item in items:
rec = item['record']
diff = item['yesterday_diff']
rows.append([rec.product.category, rec.product.name, rec.price, diff, rec.product.url])
filename = f"Price_Changes_{datetime.now().strftime('%Y%m%d_%H%M')}.csv"
return self._write_csv(filename, ['Category', 'Name', 'Price', 'Change', 'URL'], rows)
def generate_low_price_report(self):
return self._write_csv(f"Low_Price_{datetime.now().strftime('%Y%m%d')}.csv", ['Message'], [['Feature not fully implemented']])
def generate_custom_report(self, items, title):
rows = []
for item in items:
rec = item['record']
rows.append([rec.product.category, rec.product.name, rec.price, rec.product.url])
filename = f"{title}_{datetime.now().strftime('%Y%m%d_%H%M')}.csv"
return self._write_csv(filename, ['Category', 'Name', 'Price', 'URL'], rows)
def generate_delisted_report(self, items, title):
rows = []
for item in items:
p = item['product']
price = item['last_price']
rows.append([p.category, p.name, price, p.url, "DELISTED"])
filename = f"{title}_{datetime.now().strftime('%Y%m%d_%H%M')}.csv"
return self._write_csv(filename, ['Category', 'Name', 'Last Price', 'URL', 'Status'], rows)
def generate_all_products_excel(self, items):
"""匯出所有商品至 Excel (依分類分頁)"""
data = []
for item in items:
rec = item['record']
data.append({
'分類': rec.product.category,
'商品名稱': rec.product.name,
'價格': rec.price,
'連結': rec.product.url,
'更新時間': rec.timestamp.strftime('%Y-%m-%d %H:%M')
})
filename = f"MOMO_All_{datetime.now().strftime('%Y%m%d_%H%M')}.xlsx"
filepath = os.path.join(self.export_dir, filename)
df = pd.DataFrame(data)
with pd.ExcelWriter(filepath, engine='openpyxl') as writer:
for cat, group in df.groupby('分類'):
sheet_name = str(cat)[:30].replace('/', '_').replace(':', '') # 處理 Excel 工作表名稱限制
group.to_excel(writer, sheet_name=sheet_name, index=False)
return filepath
def generate_changes_excel(self, increase, decrease):
"""匯出漲跌商品至 Excel (兩個分頁)"""
filename = f"MOMO_Changes_{datetime.now().strftime('%Y%m%d_%H%M')}.xlsx"
filepath = os.path.join(self.export_dir, filename)
with pd.ExcelWriter(filepath, engine='openpyxl') as writer:
# 漲價
data_inc = [{'分類': i['record'].product.category, '商品名稱': i['record'].product.name, '價格': i['record'].price, '漲幅': i['yesterday_diff'], '連結': i['record'].product.url} for i in increase]
pd.DataFrame(data_inc).to_excel(writer, sheet_name='漲價商品', index=False)
# 跌價
data_dec = [{'分類': i['record'].product.category, '商品名稱': i['record'].product.name, '價格': i['record'].price, '跌幅': i['yesterday_diff'], '連結': i['record'].product.url} for i in decrease]
pd.DataFrame(data_dec).to_excel(writer, sheet_name='跌價商品', index=False)
return filepath
def generate_delisted_excel(self, delisted_items):
"""匯出下架商品至 Excel"""
filename = f"MOMO_Delisted_{datetime.now().strftime('%Y%m%d_%H%M')}.xlsx"
filepath = os.path.join(self.export_dir, filename)
data = [{'分類': i['product'].category, '商品名稱': i['product'].name, '最後價格': i['last_price'], '連結': i['product'].url, '狀態': '下架'} for i in delisted_items]
with pd.ExcelWriter(filepath, engine='openpyxl') as writer:
pd.DataFrame(data).to_excel(writer, sheet_name='下架商品', index=False)
return filepath