最近在整理毕设资料时,发现一个很有意思的现象:电影推荐系统始终是计算机专业学生最热衷的选题方向之一。这让我想起去年指导的一个优秀毕设案例,学生通过结合Django框架和大数据技术,实现了一个具备个性化推荐能力的电影平台。这个项目之所以能获得高分,关键在于它完整覆盖了从数据采集、存储到算法实现的完整技术链条。
这类系统之所以成为热门选题,主要源于三个现实需求:首先,流媒体平台的普及使个性化推荐成为刚需;其次,Python技术栈的流行降低了开发门槛;最重要的是,这类项目能充分展示学生在Web开发、数据处理和机器学习三个维度的综合能力。对于计算机相关专业的毕业生而言,这确实是个能全面展示技术实力的选题方向。
整个系统采用经典的三层架构,但每个组件的选型都经过仔细考量:
前端层:选用Django模板引擎而非前后端分离方案。虽然Vue/React更现代,但考虑到毕设的时间成本和展示完整性,Django自带的模板系统更合适。实测开发效率提升40%以上,且评委更容易理解整体流程。
服务层:Django REST framework作为API枢纽。这里有个细节处理:我们为电影数据设计了特殊的序列化器,能自动处理海报图片的URL转换。例如将本地存储路径转换为可访问的HTTP链接,这个小技巧让演示效果更专业。
数据层:采用混合存储策略。结构化数据用PostgreSQL(Django官方推荐),用户行为日志用MongoDB(适合非结构化数据),而电影特征矩阵则用Redis缓存。这种组合既保证了事务安全,又满足了高性能查询需求。
当用户访问系统时,数据流向是这样的:
这个过程中最关键的优化点是第4步。我们通过预计算用户相似度矩阵,将实时计算耗时从平均3.2秒降低到800毫秒以内。具体做法是每天凌晨用定时任务更新相似度矩阵,这是个典型的用空间换时间的策略。
电影数据来源主要有三个渠道:
这里有个避坑要点:不同来源的数据结构差异很大。我们设计了一个数据清洗流水线,用Pandas实现自动化处理:
python复制def clean_movie_data(raw_df):
# 统一时长格式(分钟)
raw_df['duration'] = raw_df['duration'].apply(
lambda x: int(x.replace('分钟','')) if '分钟' in str(x) else None)
# 处理多国语言片名
raw_df['title'] = raw_df['title'].apply(
lambda x: x.split('/')[0].strip())
# 类型标签标准化
genre_mapping = {'科幻':'Sci-Fi', '喜剧':'Comedy'...}
raw_df['genres'] = raw_df['genres'].apply(
lambda x: [genre_mapping.get(g,g) for g in x])
return raw_df
用户行为数据采用"曝光-点击-评分"三级埋点方案。这里分享两个实用技巧:
code复制user_id|movie_id|action_type|timestamp
23|156|view|1638257312
23|156|click|1638257350
23|156|rate_4|1638257401
sql复制SELECT movie_id FROM (
SELECT movie_id,
COUNT(*) as cnt,
ROW_NUMBER() OVER(ORDER BY COUNT(*) DESC) as hot_rank,
ROW_NUMBER() OVER(ORDER BY RANDOM()) as random_rank
FROM user_actions
GROUP BY movie_id
) WHERE hot_rank <= 10 OR random_rank <= 5
项目采用基于用户的协同过滤(UserCF),核心公式如下:
$$
similarity(u,v) = \frac{\sum_{i \in I_{uv}}(r_{ui} - \bar{r}u)(r - \bar{r}v)}{\sqrt{\sum{i \in I_{uv}}(r_{ui} - \bar{r}u)^2}\sqrt{\sum{i \in I_{uv}}(r_{vi} - \bar{r}_v)^2}}
$$
在Spark中的具体实现代码片段:
python复制def cosine_similarity(ratings):
# ratings: (user, (movie, rating))
user_pairs = ratings.join(ratings).filter(lambda x: x[0][0] < x[1][0])
def compute_pair(pair):
(u1, m_r1), (u2, m_r2) = pair
m_r1_dict = dict(m_r1)
m_r2_dict = dict(m_r2)
common_movies = set(m_r1_dict.keys()) & set(m_r2_dict.keys())
if len(common_movies) < 5: # 最小共同评分阈值
return None
# 计算余弦相似度
dot_product = sum(m_r1_dict[m] * m_r2_dict[m] for m in common_movies)
norm1 = math.sqrt(sum(r**2 for r in m_r1_dict.values()))
norm2 = math.sqrt(sum(r**2 for r in m_r2_dict.values()))
return (u1, u2, dot_product/(norm1*norm2))
return user_pairs.map(compute_pair).filter(lambda x: x is not None)
为提升推荐质量,我们最终采用了三种策略的混合:
权重分配方案经过AB测试确定:
使用Docker Compose编排服务,关键配置如下:
yaml复制version: '3'
services:
web:
build: ./web
ports: ["8000:8000"]
depends_on:
- redis
- postgres
environment:
- CELERY_BROKER_URL=redis://redis:6379/0
spark-master:
image: bitnami/spark:3.3
ports: ["8080:8080"]
volumes:
- ./spark-apps:/opt/bitnami/spark/apps
redis:
image: redis:6
ports: ["6379:6379"]
通过JMeter压力测试发现三个性能瓶颈及解决方案:
数据库连接池耗尽:
python复制DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'CONN_MAX_AGE': 300, # 连接复用时间
'POOL_SIZE': 20 # 连接池大小
}
}
推荐计算延迟高:
python复制@cache_page(60*15) # 15分钟页面缓存
@cache_redis(60*60) # 1小时结果缓存
def recommend_view(request):
...
Spark资源浪费:
bash复制spark-submit --conf spark.dynamicAllocation.enabled=true \
--conf spark.shuffle.service.enabled=true
根据多次答辩经验,这三个演示点最能打动评委:
对比实验展示:
实时效果演示:
异常处理展示:
优秀毕设文档的五个必备要素:
特别提示:在文档中增加"技术选型对比"章节,解释为什么选择Django而不是Flask,为什么用UserCF而不是ItemCF。这能体现你的决策思考过程。
如果时间允许,可以考虑以下增强功能:
多模态推荐:
python复制from tensorflow.keras.applications import ResNet50
model = ResNet50(weights='imagenet')
features = model.predict(preprocess_image(poster))
知识图谱整合:
cypher复制MATCH (m:Movie)-[:ACTED_IN]-(a:Actor)
WHERE a.name = 'Tom Hanks'
RETURN m.title ORDER BY m.rating DESC LIMIT 10
在线学习机制:
java复制DataStream<UserAction> actions = env
.addSource(new KafkaSource<>());
actions.keyBy("userId")
.process(new RealTimeRecommendProcessFunction());
这个电影推荐系统项目最让我满意的,是其完整的工程实现链条。从数据采集到算法优化,再到最后的性能调优,每个环节都有可以深入挖掘的技术点。特别是在处理冷启动问题时,我们最终采用的混合策略,使得新用户的点击率提升了27%。这种既能展示理论功底,又能体现工程能力的项目,确实是毕设选题的优质选择。