Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

500 lines
18 KiB
Python
Executable File

#!/usr/bin/env python3
"""
图片A/B测试模块
测试不同图片风格的效果
"""
import os
import json
import random
from datetime import datetime, timedelta
from advanced_image_generator import AdvancedImageGenerator
class ImageABTest:
"""图片A/B测试"""
def __init__(self):
self.generator = AdvancedImageGenerator()
self.test_db = "/home/ubuntu/.nanobot/workspace/xiaohongshu_ab_test.db"
self.styles = ['modern', 'minimal', 'gradient', 'tech']
# 初始化数据库
self._init_database()
def _init_database(self):
"""初始化测试数据库"""
import sqlite3
conn = sqlite3.connect(self.test_db)
cursor = conn.cursor()
# 创建测试记录表
cursor.execute('''
CREATE TABLE IF NOT EXISTS ab_tests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
test_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
article_title TEXT,
article_source TEXT,
style_used TEXT,
note_id TEXT,
engagement_score REAL DEFAULT 0,
likes INTEGER DEFAULT 0,
collects INTEGER DEFAULT 0,
comments INTEGER DEFAULT 0,
shares INTEGER DEFAULT 0,
check_time TIMESTAMP,
notes TEXT
)
''')
# 创建风格效果表
cursor.execute('''
CREATE TABLE IF NOT EXISTS style_performance (
style TEXT PRIMARY KEY,
total_tests INTEGER DEFAULT 0,
total_engagement REAL DEFAULT 0,
avg_engagement REAL DEFAULT 0,
best_score REAL DEFAULT 0,
last_used TIMESTAMP
)
''')
# 初始化风格数据
for style in self.styles:
cursor.execute('''
INSERT OR IGNORE INTO style_performance (style, total_tests, avg_engagement)
VALUES (?, 0, 0)
''', (style,))
conn.commit()
conn.close()
def select_best_style(self, article_title, article_source):
"""选择最佳图片风格"""
import sqlite3
conn = sqlite3.connect(self.test_db)
cursor = conn.cursor()
# 获取各风格的平均表现
cursor.execute('''
SELECT style, avg_engagement, total_tests
FROM style_performance
ORDER BY avg_engagement DESC
''')
styles_data = cursor.fetchall()
conn.close()
if not styles_data:
# 无历史数据,随机选择
return random.choice(self.styles)
# 计算选择概率(基于表现)
total_engagement = sum(data[1] for data in styles_data if data[1] > 0)
if total_engagement == 0:
# 所有风格表现都为0,随机选择
return random.choice(self.styles)
# 基于表现加权随机选择
weights = []
for style, engagement, tests in styles_data:
if tests == 0:
weight = 1.0 # 未测试过的风格给基础权重
else:
weight = engagement + 0.1 # 加0.1避免0权重
weights.append(weight)
# 归一化权重
total_weight = sum(weights)
normalized_weights = [w / total_weight for w in weights]
# 加权随机选择
import numpy as np
selected_index = np.random.choice(len(styles_data), p=normalized_weights)
selected_style = styles_data[selected_index][0]
print(f"📊 A/B测试选择: {selected_style}")
print(f" 候选风格: {[s[0] for s in styles_data]}")
print(f" 平均互动: {[s[1] for s in styles_data]}")
print(f" 测试次数: {[s[2] for s in styles_data]}")
return selected_style
def record_test(self, article_title, article_source, style_used, note_id):
"""记录A/B测试"""
import sqlite3
conn = sqlite3.connect(self.test_db)
cursor = conn.cursor()
cursor.execute('''
INSERT INTO ab_tests
(test_date, article_title, article_source, style_used, note_id, check_time)
VALUES (?, ?, ?, ?, ?, ?)
''', (
datetime.now().isoformat(),
article_title[:100],
article_source,
style_used,
note_id,
(datetime.now() + timedelta(hours=24)).isoformat() # 24小时后检查效果
))
test_id = cursor.lastrowid
# 更新风格使用次数
cursor.execute('''
UPDATE style_performance
SET total_tests = total_tests + 1,
last_used = ?
WHERE style = ?
''', (datetime.now().isoformat(), style_used))
conn.commit()
conn.close()
print(f"📝 A/B测试记录已保存 (ID: {test_id})")
print(f" 风格: {style_used}")
print(f" 笔记ID: {note_id}")
print(f" 将在24小时后检查效果")
return test_id
def update_engagement(self, test_id, engagement_data):
"""更新互动数据"""
import sqlite3
conn = sqlite3.connect(self.test_db)
cursor = conn.cursor()
# 计算互动分数(加权)
likes = engagement_data.get('likes', 0)
collects = engagement_data.get('collects', 0)
comments = engagement_data.get('comments', 0)
shares = engagement_data.get('shares', 0)
# 权重:收藏 > 评论 > 点赞 > 分享
engagement_score = (
likes * 1.0 +
collects * 3.0 +
comments * 2.0 +
shares * 1.5
)
cursor.execute('''
UPDATE ab_tests
SET engagement_score = ?,
likes = ?,
collects = ?,
comments = ?,
shares = ?,
check_time = ?
WHERE id = ?
''', (
engagement_score,
likes,
collects,
comments,
shares,
datetime.now().isoformat(),
test_id
))
# 获取测试记录
cursor.execute('SELECT style_used FROM ab_tests WHERE id = ?', (test_id,))
style_used = cursor.fetchone()[0]
# 更新风格表现
cursor.execute('''
SELECT total_tests, total_engagement
FROM style_performance
WHERE style = ?
''', (style_used,))
result = cursor.fetchone()
if result:
total_tests, total_engagement = result
new_total_engagement = total_engagement + engagement_score
new_avg_engagement = new_total_engagement / (total_tests + 1) if total_tests > 0 else engagement_score
cursor.execute('''
UPDATE style_performance
SET total_engagement = ?,
avg_engagement = ?,
best_score = MAX(best_score, ?)
WHERE style = ?
''', (new_total_engagement, new_avg_engagement, engagement_score, style_used))
conn.commit()
conn.close()
print(f"📈 互动数据已更新 (测试ID: {test_id})")
print(f" 互动分数: {engagement_score:.2f}")
print(f" 点赞: {likes}, 收藏: {collects}, 评论: {comments}, 分享: {shares}")
return engagement_score
def check_pending_tests(self):
"""检查待处理的测试"""
import sqlite3
conn = sqlite3.connect(self.test_db)
cursor = conn.cursor()
# 查找需要检查的测试(超过24小时未检查)
cursor.execute('''
SELECT id, note_id, style_used, article_title
FROM ab_tests
WHERE engagement_score = 0
AND check_time <= ?
LIMIT 10
''', (datetime.now().isoformat(),))
pending_tests = cursor.fetchall()
conn.close()
if pending_tests:
print(f"🔍 发现 {len(pending_tests)} 个待检查的A/B测试")
for test_id, note_id, style_used, title in pending_tests:
print(f" 测试ID: {test_id}, 笔记ID: {note_id}, 风格: {style_used}")
print(f" 标题: {title[:50]}...")
return pending_tests
def simulate_engagement(self, test_id):
"""模拟互动数据(用于测试)"""
import random
engagement_data = {
'likes': random.randint(5, 50),
'collects': random.randint(1, 20),
'comments': random.randint(0, 10),
'shares': random.randint(0, 5)
}
score = self.update_engagement(test_id, engagement_data)
return engagement_data, score
def get_style_recommendation(self):
"""获取风格推荐"""
import sqlite3
conn = sqlite3.connect(self.test_db)
cursor = conn.cursor()
cursor.execute('''
SELECT style, avg_engagement, total_tests, best_score
FROM style_performance
ORDER BY avg_engagement DESC
''')
styles = cursor.fetchall()
conn.close()
if not styles:
return {
'recommended_style': 'modern',
'reason': '无历史数据,使用默认风格',
'all_styles': []
}
# 最佳风格
best_style = styles[0][0]
best_score = styles[0][1]
recommendation = {
'recommended_style': best_style,
'reason': f'平均互动分数最高: {best_score:.2f}',
'confidence': min(styles[0][2] / 10, 1.0), # 置信度基于测试次数
'all_styles': []
}
for style, avg_engagement, total_tests, best_score in styles:
recommendation['all_styles'].append({
'style': style,
'avg_engagement': avg_engagement,
'total_tests': total_tests,
'best_score': best_score
})
return recommendation
def generate_report(self, days=7):
"""生成A/B测试报告"""
import sqlite3
from datetime import datetime, timedelta
conn = sqlite3.connect(self.test_db)
cursor = conn.cursor()
# 时间范围
start_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
# 总体统计
cursor.execute('''
SELECT
COUNT(*) as total_tests,
AVG(engagement_score) as avg_engagement,
SUM(likes) as total_likes,
SUM(collects) as total_collects,
SUM(comments) as total_comments,
SUM(shares) as total_shares
FROM ab_tests
WHERE test_date >= ?
''', (start_date,))
overall_stats = cursor.fetchone()
# 风格表现
cursor.execute('''
SELECT
sp.style,
sp.total_tests,
sp.avg_engagement,
sp.best_score,
COUNT(DISTINCT at.id) as recent_tests,
AVG(CASE WHEN at.test_date >= ? THEN at.engagement_score ELSE NULL END) as recent_avg
FROM style_performance sp
LEFT JOIN ab_tests at ON sp.style = at.style_used AND at.test_date >= ?
GROUP BY sp.style
ORDER BY sp.avg_engagement DESC
''', (start_date, start_date))
style_stats = cursor.fetchall()
conn.close()
# 生成报告
report = f"📊 图片A/B测试报告\n"
report += f"📅 报告周期: 最近{days}天 ({start_date} 至今)\n"
report += f"⏰ 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
if overall_stats and overall_stats[0] > 0:
total_tests, avg_engagement, total_likes, total_collects, total_comments, total_shares = overall_stats
report += f"📈 总体统计:\n"
report += f" 总测试数: {total_tests}\n"
report += f" 平均互动分数: {avg_engagement:.2f}\n"
report += f" 总点赞: {total_likes or 0}\n"
report += f" 总收藏: {total_collects or 0}\n"
report += f" 总评论: {total_comments or 0}\n"
report += f" 总分享: {total_shares or 0}\n\n"
report += f"🎨 风格表现:\n"
for style, total_tests, avg_engagement, best_score, recent_tests, recent_avg in style_stats:
report += f" {style}:\n"
report += f" 总测试: {total_tests}, 平均分数: {avg_engagement:.2f}\n"
report += f" 最佳分数: {best_score:.2f}, 最近测试: {recent_tests}\n"
if recent_avg:
report += f" 最近平均: {recent_avg:.2f}\n"
report += "\n"
# 推荐
recommendation = self.get_style_recommendation()
report += f"🎯 推荐风格: {recommendation['recommended_style']}\n"
report += f" 理由: {recommendation['reason']}\n"
report += f" 置信度: {recommendation['confidence']:.1%}\n"
return report
def main():
"""主函数"""
import argparse
parser = argparse.ArgumentParser(description='图片A/B测试')
subparsers = parser.add_subparsers(dest='command', help='命令')
# select命令
select_parser = subparsers.add_parser('select', help='选择最佳风格')
select_parser.add_argument('--title', required=True, help='文章标题')
select_parser.add_argument('--source', required=True, help='文章来源')
# record命令
record_parser = subparsers.add_parser('record', help='记录测试')
record_parser.add_argument('--title', required=True, help='文章标题')
record_parser.add_argument('--source', required=True, help='文章来源')
record_parser.add_argument('--style', required=True, help='使用的风格')
record_parser.add_argument('--note-id', required=True, help='小红书笔记ID')
# update命令
update_parser = subparsers.add_parser('update', help='更新互动数据')
update_parser.add_argument('--test-id', type=int, required=True, help='测试ID')
update_parser.add_argument('--likes', type=int, default=0, help='点赞数')
update_parser.add_argument('--collects', type=int, default=0, help='收藏数')
update_parser.add_argument('--comments', type=int, default=0, help='评论数')
update_parser.add_argument('--shares', type=int, default=0, help='分享数')
# check命令
subparsers.add_parser('check', help='检查待处理测试')
# report命令
report_parser = subparsers.add_parser('report', help='生成报告')
report_parser.add_argument('--days', type=int, default=7, help='报告天数')
# recommend命令
subparsers.add_parser('recommend', help='获取推荐')
# simulate命令
simulate_parser = subparsers.add_parser('simulate', help='模拟测试')
simulate_parser.add_argument('--test-id', type=int, required=True, help='测试ID')
args = parser.parse_args()
ab_test = ImageABTest()
if args.command == 'select':
style = ab_test.select_best_style(args.title, args.source)
print(f"🎯 推荐风格: {style}")
elif args.command == 'record':
test_id = ab_test.record_test(args.title, args.source, args.style, args.note_id)
print(f"✅ 测试记录已创建 (ID: {test_id})")
elif args.command == 'update':
engagement_data = {
'likes': args.likes,
'collects': args.collects,
'comments': args.comments,
'shares': args.shares
}
score = ab_test.update_engagement(args.test_id, engagement_data)
print(f"✅ 互动数据已更新,分数: {score:.2f}")
elif args.command == 'check':
pending = ab_test.check_pending_tests()
if pending:
print(f"发现 {len(pending)} 个待检查测试")
else:
print("✅ 无待检查测试")
elif args.command == 'report':
report = ab_test.generate_report(args.days)
print(report)
elif args.command == 'recommend':
recommendation = ab_test.get_style_recommendation()
print(f"🎯 推荐风格: {recommendation['recommended_style']}")
print(f" 理由: {recommendation['reason']}")
print(f" 置信度: {recommendation['confidence']:.1%}")
print("\n所有风格:")
for style_info in recommendation['all_styles']:
print(f" {style_info['style']}: 平均{style_info['avg_engagement']:.2f}, 测试{style_info['total_tests']}次")
elif args.command == 'simulate':
engagement_data, score = ab_test.simulate_engagement(args.test_id)
print(f"🎭 模拟互动数据:")
print(f" 点赞: {engagement_data['likes']}")
print(f" 收藏: {engagement_data['collects']}")
print(f" 评论: {engagement_data['comments']}")
print(f" 分享: {engagement_data['shares']}")
print(f" 总分: {score:.2f}")
else:
parser.print_help()
if __name__ == "__main__":
main()