什么是音乐会排期预测系统

音乐会排期预测系统是一种基于数据分析和机器学习技术的智能工具,它能够帮助音乐爱好者和活动组织者准确预测和追踪音乐会的安排。这种系统通过分析历史数据、艺术家行程、场馆可用性、季节性趋势等多种因素,为用户提供精准的音乐会日程预测。

排期预测的核心价值

排期预测系统最大的价值在于它解决了传统音乐会信息查询中的几个痛点:

  • 信息分散:用户需要在多个平台(票务网站、艺术家官网、社交媒体)之间切换
  • 信息滞后:官方信息发布往往滞后于实际安排
  • 错过机会:热门演出门票售罄速度快,用户难以及时获知
  • 地理限制:难以全面了解周边地区的所有演出信息

排期预测的技术实现原理

数据收集与整合

排期预测系统首先需要建立强大的数据收集能力。这包括:

  1. 结构化数据源

    • 历史音乐会数据库(时间、地点、艺术家、场馆)
    • 艺术家巡演历史数据
    • 场馆档期记录
    • 票务平台销售数据
  2. 非结构化数据源

    • 社交媒体动态(艺术家推文、Instagram帖子)
    • 新闻媒体报道
    • 行业内部消息
    • 粉丝论坛讨论

机器学习模型构建

排期预测的核心是机器学习模型,通常采用以下技术:

import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error
import numpy as np

class ConcertPredictor:
    def __init__(self):
        self.model = RandomForestRegressor(n_estimators=100, random_state=42)
        self.feature_columns = [
            'artist_popularity', 'venue_capacity', 'month', 
            'day_of_week', 'is_holiday', 'days_since_last_concert',
            'historical_success_rate'
        ]
    
    def prepare_features(self, data):
        """准备训练特征"""
        # 处理日期特征
        data['concert_date'] = pd.to_datetime(data['concert_date'])
        data['month'] = data['concert_date'].dt.month
        data['day_of_week'] = data['concert_date'].dt.dayofweek
        
        # 计算时间间隔特征
        data = data.sort_values(['artist_id', 'concert_date'])
        data['days_since_last_concert'] = data.groupby('artist_id')['concert_date'].diff().dt.days.fillna(365)
        
        # 艺术家热度编码(示例:基于社交媒体粉丝数)
        data['artist_popularity'] = data['artist_followers'] / 1000000
        
        # 场馆容量分级
        data['venue_capacity_level'] = pd.cut(data['venue_capacity'], 
                                            bins=[0, 1000, 5000, 20000, np.inf],
                                            labels=['small', 'medium', 'large', 'stadium'])
        
        return data[self.feature_columns]
    
    def train(self, historical_data):
        """训练预测模型"""
        features = self.prepare_features(historical_data)
        # 目标变量:音乐会是否成功举办(1表示成功,0表示取消或延期)
        target = historical_data['is_successful'].astype(int)
        
        X_train, X_test, y_train, y_test = train_test_split(
            features, target, test_size=0.2, random_state=42
        )
        
        self.model.fit(X_train, y_train)
        
        # 评估模型
        predictions = self.model.predict(X_test)
        mae = mean_absolute_error(y_test, predictions)
        print(f"模型MAE: {mae:.4f}")
        
        return self.model
    
    def predict_upcoming_concerts(self, upcoming_schedule):
        """预测新音乐会的成功概率"""
        features = self.prepare_features(upcoming_schedule)
        probabilities = self.model.predict_proba(features)[:, 1]
        return probabilities

# 使用示例
historical_data = pd.DataFrame({
    'artist_id': [1, 1, 2, 2, 3],
    'artist_followers': [5000000, 5000000, 8000000, 8000000, 2000000],
    'venue_capacity': [1500, 3000, 8000, 12000, 500],
    'concert_date': ['2023-06-15', '2023-09-20', '2023-07-10', '2023-10-05', '2023-08-12'],
    'is_successful': [1, 1, 1, 1, 0]
})

predictor = ConcertPredictor()
trained_model = predictor.train(historical_data)

# 预测新音乐会
upcoming = pd.DataFrame({
    'artist_id': [1, 2, 3],
    'artist_followers': [5000000, 8000000, 2000000],
    'venue_capacity': [2500, 10000, 800],
    'concert_date': ['2024-03-15', '2024-04-20', '2024-05-10'],
    'is_successful': [0, 0, 0]  # 未知,用于预测
})

probabilities = predictor.predict_upcoming_concerts(upcoming)
print("预测成功率:", probabilities)

时间序列分析与趋势预测

对于音乐会排期,时间序列分析尤为重要:

import pandas as pd
import numpy as np
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.arima.model import ARIMA
import matplotlib.pyplot as plt

class SeasonalConcertPredictor:
    def __init__(self):
        self.seasonal_model = None
        self.arima_model = None
    
    def analyze_seasonality(self, concert_counts):
        """分析音乐会的季节性模式"""
        # concert_counts: 按月统计的音乐会数量
        decomposition = seasonal_decompose(concert_counts, model='additive', period=12)
        
        # 可视化分解结果
        fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, figsize=(12, 10))
        decomposition.observed.plot(ax=ax1, title='Observed')
        decomposition.trend.plot(ax=ax2, title='Trend')
        decomposition.seasonal.plot(ax=ax3, title='Seasonal')
        decomposition.resid.plot(ax=ax4, title='Residual')
        plt.tight_layout()
        plt.show()
        
        return decomposition
    
    def fit_arima(self, concert_counts):
        """使用ARIMA模型进行时间序列预测"""
        # 自动选择最佳参数(简化版)
        best_aic = np.inf
        best_order = None
        
        for p in range(3):
            for d in range(2):
                for q in range(3):
                    try:
                        model = ARIMA(concert_counts, order=(p, d, q))
                        results = model.fit()
                        if results.aic < best_aic:
                            best_aic = results.aic
                            best_order = (p, d, q)
                    except:
                        continue
        
        # 使用最佳参数拟合模型
        self.arima_model = ARIMA(concert_counts, order=best_order)
        self.fitted_model = self.arima_model.fit()
        
        return self.fitted_model
    
    def forecast(self, periods=12):
        """预测未来音乐会数量"""
        if self.fitted_model is None:
            raise ValueError("模型尚未训练,请先调用fit_arima方法")
        
        forecast = self.fitted_model.forecast(steps=periods)
        return forecast

# 示例:预测未来音乐会数量
# 假设我们有过去3年的月度音乐会数据
dates = pd.date_range(start='2021-01-01', end='2023-12-01', freq='M')
concert_counts = pd.Series([
    45, 52, 68, 85, 92, 105, 110, 98, 85, 72, 60, 55,  # 2021
    48, 55, 72, 88, 95, 108, 115, 102, 88, 75, 62, 58,  # 2022
    50, 58, 75, 92, 98, 112, 118, 105, 92, 78, 65, 60   # 2023
], index=dates)

predictor = SeasonalConcertPredictor()
decomposition = predictor.analyze_seasonality(concert_counts)
arima_model = predictor.fit_arima(concert_counts)

# 预测2024年音乐会数量
forecast_2024 = predictor.forecast(periods=12)
print("2024年月度音乐会预测:", forecast_2024)

如何构建个人音乐会排期系统

数据源整合方案

要构建个人音乐会排期系统,首先需要整合多个数据源。以下是具体实现:

1. 票务平台API集成

import requests
import json
from datetime import datetime, timedelta

class TicketPlatformAPI:
    def __init__(self, api_key):
        self.api_key = api_key
        self.base_urls = {
            'ticketmaster': 'https://app.ticketmaster.com/discovery/v2/events.json',
            'eventbrite': 'https://www.eventbriteapi.com/v3/events/search/',
            'stubhub': 'https://api.stubhub.com/sellers/search/events/v3'
        }
    
    def fetch_ticketmaster_events(self, artist=None, city=None, start_date=None, end_date=None):
        """从Ticketmaster获取音乐会数据"""
        params = {
            'apikey': self.api_key,
            'classificationName': 'music',
            'size': 200
        }
        
        if artist:
            params['keyword'] = artist
        if city:
            params['city'] = city
        if start_date:
            params['startDateTime'] = start_date.strftime('%Y-%m-%dT%H:%M:%SZ')
        if end_date:
            params['endDateTime'] = end_date.strftime('%Y-%m-%dT%H:%M:%SZ')
        
        try:
            response = requests.get(self.base_urls['ticketmaster'], params=params)
            response.raise_for_status()
            data = response.json()
            
            events = []
            if '_embedded' in data and 'events' in data['_embedded']:
                for event in data['_embedded']['events']:
                    event_info = {
                        'name': event['name'],
                        'date': event['dates']['start']['dateTime'] if 'dateTime' in event['dates']['start'] else None,
                        'venue': event['_embedded']['venues'][0]['name'] if '_embedded' in event and 'venues' in event['_embedded'] else 'Unknown',
                        'city': event['_embedded']['venues'][0]['city']['name'] if '_embedded' in event and 'venues' in event['_embedded'] else 'Unknown',
                        'url': event['url'],
                        'price_range': event.get('priceRanges', [{}])[0] if event.get('priceRanges') else None
                    }
                    events.append(event_info)
            
            return events
        except requests.exceptions.RequestException as e:
            print(f"API请求失败: {e}")
            return []
    
    def fetch_artist_events(self, artist_name):
        """获取特定艺术家的所有演出"""
        # 这里可以结合多个平台的数据
        all_events = []
        
        # 从Ticketmaster获取
        tm_events = self.fetch_ticketmaster_events(artist=artist_name)
        all_events.extend(tm_events)
        
        # 从其他平台获取(示例代码)
        # eventbrite_events = self.fetch_eventbrite_events(artist_name)
        # all_events.extend(eventbrite_events)
        
        return all_events

# 使用示例
api = TicketPlatformAPI(api_key='your_api_key_here')
events = api.fetch_artist_events('Taylor Swift')
print(f"找到 {len(events)} 场演出")
for event in events[:5]:  # 显示前5场
    print(f"- {event['name']} at {event['venue']} on {event['date']}")

2. 社交媒体监控

import tweepy
import re
from textblob import TextBlob

class SocialMediaMonitor:
    def __init__(self, twitter_api_keys):
        """初始化Twitter API"""
        auth = tweepy.OAuthHandler(twitter_api_keys['consumer_key'], 
                                 twitter_api_keys['consumer_secret'])
        auth.set_access_token(twitter_api_keys['access_token'], 
                            twitter_api_keys['access_token_secret'])
        self.api = tweepy.API(auth)
    
    def search_artist_announcements(self, artist_names, days_back=7):
        """搜索艺术家最近的演出公告"""
        end_date = datetime.now()
        start_date = end_date - timedelta(days=days_back)
        
        announcements = []
        
        for artist in artist_names:
            # 构建搜索查询
            query = f'"{artist}" (concert OR tour OR show OR live OR演出) -filter:retweets'
            
            try:
                tweets = tweepy.Cursor(self.api.search_tweets,
                                     q=query,
                                     lang='en',
                                     since=start_date.strftime('%Y-%m-%d'),
                                     until=end_date.strftime('%Y-%m-%d'),
                                     tweet_mode='extended').items(50)
                
                for tweet in tweets:
                    # 分析情感和内容
                    analysis = TextBlob(tweet.full_text)
                    
                    # 检查是否包含日期信息
                    date_patterns = [
                        r'\b(\d{1,2}[-/]\d{1,2}[-/]\d{2,4})\b',  # MM/DD/YYYY or DD-MM-YYYY
                        r'\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\s+\d{1,2}\b',  # Month Day
                        r'\b(\d{1,2})\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\b'  # Day Month
                    ]
                    
                    dates_found = []
                    for pattern in date_patterns:
                        matches = re.findall(pattern, tweet.full_text, re.IGNORECASE)
                        if matches:
                            dates_found.extend(matches)
                    
                    if dates_found or 'announce' in tweet.full_text.lower() or 'coming' in tweet.full_text.lower():
                        announcements.append({
                            'artist': artist,
                            'tweet': tweet.full_text,
                            'date': tweet.created_at,
                            'sentiment': analysis.sentiment.polarity,
                            'dates_mentioned': dates_found,
                            'url': f"https://twitter.com/i/web/status/{tweet.id}"
                        })
            
            except Exception as e:
                print(f"搜索 {artist} 的推文时出错: {e}")
        
        return announcements

# 使用示例
twitter_keys = {
    'consumer_key': 'your_consumer_key',
    'consumer_secret': 'your_consumer_secret',
    'access_token': 'your_access_token',
    'access_token_secret': 'your_access_token_secret'
}

monitor = SocialMediaMonitor(twitter_keys)
artists = ['Coldplay', 'Ed Sheeran', 'BTS']
announcements = monitor.search_artist_announcements(artists)

for ann in announcements[:3]:
    print(f"【{ann['artist']}】{ann['date'].strftime('%Y-%m-%d')}")
    print(f"内容: {ann['tweet'][:100]}...")
    print(f"情感得分: {ann['sentiment']:.2f}")
    print(f"链接: {ann['url']}")
    print("-" * 50)

个人排期系统架构

1. 数据存储设计

import sqlite3
import json
from datetime import datetime

class ConcertDatabase:
    def __init__(self, db_path='concerts.db'):
        self.conn = sqlite3.connect(db_path)
        self.create_tables()
    
    def create_tables(self):
        """创建数据库表"""
        cursor = self.conn.cursor()
        
        # 艺术家表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS artists (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL,
                genre TEXT,
                popularity INTEGER,
                followers INTEGER,
                last_updated TIMESTAMP
            )
        ''')
        
        # 场馆表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS venues (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL,
                city TEXT,
                capacity INTEGER,
                latitude REAL,
                longitude REAL
            )
        ''')
        
        # 音乐会表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS concerts (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                artist_id INTEGER,
                venue_id INTEGER,
                date TIMESTAMP,
                status TEXT,
                ticket_url TEXT,
                price_range TEXT,
                source TEXT,
                confidence_score REAL,
                created_at TIMESTAMP,
                FOREIGN KEY (artist_id) REFERENCES artists (id),
                FOREIGN KEY (venue_id) REFERENCES venues (id)
            )
        ''')
        
        # 用户关注表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS user_follows (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER,
                artist_id INTEGER,
                notification_enabled BOOLEAN,
                created_at TIMESTAMP
            )
        ''')
        
        # 通知记录表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS notifications (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER,
                concert_id INTEGER,
                notification_type TEXT,
                sent_at TIMESTAMP,
                opened BOOLEAN
            )
        ''')
        
        self.conn.commit()
    
    def add_concert(self, artist_name, venue_name, city, concert_date, 
                   ticket_url=None, price_range=None, source='api', confidence=0.8):
        """添加音乐会记录"""
        cursor = self.conn.cursor()
        
        # 获取或创建艺术家
        cursor.execute("SELECT id FROM artists WHERE name = ?", (artist_name,))
        artist_result = cursor.fetchone()
        if artist_result:
            artist_id = artist_result[0]
        else:
            cursor.execute(
                "INSERT INTO artists (name, last_updated) VALUES (?, ?)",
                (artist_name, datetime.now())
            )
            artist_id = cursor.lastrowid
        
        # 获取或创建场馆
        cursor.execute("SELECT id FROM venues WHERE name = ? AND city = ?", (venue_name, city))
        venue_result = cursor.fetchone()
        if venue_result:
            venue_id = venue_result[0]
        else:
            cursor.execute(
                "INSERT INTO venues (name, city) VALUES (?, ?)",
                (venue_name, city)
            )
            venue_id = cursor.lastrowid
        
        # 添加音乐会
        cursor.execute('''
            INSERT INTO concerts 
            (artist_id, venue_id, date, status, ticket_url, price_range, source, confidence_score, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        ''', (artist_id, venue_id, concert_date, 'planned', ticket_url, 
              json.dumps(price_range) if price_range else None, source, confidence, datetime.now()))
        
        self.conn.commit()
        return cursor.lastrowid
    
    def get_upcoming_concerts(self, days_ahead=90, user_id=None):
        """获取即将举行的音乐会"""
        cursor = self.conn.cursor()
        
        query = '''
            SELECT 
                a.name as artist,
                v.name as venue,
                v.city,
                c.date,
                c.ticket_url,
                c.price_range,
                c.confidence_score
            FROM concerts c
            JOIN artists a ON c.artist_id = a.id
            JOIN venues v ON c.venue_id = v.id
            WHERE c.date BETWEEN ? AND ?
            AND c.status = 'planned'
        '''
        
        params = [
            datetime.now(),
            datetime.now() + timedelta(days=days_ahead)
        ]
        
        if user_id:
            query += ' AND EXISTS (SELECT 1 FROM user_follows uf WHERE uf.artist_id = a.id AND uf.user_id = ?)'
            params.append(user_id)
        
        query += ' ORDER BY c.date ASC'
        
        cursor.execute(query, params)
        return cursor.fetchall()
    
    def get_concerts_for_artist(self, artist_name):
        """获取特定艺术家的所有音乐会"""
        cursor = self.conn.cursor()
        cursor.execute('''
            SELECT c.date, v.name, v.city, c.ticket_url, c.confidence_score
            FROM concerts c
            JOIN artists a ON c.artist_id = a.id
            JOIN venues v ON c.venue_id = v.id
            WHERE a.name = ? AND c.date >= ?
            ORDER BY c.date
        ''', (artist_name, datetime.now()))
        
        return cursor.fetchall()

# 使用示例
db = ConcertDatabase()

# 添加一些音乐会数据
db.add_concert(
    artist_name="Coldplay",
    venue_name="Wembley Stadium",
    city="London",
    concert_date=datetime(2024, 6, 15, 20, 0),
    ticket_url="https://www.ticketmaster.co.uk/coldplay-tickets/artist/859635",
    price_range={"min": 75, "max": 250},
    source="ticketmaster",
    confidence=0.95
)

# 查询即将举行的音乐会
upcoming = db.get_upcoming_concerts(days_ahead=60)
for concert in upcoming:
    print(f"{concert[3]}: {concert[0]} at {concert[1]} ({concert[2]})")

2. 智能通知系统

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import pushover
import asyncio

class NotificationManager:
    def __init__(self, db_path='concerts.db'):
        self.db = ConcertDatabase(db_path)
        self.email_config = None
        self.pushover_config = None
    
    def configure_email(self, smtp_server, port, username, password):
        """配置邮件通知"""
        self.email_config = {
            'smtp_server': smtp_server,
            'port': port,
            'username': username,
            'password': password
        }
    
    def configure_pushover(self, api_token, user_key):
        """配置Pushover推送"""
        self.pushover_config = {
            'api_token': api_token,
            'user_key': user_key
        }
    
    def send_email_notification(self, to_email, concerts):
        """发送邮件通知"""
        if not self.email_config:
            raise ValueError("邮件配置未设置")
        
        msg = MIMEMultipart('alternative')
        msg['Subject'] = f"🎵 您关注的艺术家有新音乐会!共{len(concerts)}场"
        msg['From'] = self.email_config['username']
        msg['To'] = to_email
        
        # 创建HTML内容
        html_content = """
        <html>
        <head>
            <style>
                body { font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px; }
                .container { max-width: 600px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; }
                .header { color: #333; text-align: center; margin-bottom: 20px; }
                .concert { border-left: 4px solid #1db954; padding: 10px; margin: 10px 0; background: #f9f9f9; }
                .artist { font-size: 18px; font-weight: bold; color: #1db954; }
                .details { color: #666; margin: 5px 0; }
                .cta { display: inline-block; background: #1db954; color: white; padding: 10px 20px; 
                       text-decoration: none; border-radius: 5px; margin-top: 10px; }
            </style>
        </head>
        <body>
            <div class="container">
                <div class="header">
                    <h2>🎉 音乐会排期提醒</h2>
                    <p>您关注的艺术家有新的演出安排!</p>
                </div>
        """
        
        for concert in concerts:
            artist, venue, city, date, url, price_range, confidence = concert
            date_str = date.strftime('%Y年%m月%d日 %H:%M')
            price_info = json.loads(price_range) if price_range else {}
            
            html_content += f"""
                <div class="concert">
                    <div class="artist">{artist}</div>
                    <div class="details">📅 {date_str}</div>
                    <div class="details">📍 {venue}, {city}</div>
                    {f'<div class="details">💰 ¥{price_info.get("min", "?")} - ¥{price_info.get("max", "?")}</div>' if price_info else ''}
                    <div class="details">置信度: {confidence*100:.0f}%</div>
                    <a href="{url}" class="cta">立即购票</a>
                </div>
            """
        
        html_content += """
                <div style="margin-top: 20px; padding: 15px; background: #e8f5e9; border-radius: 5px;">
                    <p style="margin: 0; color: #2e7d32;">💡 提示:热门演出门票通常在几分钟内售罄,建议提前准备!</p>
                </div>
            </div>
        </body>
        </html>
        """
        
        msg.attach(MIMEText(html_content, 'html'))
        
        try:
            with smtplib.SMTP(self.email_config['smtp_server'], self.email_config['port']) as server:
                server.starttls()
                server.login(self.email_config['username'], self.email_config['password'])
                server.send_message(msg)
            print(f"✅ 邮件已发送至 {to_email}")
            return True
        except Exception as e:
            print(f"❌ 邮件发送失败: {e}")
            return False
    
    def send_pushover_notification(self, concerts):
        """发送Pushover推送"""
        if not self.pushover_config:
            raise ValueError("Pushover配置未设置")
        
        client = pushover.Client(self.pushover_config['user_key'], 
                                api_token=self.pushover_config['api_token'])
        
        for concert in concerts:
            artist, venue, city, date, url, price_range, confidence = concert
            date_str = date.strftime('%m/%d %H:%M')
            
            message = f"🎵 {artist}\n📅 {date_str}\n📍 {venue}, {city}\n🎯 置信度: {confidence*100:.0f}%\n🔗 {url}"
            
            client.send_message(
                message,
                title=f"新音乐会: {artist}",
                priority=1,  # 高优先级
                sound='magic'
            )
    
    async def check_and_notify(self, user_id, days_ahead=30):
        """检查新音乐会并发送通知"""
        # 获取用户关注的艺术家
        cursor = self.db.conn.cursor()
        cursor.execute('''
            SELECT a.name 
            FROM user_follows uf
            JOIN artists a ON uf.artist_id = a.id
            WHERE uf.user_id = ? AND uf.notification_enabled = 1
        ''', (user_id,))
        
        followed_artists = [row[0] for row in cursor.fetchall()]
        
        if not followed_artists:
            print("用户未关注任何艺术家")
            return
        
        # 检查新音乐会
        new_concerts = []
        for artist in followed_artists:
            concerts = self.db.get_concerts_for_artist(artist)
            if concerts:
                new_concerts.extend(concerts)
        
        if new_concerts:
            # 获取用户邮箱
            cursor.execute('SELECT email FROM users WHERE id = ?', (user_id,))
            user_email = cursor.fetchone()
            
            if user_email and user_email[0]:
                self.send_email_notification(user_email[0], new_concerts)
            
            # 发送Pushover推送
            if self.pushover_config:
                self.send_pushover_notification(new_concerts)
            
            # 记录通知
            for concert in new_concerts:
                cursor.execute('''
                    INSERT INTO notifications (user_id, concert_id, notification_type, sent_at, opened)
                    VALUES (?, ?, ?, ?, ?)
                ''', (user_id, concert[3], 'new_concert', datetime.now(), 0))
            
            self.db.conn.commit()
        
        return len(new_concerts)

# 使用示例
notifier = NotificationManager()

# 配置通知渠道
notifier.configure_email(
    smtp_server='smtp.gmail.com',
    port=587,
    username='your_email@gmail.com',
    password='your_app_password'
)

notifier.configure_pushover(
    api_token='your_pushover_api_token',
    user_key='your_pushover_user_key'
)

# 检查并发送通知(需要在异步环境中运行)
# asyncio.run(notifier.check_and_notify(user_id=1))

高级功能:个性化推荐与预测

基于用户偏好的推荐系统

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import TfidfVectorizer

class PersonalizedConcertRecommender:
    def __init__(self, db_path='concerts.db'):
        self.db = ConcertDatabase(db_path)
        self.user_profiles = {}
        self.artist_embeddings = {}
    
    def build_artist_embeddings(self):
        """基于艺术家特征构建嵌入向量"""
        cursor = self.db.conn.cursor()
        cursor.execute('''
            SELECT a.id, a.name, a.genre, a.popularity, a.followers
            FROM artists a
        ''')
        
        artists = cursor.fetchall()
        
        # 使用TF-IDF处理文本特征
        genres = [artist[2] if artist[2] else '' for artist in artists]
        vectorizer = TfidfVectorizer()
        genre_vectors = vectorizer.fit_transform(genres).toarray()
        
        # 归一化数值特征
        popularity = np.array([artist[3] if artist[3] else 0 for artist in artists]).reshape(-1, 1)
        followers = np.array([artist[4] if artist[4] else 0 for artist in artists]).reshape(-1, 1)
        
        # 组合所有特征
        for i, artist in enumerate(artists):
            # 归一化数值特征
            pop_norm = (popularity[i] - popularity.min()) / (popularity.max() - popularity.min() + 1e-6)
            foll_norm = (followers[i] - followers.min()) / (followers.max() - followers.min() + 1e-6)
            
            # 组合向量
            embedding = np.concatenate([
                genre_vectors[i] * 0.6,  # 文本特征权重
                np.array([pop_norm[0] * 0.2]),  # 流行度权重
                np.array([foll_norm[0] * 0.2])  # 粉丝数权重
            ])
            
            self.artist_embeddings[artist[0]] = {
                'name': artist[1],
                'vector': embedding
            }
    
    def update_user_profile(self, user_id, liked_artists, disliked_artists=None):
        """更新用户画像"""
        if disliked_artists is None:
            disliked_artists = []
        
        user_vector = np.zeros(len(next(iter(self.artist_embeddings.values()))['vector']))
        
        # 为喜欢的艺术家添加正向权重
        for artist_id in liked_artists:
            if artist_id in self.artist_embeddings:
                user_vector += self.artist_embeddings[artist_id]['vector'] * 1.0
        
        # 为不喜欢的艺术家添加负向权重
        for artist_id in disliked_artists:
            if artist_id in self.artist_embeddings:
                user_vector -= self.artist_embeddings[artist_id]['vector'] * 0.5
        
        # 归一化
        if np.linalg.norm(user_vector) > 0:
            user_vector = user_vector / np.linalg.norm(user_vector)
        
        self.user_profiles[user_id] = {
            'vector': user_vector,
            'last_updated': datetime.now()
        }
    
    def recommend_concerts(self, user_id, top_k=10):
        """推荐音乐会"""
        if user_id not in self.user_profiles:
            return []
        
        user_vector = self.user_profiles[user_id]['vector'].reshape(1, -1)
        
        # 计算与所有艺术家的相似度
        similarities = []
        for artist_id, data in self.artist_embeddings.items():
            artist_vector = data['vector'].reshape(1, -1)
            similarity = cosine_similarity(user_vector, artist_vector)[0][0]
            similarities.append((artist_id, data['name'], similarity))
        
        # 排序并选择top_k
        similarities.sort(key=lambda x: x[2], reverse=True)
        top_artists = similarities[:top_k]
        
        # 获取这些艺术家的音乐会
        recommendations = []
        for artist_id, artist_name, score in top_artists:
            concerts = self.db.get_concerts_for_artist(artist_name)
            for concert in concerts:
                recommendations.append({
                    'artist': artist_name,
                    'venue': concert[1],
                    'city': concert[2],
                    'date': concert[0],
                    'url': concert[3],
                    'relevance_score': score,
                    'confidence': concert[4]
                })
        
        # 按相关性和日期排序
        recommendations.sort(key=lambda x: (x['relevance_score'], -x['date'].timestamp()), reverse=True)
        
        return recommendations[:top_k]

# 使用示例
recommender = PersonalizedConcertRecommender()
recommender.build_artist_embeddings()

# 假设用户喜欢Coldplay和Ed Sheeran
liked_artists = [1, 2]  # 假设数据库中的ID
recommender.update_user_profile(user_id=1, liked_artists=liked_artists)

# 获取推荐
recommendations = recommender.recommend_concerts(user_id=1, top_k=5)
print("为您推荐的音乐会:")
for rec in recommendations:
    print(f"🎵 {rec['artist']} - {rec['venue']} ({rec['city']})")
    print(f"   日期: {rec['date'].strftime('%Y-%m-%d')}")
    print(f"   相关性: {rec['relevance_score']:.2f}")
    print(f"   置信度: {rec['confidence']:.2f}")
    print()

实际部署方案

完整的系统架构

┌─────────────────────────────────────────────────────────────┐
│                     用户界面层 (Web/Mobile)                  │
│  - 音乐会查询界面                                           │
│  - 艺术家关注管理                                           │
│  - 通知设置                                                 │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   API服务层 (Flask/FastAPI)                  │
│  - RESTful API接口                                          │
│  - 认证与授权                                               │
│  - 请求限流                                                 │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   核心处理层 (Python服务)                    │
│  - 数据收集器 (API爬虫)                                     │
│  - 预测引擎 (ML模型)                                        │
│  - 推荐系统                                                 │
│  - 通知调度器                                               │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   数据存储层 (SQLite/PostgreSQL)            │
│  - 音乐会数据库                                             │
│  - 用户画像                                                 │
│  - 历史记录                                                 │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   外部数据源                                 │
│  - Ticketmaster API                                         │
│  - Spotify API (艺术家信息)                                 │
│  - Twitter API (社交媒体监控)                               │
│  - Google Maps API (场馆位置)                               │
└─────────────────────────────────────────────────────────────┘

部署脚本示例

# deploy.py
import os
import subprocess
import sys

def install_dependencies():
    """安装依赖"""
    print("📦 安装Python依赖...")
    subprocess.check_call([sys.executable, "-m", "pip", "install", 
                          "flask", "requests", "pandas", "numpy", 
                          "scikit-learn", "tweepy", "textblob", 
                          "pushover", "smtplib", "sqlite3"])

def setup_database():
    """初始化数据库"""
    print("🗄️ 初始化数据库...")
    from concert_system import ConcertDatabase
    db = ConcertDatabase()
    print("✅ 数据库初始化完成")

def create_config_template():
    """创建配置文件模板"""
    config_content = """
# config.py
# API密钥配置
API_KEYS = {
    'ticketmaster': 'YOUR_TICKETMASTER_API_KEY',
    'spotify': 'YOUR_SPOTIFY_CLIENT_ID',
    'twitter': {
        'consumer_key': 'YOUR_TWITTER_CONSUMER_KEY',
        'consumer_secret': 'YOUR_TWITTER_CONSUMER_SECRET',
        'access_token': 'YOUR_TWITTER_ACCESS_TOKEN',
        'access_token_secret': 'YOUR_TWITTER_ACCESS_TOKEN_SECRET'
    },
    'pushover': {
        'api_token': 'YOUR_PUSHOVER_API_TOKEN',
        'user_key': 'YOUR_PUSHOVER_USER_KEY'
    }
}

# 邮件配置
EMAIL_CONFIG = {
    'smtp_server': 'smtp.gmail.com',
    'port': 587,
    'username': 'your_email@gmail.com',
    'password': 'YOUR_APP_PASSWORD'
}

# 系统配置
SYSTEM_CONFIG = {
    'check_interval_minutes': 60,  # 检查新音乐会的间隔
    'prediction_days_ahead': 90,   # 预测未来90天
    'confidence_threshold': 0.7,   # 置信度阈值
    'notification_cooldown_hours': 24  # 通知冷却时间
}
"""
    
    with open('config.py', 'w') as f:
        f.write(config_content)
    print("✅ 配置文件模板已创建 (config.py)")

def create_systemd_service():
    """创建systemd服务文件(Linux)"""
    service_content = """
[Unit]
Description=Concert Prediction Service
After=network.target

[Service]
Type=simple
User=your_username
WorkingDirectory=/path/to/concert_system
ExecStart=/usr/bin/python3 /path/to/concert_system/main.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
"""
    
    with open('concert-prediction.service', 'w') as f:
        f.write(service_content)
    print("✅ Systemd服务文件已创建")

def main():
    print("🎵 音乐会排期预测系统部署脚本")
    print("=" * 50)
    
    install_dependencies()
    setup_database()
    create_config_template()
    create_systemd_service()
    
    print("\n" + "=" * 50)
    print("🎉 部署完成!")
    print("\n下一步:")
    print("1. 编辑 config.py 填入您的API密钥")
    print("2. 运行 python main.py 启动服务")
    print("3. 访问 http://localhost:5000 查看Web界面")
    print("\n提示:建议使用虚拟环境管理依赖")
    print("创建虚拟环境: python -m venv venv")
    print("激活虚拟环境: source venv/bin/activate  (Linux/Mac) 或 venv\\Scripts\\activate (Windows)")

if __name__ == '__main__':
    main()

最佳实践与注意事项

数据隐私与安全

  1. API密钥管理

    • 永远不要在代码中硬编码密钥
    • 使用环境变量或专门的密钥管理服务
    • 定期轮换密钥
  2. 用户数据保护

    • 遵守GDPR等数据保护法规
    • 加密存储敏感信息
    • 提供用户数据导出和删除功能

系统性能优化

  1. 缓存策略

    • 使用Redis缓存热门查询结果
    • 对API响应进行本地缓存,避免频繁请求
  2. 异步处理

    • 使用Celery或asyncio处理耗时任务
    • 将数据收集和预测任务放入后台队列

错误处理与监控

import logging
from functools import wraps

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('concert_system.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

def error_handler(func):
    """错误处理装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            logger.error(f"函数 {func.__name__} 执行出错: {str(e)}", exc_info=True)
            # 可以在这里添加告警逻辑,如发送邮件通知管理员
            return None
    return wrapper

@error_handler
def fetch_concert_data():
    """带错误处理的数据获取"""
    # 模拟可能出错的操作
    import random
    if random.random() < 0.3:
        raise ConnectionError("API连接失败")
    return {"status": "success"}

# 使用示例
result = fetch_concert_data()
if result is None:
    print("数据获取失败,请检查日志")

总结

通过构建一个完整的音乐会排期预测系统,您可以:

  1. 实时追踪:自动收集多个数据源的音乐会信息
  2. 智能预测:利用机器学习预测音乐会的成功概率和时间
  3. 个性化推荐:基于您的音乐偏好推荐最合适的演出
  4. 及时通知:通过邮件、推送等多种方式确保您不会错过任何演出

这个系统不仅能帮助您个人使用,还可以扩展为商业服务,为更多音乐爱好者提供价值。关键在于持续优化数据源、提升预测准确率,并提供优秀的用户体验。

开始构建您的音乐会排期系统,让每一场精彩演出都不再错过!