1. 项目背景与核心价值

豆瓣电影作为国内最具影响力的影视评分平台之一,积累了海量的用户评分、评论和电影元数据。这些数据蕴含着丰富的用户行为模式和电影特征信息,为构建个性化推荐系统提供了理想的数据基础。本项目通过Python技术栈实现了一个完整的电影数据采集、分析与推荐系统,主要解决以下三个核心问题:

  1. 数据获取难题 :传统人工收集豆瓣电影数据效率低下且容易触发反爬机制,需要设计稳定的自动化采集方案
  2. 推荐精准度问题 :简单基于评分的推荐难以满足用户个性化需求,需要结合多种算法提升推荐质量
  3. 可视化呈现不足 :原始数据难以直观展示电影特征分布和用户偏好,需要专业可视化工具辅助分析

系统采用Flask+Vue前后端分离架构,后端使用Python实现数据采集、存储和分析功能,前端通过Vue构建交互式可视化界面。关键技术栈包括:

  • 数据采集层 :Scrapy爬虫框架+反反爬策略
  • 数据处理层 :Pandas数据清洗+MySQL存储
  • 算法层 :LSTM情感分析+协同过滤推荐
  • 可视化层 :Echarts动态图表+Vue组件化开发

提示:在实际部署时,豆瓣API有严格的访问频率限制,建议通过设置合理的爬取间隔(建议2-3秒/次)和使用代理池来避免被封禁IP。

2. 系统架构设计

2.1 整体技术架构

系统采用典型的三层架构设计,各层技术选型如下:

[前端展示层] Vue.js + ElementUI + ECharts
    ↑
[业务逻辑层] Flask RESTful API + JWT认证
    ↑
[数据服务层] Scrapy + MySQL + Redis
前端架构设计考虑:
  • 选择Vue.js而非React/Angular的原因:更轻量级、学习曲线平缓、与ECharts集成更简单
  • 使用Vue CLI 4.x搭建项目基础结构
  • 按功能模块划分组件:
    • MovieList.vue - 电影列表展示
    • Recommendation.vue - 推荐结果展示
    • AnalysisDashboard.vue - 数据分析看板
    • CommentAnalysis.vue - 评论情感分析
后端架构设计要点:
  • Flask蓝图(Blueprint)组织路由
  • SQLAlchemy作为ORM工具
  • Marshmallow实现数据序列化
  • JWT(JSON Web Token)处理用户认证
  • Redis缓存热门电影数据和推荐结果

2.2 数据库设计

主要数据表结构设计:

CREATE TABLE `movies` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `douban_id` varchar(20) NOT NULL COMMENT '豆瓣ID',
  `title` varchar(100) NOT NULL,
  `director` varchar(100) DEFAULT NULL,
  `casts` varchar(255) DEFAULT NULL COMMENT '主演列表,逗号分隔',
  `genres` varchar(100) DEFAULT NULL COMMENT '类型,逗号分隔',
  `rating` decimal(3,1) DEFAULT NULL,
  `votes` int(11) DEFAULT NULL COMMENT '评分人数',
  `release_date` date DEFAULT NULL,
  `duration` int(11) DEFAULT NULL COMMENT '片长(分钟)',
  `summary` text,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_douban_id` (`douban_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `comments` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `movie_id` int(11) NOT NULL,
  `user_id` varchar(50) NOT NULL,
  `content` text NOT NULL,
  `rating` smallint(6) DEFAULT NULL COMMENT '用户评分(1-5)',
  `sentiment` smallint(6) DEFAULT NULL COMMENT '情感分析结果(0负向,1中性,2正向)',
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_movie_id` (`movie_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意:实际部署时建议为频繁查询的字段(如genres、rating等)添加适当索引,但要注意索引过多会影响写入性能。

3. 核心功能实现

3.1 数据采集模块

豆瓣电影数据采集面临的主要挑战是反爬机制,我们的解决方案是:

import scrapy
import time
import random
from fake_useragent import UserAgent

class DoubanMovieSpider(scrapy.Spider):
    name = 'douban_movie'
    allowed_domains = ['movie.douban.com']
    start_urls = ['https://movie.douban.com/top250']
    
    custom_settings = {
        'DOWNLOAD_DELAY': random.uniform(2, 5),
        'CONCURRENT_REQUESTS_PER_DOMAIN': 1,
        'DEFAULT_REQUEST_HEADERS': {
            'User-Agent': UserAgent().random,
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.9',
        }
    }
    
    def parse(self, response):
        for movie in response.css('.item'):
            yield {
                'title': movie.css('.title::text').get(),
                'rating': float(movie.css('.rating_num::text').get()),
                'votes': int(movie.css('.star span::text').re_first(r'(\d+)人评价')),
                'url': movie.css('.hd a::attr(href)').get()
            }
        
        next_page = response.css('.next a::attr(href)').get()
        if next_page:
            yield response.follow(next_page, self.parse)

关键反爬策略:

  1. 随机User-Agent轮换
  2. 2-5秒随机请求间隔
  3. 限制并发连接数
  4. 使用代理IP池(需额外配置)

3.2 LSTM情感分析模型

针对电影评论的情感分析,我们构建了一个双层LSTM网络:

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, LSTM, Dense
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

# 数据预处理
tokenizer = Tokenizer(num_words=5000)
tokenizer.fit_on_texts(train_texts)
sequences = tokenizer.texts_to_sequences(train_texts)
padded_sequences = pad_sequences(sequences, maxlen=200)

# 模型构建
model = Sequential([
    Embedding(5000, 128, input_length=200),
    LSTM(64, return_sequences=True),
    LSTM(32),
    Dense(3, activation='softmax')  # 3类情感输出
])

model.compile(loss='categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

# 模型训练
history = model.fit(
    padded_sequences, 
    train_labels,
    batch_size=32,
    epochs=10,
    validation_split=0.2
)

模型训练技巧:

  1. 使用预训练的中文词向量(如腾讯AI Lab的ChineseEmbedding)
  2. 添加Dropout层防止过拟合
  3. 使用EarlyStopping回调自动终止训练
  4. 对不平衡数据集采用class_weight参数调整

3.3 混合推荐算法

系统实现了三种推荐策略的混合:

  1. 基于内容的推荐 :使用TF-IDF计算电影相似度
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import linear_kernel

tfidf = TfidfVectorizer(stop_words='english')
tfidf_matrix = tfidf.fit_transform(movies['genres'] + ' ' + movies['director'])
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)
  1. 协同过滤推荐 :使用Surprise库实现
from surprise import Dataset, KNNBasic
from surprise.model_selection import train_test_split

data = Dataset.load_builtin('ml-100k')
trainset, testset = train_test_split(data, test_size=0.25)
algo = KNNBasic(k=40, min_k=1, sim_options={'user_based': False})
algo.fit(trainset)
  1. 热门电影推荐 :结合评分和时间衰减因子
import math
from datetime import datetime

def hot_score(rating, votes, date):
    # 时间衰减因子(半衰期30天)
    days = (datetime.now() - date).days
    decay = math.exp(-days * math.log(2) / 30)  
    return rating * votes**0.8 * decay

实际应用中,我们会根据用户行为数据动态调整三种算法的权重比例。

4. 可视化实现

4.1 ECharts集成方案

前端通过Vue-ECharts组件实现可视化:

<template>
  <div class="chart-container">
    <v-chart :option="chartOption" autoresize />
  </div>
</template>

<script>
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { PieChart, BarChart, LineChart } from 'echarts/charts'
import {
  TitleComponent,
  TooltipComponent,
  LegendComponent,
  GridComponent
} from 'echarts/components'
import VChart from 'vue-echarts'

use([
  CanvasRenderer,
  PieChart,
  BarChart,
  LineChart,
  TitleComponent,
  TooltipComponent,
  LegendComponent,
  GridComponent
])

export default {
  components: { VChart },
  data() {
    return {
      chartOption: {
        title: { text: '电影类型分布' },
        tooltip: { trigger: 'item' },
        series: [{
          name: '类型',
          type: 'pie',
          radius: '50%',
          data: this.genreData,
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }]
      }
    }
  },
  props: {
    genreData: {
      type: Array,
      default: () => []
    }
  }
}
</script>

4.2 典型可视化场景

  1. 电影评分分布雷达图
option = {
  radar: {
    indicator: [
      { name: '剧情', max: 5 },
      { name: '表演', max: 5 },
      { name: '视觉效果', max: 5 },
      { name: '音乐', max: 5 },
      { name: '导演', max: 5 }
    ]
  },
  series: [{
    type: 'radar',
    data: [
      {
        value: [4.2, 4.5, 3.8, 4.1, 4.3],
        name: '肖申克的救赎'
      }
    ]
  }]
}
  1. 评论情感时序图
option = {
  xAxis: {
    type: 'category',
    data: ['周一','周二','周三','周四','周五','周六','周日']
  },
  yAxis: { type: 'value' },
  series: [{
    data: [120, 200, 150, 80, 70, 110, 130],
    type: 'line',
    smooth: true
  }]
}
  1. 导演作品气泡图
option = {
  dataset: {
    dimensions: ['score', 'year', 'title', 'votes'],
    source: [
      [8.7, 1994, '肖申克的救赎', 1500000],
      [9.3, 1972, '教父', 1200000]
    ]
  },
  xAxis: { type: 'value' },
  yAxis: { type: 'value' },
  visualMap: {
    dimension: 3,
    min: 0,
    max: 2000000,
    inRange: {
      symbolSize: [10, 50]
    }
  },
  series: [{
    type: 'scatter',
    encode: {
      x: 1,
      y: 0,
      tooltip: [2, 0, 1, 3]
    }
  }]
}

5. 系统部署与优化

5.1 性能优化策略

  1. 数据库优化

    • 为频繁查询的字段建立复合索引
    • 使用Redis缓存热门查询结果
    • 对大表进行分库分表(如评论表按电影ID分片)
  2. 前端性能优化

    • 使用Vue的异步组件和路由懒加载
    • 对ECharts图表进行防抖处理
    • 实现服务端渲染(SSR)提升首屏速度
  3. 推荐算法优化

    • 离线计算用户相似度矩阵
    • 使用Faiss加速向量相似度计算
    • 实现增量更新机制

5.2 部署方案

推荐使用Docker Compose进行容器化部署:

version: '3'

services:
  web:
    build: ./web
    ports:
      - "5000:5000"
    environment:
      - FLASK_ENV=production
      - DATABASE_URL=mysql://user:pass@db/movies
    depends_on:
      - db
      - redis

  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=secret
      - MYSQL_DATABASE=movies

  redis:
    image: redis:alpine

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./static:/usr/share/nginx/html
    depends_on:
      - web

volumes:
  db_data:

关键部署注意事项:

  1. 生产环境务必关闭Flask的debug模式
  2. 使用Gunicorn+Gevent作为WSGI服务器
  3. 配置Nginx反向代理和静态文件服务
  4. 设置定期数据库备份任务

6. 项目扩展方向

  1. 实时推荐系统 :引入Kafka消息队列实现实时用户行为处理
  2. 多模态分析 :结合电影海报图像进行视觉特征分析
  3. 社交网络分析 :构建用户-电影二部图进行社区发现
  4. A/B测试框架 :评估不同推荐算法的实际效果
  5. 移动端适配 :开发React Native跨平台应用

在实际开发过程中,我特别建议重视日志系统的建设。一个完善的日志系统能极大提升调试和运维效率。这是我们使用的日志配置:

import logging
from logging.handlers import RotatingFileHandler

def setup_logger(name):
    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)
    
    formatter = logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    
    # 控制台输出
    ch = logging.StreamHandler()
    ch.setFormatter(formatter)
    logger.addHandler(ch)
    
    # 文件输出(自动轮转)
    fh = RotatingFileHandler('app.log', maxBytes=10*1024*1024, backupCount=5)
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    
    return logger

另一个实用技巧是使用Python的multiprocessing模块加速数据预处理:

from multiprocessing import Pool

def process_movie(movie_data):
    # 复杂的数据处理逻辑
    return processed_data

if __name__ == '__main__':
    with Pool(processes=4) as pool:
        results = pool.map(process_movie, all_movies)
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐