191 lines
6.8 KiB
Python
191 lines
6.8 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""CoolBotDataSys 每日飞书日报推送"""
|
|||
|
|
import sys
|
|||
|
|
import os
|
|||
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|||
|
|
|
|||
|
|
import requests
|
|||
|
|
import logging
|
|||
|
|
from datetime import datetime
|
|||
|
|
from database import db
|
|||
|
|
|
|||
|
|
logging.basicConfig(level=logging.INFO)
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
def get_collection_stats():
|
|||
|
|
"""获取藏品统计"""
|
|||
|
|
with db.get_cursor() as cursor:
|
|||
|
|
cursor.execute("SELECT COUNT(*) as cnt FROM collections")
|
|||
|
|
total = cursor.fetchone()["cnt"]
|
|||
|
|
return {"total_collections": total}
|
|||
|
|
|
|||
|
|
def get_yichens_stats():
|
|||
|
|
"""获取一尘数据统计"""
|
|||
|
|
with db.get_cursor() as cursor:
|
|||
|
|
# 总帖子数
|
|||
|
|
cursor.execute("SELECT COUNT(*) as cnt FROM yichens_posts")
|
|||
|
|
total_posts = cursor.fetchone()["cnt"]
|
|||
|
|
|
|||
|
|
# 今日新增
|
|||
|
|
cursor.execute("SELECT COUNT(*) as cnt FROM yichens_posts WHERE DATE(crawled_at) = CURDATE()")
|
|||
|
|
today_posts = cursor.fetchone()["cnt"]
|
|||
|
|
|
|||
|
|
# 交易帖数量
|
|||
|
|
cursor.execute("SELECT COUNT(*) as cnt FROM yichens_posts WHERE post_type = 'deal'")
|
|||
|
|
deal_posts = cursor.fetchone()["cnt"]
|
|||
|
|
|
|||
|
|
# 有价格标注的帖子
|
|||
|
|
cursor.execute("SELECT COUNT(*) as cnt FROM yichens_posts WHERE price IS NOT NULL AND price > 0")
|
|||
|
|
priced_posts = cursor.fetchone()["cnt"]
|
|||
|
|
|
|||
|
|
# 今日价格区间统计
|
|||
|
|
cursor.execute("""
|
|||
|
|
SELECT
|
|||
|
|
COUNT(*) as cnt,
|
|||
|
|
AVG(price) as avg_price,
|
|||
|
|
MIN(price) as min_price,
|
|||
|
|
MAX(price) as max_price
|
|||
|
|
FROM yichens_posts
|
|||
|
|
WHERE price IS NOT NULL AND price > 0 AND DATE(crawled_at) = CURDATE()
|
|||
|
|
""")
|
|||
|
|
today_price_stats = cursor.fetchone()
|
|||
|
|
|
|||
|
|
# 最新帖子(标题示例)
|
|||
|
|
cursor.execute("""
|
|||
|
|
SELECT title, price, price_unit
|
|||
|
|
FROM yichens_posts
|
|||
|
|
WHERE price IS NOT NULL AND price > 0
|
|||
|
|
ORDER BY crawled_at DESC LIMIT 5
|
|||
|
|
""")
|
|||
|
|
latest_with_price = cursor.fetchall()
|
|||
|
|
|
|||
|
|
# 最近爬虫状态
|
|||
|
|
cursor.execute("""
|
|||
|
|
SELECT source, status, items_count, finished_at
|
|||
|
|
FROM crawl_logs
|
|||
|
|
ORDER BY finished_at DESC LIMIT 3
|
|||
|
|
""")
|
|||
|
|
crawl_status = cursor.fetchall()
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"total_posts": total_posts,
|
|||
|
|
"today_posts": today_posts,
|
|||
|
|
"deal_posts": deal_posts,
|
|||
|
|
"priced_posts": priced_posts,
|
|||
|
|
"today_price_stats": today_price_stats,
|
|||
|
|
"latest_with_price": latest_with_price,
|
|||
|
|
"crawl_status": crawl_status
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def build_feishu_message(collection_stats, yichens_stats):
|
|||
|
|
"""构建飞书消息卡片"""
|
|||
|
|
now = datetime.now()
|
|||
|
|
|
|||
|
|
# 最新价格帖子示例
|
|||
|
|
latest_items = ""
|
|||
|
|
for item in yichens_stats.get("latest_with_price", []):
|
|||
|
|
price_str = f"¥{item['price']}{item['price_unit']}" if item['price'] else "价格待确认"
|
|||
|
|
latest_items += f"- {item['title'][:30]}... : {price_str}\n"
|
|||
|
|
if not latest_items:
|
|||
|
|
latest_items = "今日暂无价格数据\n"
|
|||
|
|
|
|||
|
|
# 爬虫状态
|
|||
|
|
crawl_info = ""
|
|||
|
|
for log in yichens_stats.get("crawl_status", []):
|
|||
|
|
status_emoji = "✅" if log["status"] == "success" else "❌"
|
|||
|
|
crawl_info += f"{status_emoji} {log['source']}: {log['items_count']}条 @ {log['finished_at']}\n"
|
|||
|
|
if not crawl_info:
|
|||
|
|
crawl_info = "暂无爬虫运行记录\n"
|
|||
|
|
|
|||
|
|
# 价格统计
|
|||
|
|
price_stats = yichens_stats.get("today_price_stats", {})
|
|||
|
|
price_info = ""
|
|||
|
|
if price_stats and price_stats.get("cnt", 0) > 0:
|
|||
|
|
price_info = f"均价: ¥{price_stats['avg_price']:.0f} | 区间: ¥{price_stats['min_price']:.0f}~¥{price_stats['max_price']:.0f} ({price_stats['cnt']}条)"
|
|||
|
|
else:
|
|||
|
|
price_info = "今日暂无价格统计"
|
|||
|
|
|
|||
|
|
message = {
|
|||
|
|
"msg_type": "interactive",
|
|||
|
|
"card": {
|
|||
|
|
"header": {
|
|||
|
|
"title": {"tag": "plain_text", "content": f"📊 CoolBotDataSys 日报 {now.strftime('%Y-%m-%d %H:%M')}"},
|
|||
|
|
"template": "blue"
|
|||
|
|
},
|
|||
|
|
"elements": [
|
|||
|
|
{
|
|||
|
|
"tag": "div",
|
|||
|
|
"text": {
|
|||
|
|
"tag": "lark_md",
|
|||
|
|
"content": (
|
|||
|
|
f"**🐉 一尘连体纪念钞数据**\n"
|
|||
|
|
f"- 总帖子: {yichens_stats['total_posts']} 条\n"
|
|||
|
|
f"- 今日新增: +{yichens_stats['today_posts']} 条\n"
|
|||
|
|
f"- 交易帖: {yichens_stats['deal_posts']} 条\n"
|
|||
|
|
f"- 有价格: {yichens_stats['priced_posts']} 条\n\n"
|
|||
|
|
f"**💰 今日价格动态**\n{price_info}\n\n"
|
|||
|
|
f"**📈 最新交易帖**\n{latest_items}\n\n"
|
|||
|
|
f"**🔄 爬虫状态**\n{crawl_info}"
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
{"tag": "hr"},
|
|||
|
|
{
|
|||
|
|
"tag": "note",
|
|||
|
|
"elements": [
|
|||
|
|
{"tag": "plain_text", "content": f"CoolBotDataSys · {now.strftime('%H:%M:%S')}"}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return message
|
|||
|
|
|
|||
|
|
def send_feishu(message):
|
|||
|
|
"""发送飞书消息"""
|
|||
|
|
webhook = os.environ.get("FEISHU_WEBHOOK_URL", "")
|
|||
|
|
if not webhook:
|
|||
|
|
logger.warning("飞书Webhook未配置,跳过发送")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
response = requests.post(webhook, json=message, timeout=10)
|
|||
|
|
result = response.json()
|
|||
|
|
if result.get("code") == 0 or result.get("StatusCode") == 0:
|
|||
|
|
logger.info("✅ 飞书消息发送成功")
|
|||
|
|
return True
|
|||
|
|
else:
|
|||
|
|
logger.error(f"❌ 飞书发送失败: {result}")
|
|||
|
|
return False
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"❌ 飞书发送异常: {e}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
logger.info("📊 开始生成日报...")
|
|||
|
|
|
|||
|
|
collection_stats = get_collection_stats()
|
|||
|
|
yichens_stats = get_yichens_stats()
|
|||
|
|
|
|||
|
|
print(f"统计概览:")
|
|||
|
|
print(f" 一尘帖子总数: {yichens_stats['total_posts']}")
|
|||
|
|
print(f" 今日新增: {yichens_stats['today_posts']}")
|
|||
|
|
print(f" 交易帖: {yichens_stats['deal_posts']}")
|
|||
|
|
print(f" 有价格标注: {yichens_stats['priced_posts']}")
|
|||
|
|
|
|||
|
|
message = build_feishu_message(collection_stats, yichens_stats)
|
|||
|
|
print(f"\n消息卡片内容预览:")
|
|||
|
|
print(message["card"]["elements"][0]["text"]["content"][:500])
|
|||
|
|
|
|||
|
|
# 检查是否启用飞书推送
|
|||
|
|
webhook = os.environ.get("FEISHU_WEBHOOK_URL", "")
|
|||
|
|
if webhook:
|
|||
|
|
send_feishu(message)
|
|||
|
|
else:
|
|||
|
|
print("\n⚠️ 未配置 FEISHU_WEBHOOK_URL 环境变量,跳过飞书推送")
|
|||
|
|
|
|||
|
|
return yichens_stats
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|