简介:本资源为基于Python、Spark与Hadoop的用户画像电影推荐系统毕业设计源码案例,面向计算机、大数据及人工智能方向的学生与开发者,可用于课程设计、毕业项目或推荐算法入门实践。压缩包共802个文件,约16.2MB,以JavaScript与CSS前端资源为主,辅以Python核心代码、SQL脚本、HTML页面及图片字体等静态素材,涵盖数据清洗、用户画像构建、协同过滤与内容推荐等模块,目录结构完整便于二次开发。已有82人学习下载。案例完整呈现了从Hadoop存储清洗、Spark内存迭代计算到推荐算法落地的全流程,并涉及实时流处理、隐私加密与交互界面设计等要点,读者可据此掌握用户行为分析、画像建模与个性化推荐系统的搭建思路,适合作为大数据推荐方向的学习范本与工程参考。
1. 从一份毕设源码说起:Python+Spark+Hadoop 电影推荐系统能跑出什么
如果你正在做大数据方向的毕业设计,或者需要一套能写进简历的完整项目,这套基于 Python+Spark+Hadoop 的用户画像电影推荐系统源码值得花时间拆一遍。它不是那种只有几个脚本的玩具工程,而是把数据采集、清洗、用户画像构建、协同过滤推荐、可视化展示串成了一条完整链路。适合三类人:一是毕设选题卡在“推荐系统”方向、需要可运行代码参考的学生;二是想从 Python 单机分析过渡到 Spark 分布式计算的数据从业者;三是需要一套 Hadoop 生态实操案例来理解离线数仓流程的工程师。整套代码围绕 MovieLens 数据集展开,用 Spark 做特征工程和 ALS 矩阵分解,Hadoop 负责底层存储,Python 负责调度和结果输出。下面按“资源是什么、怎么用、坑在哪”的顺序拆开讲。
2. 环境搭建与数据流转:Hadoop 伪分布式 + Spark 本地模式怎么配
2.1 为什么选伪分布式而不是 Docker 镜像
很多教程一上来就让你拉 Hadoop 的 Docker 镜像,省事是省事,但毕设答辩时老师问一句“NameNode 和 DataNode 的通信端口是多少”就容易卡壳。伪分布式搭建虽然多花半小时,但每个配置文件你亲手改过,面试时也能说清楚。常见做法是 Ubuntu 20.04 或 22.04 打底,JDK 选 8 或 11,Hadoop 用 3.x 系列。注意 Hadoop 3.x 默认端口和 2.x 有区别,NameNode Web UI 从 50070 变成了 9870,别照着老教程配半天发现打不开。
安装步骤不复杂,但顺序不能乱:
# 1. 创建 hadoop 用户并配置 sudo 权限 sudo adduser hadoop sudo usermod -aG sudo hadoop # 2. 安装 JDK 并配置环境变量 sudo apt install openjdk-8-jdk -y echo 'export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64' >> ~/.bashrc echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc source ~/.bashrc # 3. 下载并解压 Hadoop 3.3.x wget https://archive.apache.org/dist/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz tar -xzf hadoop-3.3.6.tar.gz -C /usr/local/ sudo mv /usr/local/hadoop-3.3.6 /usr/local/hadoop sudo chown -R hadoop:hadoop /usr/local/hadoop这段脚本做了三件事:建独立用户避免权限混乱、装 JDK 因为 Hadoop 依赖 Java 运行时、解压到/usr/local/hadoop方便后续配环境变量。参数上唯一要注意的是 JDK 版本,Hadoop 3.3.x 对 JDK 8 兼容最好,JDK 17 会有模块化访问警告,虽然能跑但日志里一堆 WARNING 看着心烦。
2.2 核心配置文件改哪几行
Hadoop 伪分布式需要改五个文件,分布在etc/hadoop/目录下。不用全量替换,只改关键行:
<!-- core-site.xml:指定 HDFS 的默认文件系统地址 --> <configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> </configuration> <!-- hdfs-site.xml:设置副本数为 1,伪分布式不需要多副本 --> <configuration> <property> <name>dfs.replication</name> <value>1</value> </property> </configuration> <!-- mapred-site.xml:指定 MapReduce 跑在 YARN 上 --> <configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> </configuration> <!-- yarn-site.xml:配置 NodeManager 的辅助服务 --> <configuration> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> </configuration>fs.defaultFS里的端口 9000 是 NameNode 的 RPC 端口,和 Web UI 的 9870 不是一回事。dfs.replication设成 1 是因为伪分布式只有一个 DataNode,设成 3 会一直报副本不足的警告。改完执行hdfs namenode -format格式化,然后start-dfs.sh和start-yarn.sh启动。用jps命令检查,应该看到 NameNode、DataNode、ResourceManager、NodeManager、SecondaryNameNode 五个进程。
2.3 Spark 本地模式读取 HDFS 数据
Spark 在这套系统里承担计算任务,本地模式就够用,不需要单独搭集群。安装 Spark 后重点配两个环境变量:
# Spark 环境变量配置 export SPARK_HOME=/usr/local/spark export PATH=$SPARK_HOME/bin:$PATH export PYSPARK_PYTHON=/usr/bin/python3 # 验证 Spark 能否读取 HDFS hdfs dfs -mkdir -p /movie/data hdfs dfs -put ratings.csv /movie/data/ pyspark --master local[*]进入 PySpark 交互界面后,用spark.read.csv("hdfs://localhost:9000/movie/data/ratings.csv", header=True)测试读取。如果报Connection refused,八成是 HDFS 没启动或者fs.defaultFS配错了。PYSPARK_PYTHON这个变量容易被忽略,不配的话 Spark 可能调用系统默认 Python 2,跑 Python 3 写的 UDF 直接报语法错误。
3. 用户画像构建:从评分矩阵到标签体系的 Spark 实现
3.1 用户画像的数据维度怎么定
电影推荐系统的用户画像通常从三个维度切入:人口统计学特征(年龄、性别、职业)、行为特征(评分、观看时长、点击流)、兴趣偏好(类型偏好、导演偏好、时段偏好)。MovieLens 数据集自带用户属性表和评分表,可以直接关联。但很多毕设只用了评分数据,画像维度太单薄,答辩时容易被问“你的用户画像和推荐结果之间的关联怎么验证”。
我一般会从评分表里再衍生几个统计特征:用户评分均值、评分标准差、活跃天数、类型偏好熵。这几个指标计算量不大,但能让画像从“静态标签”变成“动态向量”。比如评分标准差低说明用户打分比较随意,推荐时应该降低相似度阈值;类型偏好熵高说明兴趣广泛,可以跨类型推荐。
3.2 用 Spark SQL 做特征聚合
下面这段代码演示从原始评分表构建用户画像特征向量:
from pyspark.sql import SparkSession from pyspark.sql.functions import col, avg, stddev, count, countDistinct, datediff, max, min spark = SparkSession.builder \ .appName("UserProfile") \ .master("local[*]") \ .getOrCreate() # 读取评分数据和电影数据 ratings = spark.read.csv("hdfs://localhost:9000/movie/data/ratings.csv", header=True, inferSchema=True) movies = spark.read.csv("hdfs://localhost:9000/movie/data/movies.csv", header=True, inferSchema=True) # 关联电影类型 ratings_with_genre = ratings.join(movies.select("movieId", "genres"), on="movieId", how="left") # 按用户聚合统计特征 user_profile = ratings_with_genre.groupBy("userId").agg( avg("rating").alias("avg_rating"), # 用户平均评分 stddev("rating").alias("rating_std"), # 评分标准差 count("movieId").alias("rating_count"), # 评分总数 countDistinct("genres").alias("genre_count"), # 看过的类型数 min("timestamp").alias("first_rating"), # 首次评分时间 max("timestamp").alias("last_rating") # 最近评分时间 ) # 计算活跃天数 user_profile = user_profile.withColumn( "active_days", (col("last_rating") - col("first_rating")) / 86400 ) user_profile.show(5)groupBy("userId")后的聚合操作全部在 Spark 执行器上并行计算,数据量大时比 Pandas 快一个数量级。stddev对只有一条评分的用户返回 null,后续做向量拼接时要用fillna(0)处理。active_days用时间戳差值除以 86400 换算成天,注意时间戳单位是秒不是毫秒。这段代码的输出可以直接写入 Hive 表或者 Parquet 文件,供推荐模块调用。
3.3 标签权重怎么定才不拍脑袋
画像标签的权重分配是很多毕设的薄弱环节,常见做法是平均加权,但实际业务里评分行为比人口属性更能反映偏好。我一般用 TF-IDF 的思路给标签加权:用户对某类型的评分次数占其总评分次数的比例作为 TF,该类型在所有用户中的出现频率取倒数作为 IDF。这样既考虑了个人偏好强度,又抑制了大众类型(比如剧情片)的权重膨胀。
具体实现时,先用explode把 genres 字段拆成多行,然后按用户和类型分组统计,最后用窗口函数计算权重。代码不复杂,但要注意explode后数据量会膨胀,如果原始评分表有百万行,拆完可能到千万级,Spark 默认的 shuffle 分区数可能不够,需要手动设spark.sql.shuffle.partitions=200。
4. ALS 协同过滤推荐:参数调优与冷启动处理
4.1 ALS 在 Spark MLlib 里的调用方式
Spark MLlib 的 ALS 实现是这套系统的推荐核心。它把用户-物品评分矩阵分解成两个低秩矩阵,通过交替最小二乘迭代逼近原始矩阵。调用方式很直接:
from pyspark.ml.recommendation import ALS from pyspark.ml.evaluation import RegressionEvaluator # 准备训练数据,ALS 要求用户和物品 ID 是整数 ratings_als = ratings.select( col("userId").cast("integer"), col("movieId").cast("integer"), col("rating").cast("float") ) # 划分训练集和测试集 (training, test) = ratings_als.randomSplit([0.8, 0.2], seed=42) # 构建 ALS 模型 als = ALS( maxIter=10, # 迭代次数 regParam=0.01, # 正则化系数 userCol="userId", itemCol="movieId", ratingCol="rating", coldStartStrategy="drop", # 冷启动策略:丢弃无法预测的项 nonnegative=True, # 非负约束,避免负评分 implicitPrefs=False # 显式反馈 ) model = als.fit(training) # 预测并评估 predictions = model.transform(test) evaluator = RegressionEvaluator( metricName="rmse", labelCol="rating", predictionCol="prediction" ) rmse = evaluator.evaluate(predictions) print(f"RMSE = {rmse}")maxIter设 10 是经验值,再大收敛不明显但耗时翻倍。regParam控制过拟合,0.01 到 0.1 之间调,数据稀疏时往大了调。coldStartStrategy="drop"很关键,测试集里可能有训练时没出现过的用户或电影,不设这个参数预测结果会出 NaN,RMSE 直接变 NaN。nonnegative=True对显式评分数据建议开启,因为评分范围是 0.5 到 5,负预测没有物理意义。
4.2 参数怎么调:从 RMSE 到业务指标
RMSE 是最常用的评估指标,但它衡量的是评分预测误差,不是推荐列表质量。实际调参时我会同时看两个指标:RMSE 和 Top-N 推荐的命中率。RMSE 低不代表推荐结果好,因为用户真正感兴趣的电影可能根本没被评分过。
调参顺序一般是:先固定maxIter=10,在[0.001, 0.01, 0.1, 1]里搜regParam;然后固定最优regParam,在[5, 10, 20]里搜maxIter;最后调rank(隐因子维度),从 10 开始试到 200。rank越大模型容量越大,但训练时间和内存占用也线性增长。本地模式下rank超过 100 就容易 OOM,建议控制在 50 以内。
如果 RMSE 卡在 0.8 左右降不下去,检查一下数据里有没有评分分布极度偏斜的用户。有些用户只打 5 分或只打 1 分,这类极端数据会拉高整体误差。处理办法是设一个评分数量阈值,比如只保留评分次数大于 20 的用户,或者对极端评分做截断。
4.3 冷启动的三种应对策略
冷启动分两类:新用户没有评分记录,新电影没有被人评过分。ALS 的coldStartStrategy只能处理预测阶段的 NaN,不能真正解决推荐问题。常见做法有三种:
第一种是基于内容的兜底推荐。新用户注册时让他选几个喜欢的类型,直接推荐该类型下热度最高的电影。这套系统里可以用movies表的 genres 字段做简单匹配。
第二种是利用人口统计学特征做分组推荐。新用户的年龄、性别、职业和已有用户群匹配,把相似用户群的高分电影推给他。MovieLens 的users.csv里有这些字段,做一次分组聚合就行。
第三种是混合推荐,把 ALS 的协同过滤结果和基于内容的推荐结果加权融合。权重可以设成 0.7 和 0.3,新用户场景下调低协同过滤权重。这套源码里如果没实现混合逻辑,可以自己加一个union操作把两个结果集合并去重。
5. 避坑与排查:这套源码跑不起来时先看这五条
5.1 报错 “java.net.ConnectException: Call From ... to localhost:9000 failed”
现象是 Spark 任务一启动就抛连接异常,日志里反复出现Retrying connect to server。原因通常是 HDFS 没启动,或者core-site.xml里的fs.defaultFS写成了hdfs://localhost:8020但 NameNode 实际监听的是 9000。解决步骤:先jps确认 NameNode 进程在不在,不在就start-dfs.sh;在的话用netstat -tlnp | grep 9000看端口监听状态;最后检查 Spark 的spark-defaults.conf里有没有覆盖fs.defaultFS配置。
5.2 ALS 训练报 “User column must be integer type”
现象是als.fit(training)直接抛IllegalArgumentException。原因是ratings.csv读进来时userId被推断成字符串或长整型,而 ALS 要求整数类型。解决:在select里显式cast("integer"),别依赖inferSchema的自动推断。如果 cast 后还有空值,加一步dropna(subset=["userId", "movieId", "rating"])。
5.3 推荐结果全是同一部电影
现象是给不同用户生成的 Top-10 列表高度重合,甚至第一名都是同一部。原因一般是数据稀疏导致 ALS 退化成全局热门推荐,或者rank设得太低模型没学到个性化特征。解决:先统计评分矩阵的稀疏度(非零元素占比),低于 1% 就要考虑降regParam或增rank;然后检查训练数据里有没有把测试集混进去造成信息泄露;最后确认coldStartStrategy没有把大量预测结果 drop 掉导致输出为空。
5.4 Hadoop 启动后 DataNode 自动退出
现象是start-dfs.sh后jps看到 DataNode 闪一下就没了。原因通常是dfs.datanode.data.dir指向的目录权限不对,或者多次format导致 clusterID 不一致。解决:先看logs/hadoop-hadoop-datanode-*.log里的具体报错;如果是权限问题,chown -R hadoop:hadoop数据目录;如果是 clusterID 不匹配,删掉data和name目录重新format,注意 format 前一定要停掉所有 Hadoop 进程。
5.5 PySpark 里调用 Python 第三方库报 ModuleNotFoundError
现象是 Driver 端能 import 的库,在 Executor 里报找不到模块。原因是 Spark 的 Executor 运行在独立的 Python 环境里,不会自动继承 Driver 的sys.path。解决:单机模式下在spark-env.sh里配PYSPARK_PYTHON和PYSPARK_DRIVER_PYTHON指向同一个 Python 解释器;集群模式下需要用--py-files打包依赖或者用 conda 创建统一环境。这套源码如果用了 numpy 或 pandas,务必确认 Executor 环境里也装了。
6. 进阶技巧:把离线推荐结果对接到 Web 端展示
毕设答辩时如果只展示控制台输出的推荐列表,视觉效果会打折扣。常见做法是把 ALS 的推荐结果写入 MySQL 或 CSV,然后用 Flask 或 Django 搭一个简单页面展示。这里给一个最小化的对接方案:
# 将推荐结果写入 CSV 供 Web 端读取 user_recs = model.recommendForAllUsers(10) # 每个用户推荐 10 部电影 # 展开 recommendations 数组字段 from pyspark.sql.functions import explode user_recs_flat = user_recs.select( "userId", explode("recommendations").alias("rec") ).select( "userId", col("rec.movieId").alias("movieId"), col("rec.rating").alias("predicted_rating") ) # 关联电影标题 final_recs = user_recs_flat.join( movies.select("movieId", "title", "genres"), on="movieId", how="left" ) # 写入本地 CSV final_recs.toPandas().to_csv("/tmp/movie_recommendations.csv", index=False)recommendForAllUsers(10)返回的是数组结构,每个元素包含movieId和rating两个字段,必须用explode炸开才能存成表格。toPandas()会把数据拉到 Driver 端,数据量大时慎用,但推荐结果通常只有几万行,内存扛得住。写入 CSV 后,Flask 端用pandas.read_csv读进来,按userId过滤就能渲染页面。
验证推荐效果时,我习惯抽三个典型用户做人工检查:一个评分次数最多的活跃用户、一个评分次数最少的冷启动用户、一个评分标准差最大的挑剔用户。看活跃用户的推荐列表是否覆盖了他历史偏好的类型,看冷启动用户是否拿到了合理的兜底推荐,看挑剔用户的推荐评分是否普遍偏高。这个习惯是从一次答辩被问“你怎么证明推荐结果合理”之后养成的,从那以后我每次跑完 ALS 都强制走一遍人工抽检,再也没在这个问题上翻过车。希望帮到你。
本文还有配套的精品资源,点击获取