Python实现豆瓣电影数据采集与推荐系统
·
1. 项目背景与核心价值
豆瓣电影作为国内最具影响力的影视评分平台之一,积累了海量的用户评分、评论和电影元数据。这些数据蕴含着丰富的用户行为模式和电影特征信息,为构建个性化推荐系统提供了理想的数据基础。本项目通过Python技术栈实现了一个完整的电影数据采集、分析与推荐系统,主要解决以下三个核心问题:
- 数据获取难题 :传统人工收集豆瓣电影数据效率低下且容易触发反爬机制,需要设计稳定的自动化采集方案
- 推荐精准度问题 :简单基于评分的推荐难以满足用户个性化需求,需要结合多种算法提升推荐质量
- 可视化呈现不足 :原始数据难以直观展示电影特征分布和用户偏好,需要专业可视化工具辅助分析
系统采用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)
关键反爬策略:
- 随机User-Agent轮换
- 2-5秒随机请求间隔
- 限制并发连接数
- 使用代理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
)
模型训练技巧:
- 使用预训练的中文词向量(如腾讯AI Lab的ChineseEmbedding)
- 添加Dropout层防止过拟合
- 使用EarlyStopping回调自动终止训练
- 对不平衡数据集采用class_weight参数调整
3.3 混合推荐算法
系统实现了三种推荐策略的混合:
- 基于内容的推荐 :使用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)
- 协同过滤推荐 :使用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)
- 热门电影推荐 :结合评分和时间衰减因子
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 典型可视化场景
- 电影评分分布雷达图 :
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: '肖申克的救赎'
}
]
}]
}
- 评论情感时序图 :
option = {
xAxis: {
type: 'category',
data: ['周一','周二','周三','周四','周五','周六','周日']
},
yAxis: { type: 'value' },
series: [{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'line',
smooth: true
}]
}
- 导演作品气泡图 :
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 性能优化策略
-
数据库优化 :
- 为频繁查询的字段建立复合索引
- 使用Redis缓存热门查询结果
- 对大表进行分库分表(如评论表按电影ID分片)
-
前端性能优化 :
- 使用Vue的异步组件和路由懒加载
- 对ECharts图表进行防抖处理
- 实现服务端渲染(SSR)提升首屏速度
-
推荐算法优化 :
- 离线计算用户相似度矩阵
- 使用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:
关键部署注意事项:
- 生产环境务必关闭Flask的debug模式
- 使用Gunicorn+Gevent作为WSGI服务器
- 配置Nginx反向代理和静态文件服务
- 设置定期数据库备份任务
6. 项目扩展方向
- 实时推荐系统 :引入Kafka消息队列实现实时用户行为处理
- 多模态分析 :结合电影海报图像进行视觉特征分析
- 社交网络分析 :构建用户-电影二部图进行社区发现
- A/B测试框架 :评估不同推荐算法的实际效果
- 移动端适配 :开发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)
更多推荐


所有评论(0)