
简介基于Hadoop大数据技术的电影推荐系统项目资源包含可运行源码、SQL脚本与设计文档LW适用于毕业设计、课程设计、大作业和工程实训等场景也适合初学或进阶学习者作为项目实战练手。系统采用Python爬虫采集电影数据结合SpringBoot、Vue、MySQL与Hadoop构成完整技术栈管理员可在后台管理用户、维护电影信息、审核论坛交流并配置系统通过可视化看板查看评分、导演、类型、地区等实时统计图表实现更精准的个性化推荐用户则可在个人中心设置偏好、查看收藏列表与浏览历史。资源共642个文件压缩包大小约26MB涵盖Java后端源码、Vue前端页面、Python爬虫脚本、SQL数据库脚本、启动与运行脚本以及docx/doc说明文档其中还包含JS、CSS、XML等辅助文件用于页面交互、样式与配置目录结构清晰便于分类查阅。目前已有94人学习下载。借助该完整项目可以掌握从数据采集、分布式存储与计算到后端接口开发、前端管理页面搭建的完整项目落地思路附带的SQL文件和设计文档还能辅助快速部署、二次开发或直接作为毕业设计、项目立项的可靠基础。1. 这套电影推荐系统不是让你用 Hadoop 扛实时流量而是补上离线计算这条腿拿到一份 5b002 基于 Hadoop 大数据技术的电影推荐系统设计时很多人第一反应是现在谁还用 MapReduce 做推荐确实线上实时推荐基本被 Flink 和 Spark Streaming 占据但这类 springbootspider 题目的重点本就不在实时性而是一条完整的离线链路spider 负责采数据Hadoop 负责批量算SpringBoot 负责把结果暴露成接口。对课程设计和毕设来说这个组合比单机推荐系统更有辨识度也比纯 Spark 方案更容易部署到普通虚拟机。适合人群很明确想用大数据技术跑通一个闭环又不想把精力全耗在实时计算上的同学以及第一次把 Hadoop 和 Web 工程串起来的一线开发。先把这条链路跑通再谈优化。2. 先想清楚架构这四层各自解决什么问题2.1 推荐数据天生是离线的正好放进 Hadoop电影推荐系统处理的数据是用户对电影的评分、点击、收藏记录。这类数据有几个特点总量大但增长慢、单条价值密度低、建模不要求秒级回算。以豆瓣或 MovieLens 的结构为例一天可能产生几十万条评分一个学期也就是几千万条。这个量级在单机上用 MySQL 也能跑但处理全量数据时需要预先清洗、分布式存储和容错这些正是 Hadoop 的看家本领。题目既然挂在 Hadoop 大数据技术名下就必须把为什么不上单机、为什么不用 Spark 实时算讲清楚否则答辩时容易被问倒。我的选择是Hadoop 负责离线计算层。HDFS 存放原始评分和爬虫落地的电影元数据MapReduce 在凌晨固定时段批量计算用户兴趣或物品相似度把结果写回 HDFS。交互式查询交给 SpringBoot它不碰算法只读已经算好的结果。这个方案的优点是把大数据处理和Web 开发解耦哪一层出了问题都能单独定位。相比直接用 Spark 全家桶Hadoop 的 MR 概念更基础也更容易在白板上画出执行流程。2.2 Spider 采集的数据为什么要先落到 HDFS爬虫在这个项目里不是核心算法而是数据来源。常见的做法有两种一种是爬虫抓完直接写 MySQLSpringBoot 从 MySQL 服务页面一种是抓完先落本地 CSV再通过hadoop fs -put丢进 HDFS。前者适合在线业务后者适合离线训练。我在做这类设计时更倾向第二种理由有三个HDFS 对追加写入和批处理友好MR 直接读 HDFS 上的文件省去先导 MySQL 再导 HDFS的中间步骤原始数据不需要频繁更新正好符合 HDFS 的一次写入、多次读取模型如果爬虫抓坏了删目录重跑即可不会污染业务数据库。爬虫抓的字段要克制别一上来就抓短评全文。我会定义两张最基础的表movies.csv至少包含movieId,title,genresratings.csv包含userId,movieId,rating,timestamp。评分数据尽量从公开数据集拿爬虫只负责补充电影海报、上映年份或简介这样能避开两个坑评分稀疏导致推荐结果全空以及大规模爬取引发的访问频率问题。一句话spider 是配角它不是让你证明爬虫技术有多强而是让推荐系统有数据可用。2.3 SpringBoot 不负责算法它只做接口层和调度入口很多第一次做这个题目的人会把 SpringBoot 当成计算引擎试图在内存里用 Java 写协同过滤这会把项目做成一个普通 Web 应用挂了个 Hadoop 的名字。正确的分工应该是SpringBoot 接收前端的用户 ID 请求从 HDFS 结果目录读取对应的推荐列表返回 JSON另外通过Scheduled或简单的手动触发接口执行上传数据到 HDFS → 提交 Hadoop Streaming 任务的脚本。如果想让查询更快可以在 SpringBoot 里加一层内存缓存把当天的推荐结果预热到ConcurrentHashMap而不是每次请求都去读 HDFS。同一个用户重复登录时命中缓存就不需要再查文件系统。缓存只适合离线结果因为离线结果在下次计算前不会变化非常适合缓存。这样做的好处是答辩时你能讲清楚计算离线做、查询在线做、缓存挡住重复读三个层次而不是一锅粥。落地顺序我固定在六步定目录结构、写爬虫、传 HDFS、提交流程跑 MR、结果落目录、SpringBoot 读结果中间任何一步失败都不影响前后步骤排查。3. 从零搭伪分布式 Hadoop安装、配置和 IDEA 联调3.1 安装前必须定死的三件事JDK、SSH、Hadoop 版本伪分布式是整个项目最容易翻车的环节主要不是 Hadoop 本身难而是环境太杂。我一般建议在虚拟机上装 CentOS 7 或 Ubuntu 20.04 这类 LTS 版本内存给到 4G 以上然后按顺序处理三件事。第一件事是 JDK。Hadoop 2.x 和 3.x 对 JDK 版本要求不同但一致性要求是必须先装 JDK再装 Hadoop再改JAVA_HOME。我常用的组合是 JDK 8 配 Hadoop 3.x 系列兼容性最稳。装完验证java -version然后把JAVA_HOME写进~/.bashrc。第二件事是 SSH 免密登录。伪分布式虽然没有多台机器但 NameNode 和 DataNode 之间仍然要通过 SSH 启动免密省掉每次输入密码的麻烦。命令很简单ssh-keygen -t rsa -P -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys ssh localhost测试时如果出现Host key verification failed先执行一次ssh-keyscan localhost ~/.ssh/known_hosts。这一步没做通后面执行start-dfs.sh会出现 DataNode 连不上 NameNode 的怪现象日志却干干净净。第三件事是 Hadoop 安装路径。解压后我习惯统一放在/usr/local/hadoop而不是/root/hadoop因为后面很多配置文件里要写绝对路径路径写得太深容易出错。解压完成后在~/.bashrc里加export HADOOP_HOME/usr/local/hadoop export PATH$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin然后source ~/.bashrc执行hadoop version确认能输出版本信息。到这里安装阶段完成。3.2 三个必须手改的 XML 配置文件Hadoop 伪分布式只需要改三个文件core-site.xml、hdfs-site.xml、yarn-site.xml。千万不要去改hadoop-env.sh里的内存参数堆得过大小虚拟机反而会起不来。core-site.xml指定默认文件系统为 HDFS并设置 NameNode 地址configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property property namehadoop.tmp.dir/name value/usr/local/hadoop/tmp/value /property /configurationhadoop.tmp.dir是 Hadoop 持久化数据的根目录必须是一个真实存在的目录并确认当前用户有写权限。默认值在/tmp下系统重启会被清空这是 NameNode 起不来的常见原因之一。hdfs-site.xml设置副本数为 1configuration property namedfs.replication/name value1/value /property property namedfs.namenode.name.dir/name value/usr/local/hadoop/nn/value /property property namedfs.datanode.data.dir/name value/usr/local/hadoop/dn/value /property /configuration伪分布式只有一台机器副本数设成 3 会导致 DataNode 一直处于Under-Replicated状态虽然不影响小实验但看着碍眼也会拖慢写入。nn和dn目录也要提前建好。yarn-site.xml是后面提交任务卡住的重点先给一个最小配置configuration property nameyarn.nodemanager.resource.memory-mb/name value3072/value /property property nameyarn.nodemanager.vmem-check-enabled/name valuefalse/value /property property nameyarn.resourcemanager.hostname/name valuelocalhost/value /property /configurationmemory-mb要结合虚拟机物理内存设置我给 4G 虚拟机留 3Gvmem-check-enabled在伪分布式环境建议关掉否则 JVM 虚拟内存超限会把容器杀掉任务反复失败。3.3 启动、校验以及 Windows 下用 IDEA 联调 HDFS配置完成后第一次启动要先格式化 NameNode这条命令只能执行一次重复格式化会把之前的元数据全部清掉hdfs namenode -format start-dfs.sh start-yarn.sh jpsjps输出里应该看到NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager五个进程。少任何一个都说明启动没成功不要急着看代码先去$HADOOP_HOME/logs目录翻对应组件的.log文件。接下来创建项目要用的目录并上传一个测试文件hadoop fs -mkdir -p /movie/raw echo 1,101,5.0 /tmp/test.csv hadoop fs -put /tmp/test.csv /movie/raw/ hadoop fs -cat /movie/raw/test.csv能输出1,101,5.0说明 HDFS 链路通了后面就可以把真实评分数据丢进去。Windows 上用 IDEA 调试 SpringBoot 时Java 代码要访问 Linux 上的 HDFS最常见的问题是没有把core-site.xml放进 classpath以及缺少winutils.exe。我一般会在src/main/resources下放一份core-site.xml里面把fs.defaultFS指向hdfs://192.168.x.x:9000然后把 Windows 上的HADOOP_USER_NAME环境变量设成 Linux 上的启动用户通常是root或hadoop这样能省掉大量权限问题。开发机联不上时优先在宿主机用telnet 虚拟机IP 9000测端口通不通而不要先怀疑代码。4. 把推荐链路跑通爬虫、Streaming 计算与 SpringBoot 接口4.1 用 Jsoup 写一个可停可续的爬虫把电影数据落到本地 CSV既然标题里有spider爬虫模块建议直接放在 SpringBoot 工程里用 Jsoup 做页面解析这样整个项目保持 Java 技术栈统一。下面是最小版本的核心逻辑public void crawlMovieList() { String url https://example.com/movies?page1; Connection conn Jsoup.connect(url) .timeout(3000) .header(User-Agent, Mozilla/5.0); Document doc conn.get(); Elements items doc.select(.movie-item); try (BufferedWriter writer Files.newBufferedWriter( Paths.get(movies.csv), StandardCharsets.UTF_8)) { writer.write(movieId,title,genres\n); for (Element item : items) { String title item.select(.title).text().trim(); String genres item.select(.genres).text().trim(); writer.write(item.attr(data-id) , title , genres \n); } } catch (IOException e) { log.error(爬取失败, e); } }代码里的example.com只是占位实际换成你选定的目标页面。这段逻辑有三个参数值得调timeout设 3 秒是为了不让单页卡死整个任务User-Agent必须带上很多页面会拦截无头请求写入文件用StandardCharsets.UTF_8避免中文电影名变成乱码。爬完后先人工抽查几十行 CSV确认没有奇怪的引号或换行再执行hadoop fs -put movies.csv /movie/raw/movies.csv不要直接让爬虫去连 HDFS中间保留本地 CSV 这一步既方便检查数据质量也能在爬虫写坏时快速重来。这是反复踩坑后留下的习惯。4.2 用 Hadoop Streaming 跑电影相似度mapper.py 和 reducer.py 的最小版本推荐计算如果全用 Java MapReduce 写类多、打包慢不适合课程设计这种短周期项目。我一般用 Hadoop Streaming 提交 Python 脚本既保留 MR 分布式执行又能快速调逻辑。任务输入是/movie/raw/ratings.csv每一行是userId,movieId,rating目标是统计被同一个用户看过的电影两两之间共现次数作为电影相似度的基础。mapper.py读入每个用户的全部评分把同一用户看过的电影两两组合输出movieA:movieB\t1#!/usr/bin/env python3 import sys def parse(): user_movies {} for line in sys.stdin: line line.strip() if not line: continue parts line.split(,) if len(parts) 3: continue uid, mid, rating parts[0], parts[1], parts[2] user_movies.setdefault(uid, []).append(mid) for uid, mids in user_movies.items(): mids sorted(set(mids)) for i in range(len(mids)): for j in range(i 1, len(mids)): print(f{mids[i]}:{mids[j]}\t1)reducer.py累加相同键的次数输出共现次数前 100 对#!/usr/bin/env python3 import sys from collections import Counter counter Counter() for line in sys.stdin: line line.strip() if \t not in line: continue key, _ line.split(\t, 1) counter[key] 1 for pair, cnt in counter.most_common(100): print(f{pair}\t{cnt})这里有个容易忽略的点同一用户重复看同一部电影时评分会出现多行set(mids)去重后共现次数才不会被同一用户重复刷高。当单个用户观影量很大时建议先按评分排序取 TopN 再两两组合否则组合数随观影量平方增长map 输出会爆炸。提交命令如下hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-*.jar \ -files mapper.py,reducer.py \ -mapper python3 mapper.py \ -reducer python3 reducer.py \ -input /movie/raw/ratings.csv \ -output /movie/sim-files是分发脚本到所有节点的关键不加它任务会报File not found。输出目录/movie/sim必须事先不存在否则 Hadoop 会拒绝执行。跑完用hadoop fs -cat /movie/sim/part-00000查看能看到类似101:102\t3的结果就说明计算链路通了。4.3 SpringBoot 读取 HDFS 结果FileSystem API 与用户兜底策略计算层产出相似度结果后SpringBoot 的职责是把它变成可查询的推荐接口。下面这段代码用 Hadoop FileSystem API 读取/movie/sim下所有 part 文件整理成倒排索引结构Configuration public class HdfsClient { private FileSystem fileSystem; PostConstruct public void init() throws IOException { Configuration conf new Configuration(); conf.set(fs.defaultFS, hdfs://localhost:9000); fileSystem FileSystem.get(conf); } public ListString readSimResult() throws IOException { ListString lines new ArrayList(); RemoteIteratorLocatedFileStatus files fileSystem.listFiles(new Path(/movie/sim), true); while (files.hasNext()) { LocatedFileStatus status files.next(); try (BufferedReader reader new BufferedReader( new InputStreamReader(fileSystem.open(status.getPath())))) { String line; while ((line reader.readLine()) ! null) { lines.add(line); } } } return lines; } }fs.defaultFS可以直接从 classpath 里的core-site.xml读取但我在代码里显式写了一次目的是让大家看清这个参数来自哪里。在实际项目中连接地址应该放到application.yml用Value注入不要写死在类里。读取结果后用字符串解析movieA:movieB\t次数建立相似电影 权重的映射再写一个RecommendController返回 JSON。有一个必须处理的情况冷门用户没有出现在任何共现结果里。我的做法是接口先查用户已看过的电影再根据相似度映射找候选电影如果候选为空就返回评分次数最多的 Top10 热门电影兜底并单独标一个source: hot字段。这样既不会让前端拿到空列表也方便答辩时解释冷启动策略。5. 避坑清单我跑这个项目时翻过的车请你直接跳过5.1 NameNode 起不来日志里全是 incompatible clusterID现象格式化后第一次执行start-dfs.shNameNode 进程启动后立刻退出日志里出现storage directory ... has incompatible clusterIDDataNode 那边始终连不上。原因重复执行了hdfs namenode -format而 DataNode 的持久化目录还保留着上一次格式化的 clusterID新旧集群 ID 对不上。通常是参考教程时手痒多敲了一次格式化命令。解决全部停止后清掉配置过的临时目录再重新格式化这是唯一可靠的解法。rm -rf /usr/local/hadoop/tmp /usr/local/hadoop/nn /usr/local/hadoop/dn hdfs namenode -format start-dfs.sh注意格式化命令一生只敲一次之后除非换机器再也不要碰它。清空目录后就能恢复但之前上传的数据会全部丢失。5.2 YARN 任务一直卡在 ACCEPTED节点日志什么错都不报现象提交 Hadoop Streaming 后任务状态一直停在ACCEPTEDNodeManager 里看不到 container 启动等一小时也不动。原因为伪分布式配置的yarn.nodemanager.resource.memory-mb过大或者 JVM 虚拟内存检查开启容器启动了又被静默杀掉。这种问题最像玄学因为它不给你可读的堆栈纯靠日志关键词去猜。解决先确认虚拟机内存free -h看一下真实可用内存然后把yarn-site.xml中的memory-mb调整到不超过物理内存的一半并确认已经设置yarn.nodemanager.vmem-check-enabled为false。改完重启 YARN任务就能正常跑到RUNNING和SUCCEEDED。5.3 SpringBoot 读 HDFS 报Permission denied: userAdministrator现象IDEA 里启动 SpringBoot调用读取/movie/sim的接口日志抛出Permission denied文件明明用 hadoop 用户能读。原因Java 客户端默认取当前系统用户名Windows 下是AdministratorLinux 上的 HDFS 用户是hadoopHDFS 目录权限默认 755其他用户只能读如果结果目录的写权限也没开问题会更明显。解决开发环境最省事的办法是启动 SpringBoot 前设置HADOOP_USER_NAMEhadoop也可以在代码里用UserGroupInformation.createRemoteUser(hadoop)再登录代理。如果只是单机实验直接修改 HDFS 目录权限也可以但不推荐养成关权限的习惯。5.4 爬虫抓下来的中文电影名到推荐结果里全是乱码现象本地 CSV 打开正常传到 HDFS 后用hadoop fs -cat也正常但推荐接口返回的中文标题变成æµ·ç一类字符。原因编码链路不统一。爬虫输出文件用了StandardCharsets.UTF_8但某些中间脚本用系统默认编码读取或者 CSV 被 Excel 打开过一次并保存成了 GBKHadoop Streaming 读入时按平台默认编码解析于是中文字节被错误翻译。解决全链路强制 UTF-8。爬虫写出时指定StandardCharsets.UTF_8上传前检查文件头部是否有 BOMHadoop Streaming 脚本里读取标准输入后立即encode(utf-8)SpringBoot 读取 HDFS 文件时用InputStreamReader(fileSystem.open(path), StandardCharsets.UTF_8)。乱码问题一旦发生检查 CSV 文件字节比检查代码更有效因为大多数时候是源文件已经被改坏了。5.5 相似度计算结果全是 0或者只有共现没有加权现象part-00000里能输出键值对但推荐列表里排序跟人工判断完全不符热门烂片排在最前面。原因只用共现次数做相似度对热门电影不友好另外评分数据如果太稀疏大多电影对只共现一两次区分度极低。还有一个隐蔽原因mapper.py里没有过滤低评分用户打了 1 星的电影也参与共现等于把不喜欢的电影也当成了相似。解决协同过滤里我一般先按评分过滤比如只保留rating 3.0的记录再跑共现然后在 reducer 里把共现次数换算成 Jaccard 系数或余弦相似度而不是直接输出次数。如果数据量实在太小就回去扩大评分数据来源算法调参解决不了数据稀疏的根本问题。6. 先做离线评估再把计算层从 Hadoop 平滑换成 Spark6.1 用留出法评估推荐质量别只靠人工看在收尾之前建议给推荐结果加一个最简单的离线评估。把评分数据按时间分成训练集和测试集训练集算相似度测试集用来检查用户是不是真的看了推荐电影。Top10 推荐里命中测试集的个数除以 10 就是精确率命中数除以该用户测试集评分总数就是召回率。真实稀疏数据上Top10 命中两三部已经算正常数字虚高反而要怀疑数据泄漏。6.2 保留接口层用 Spark ALS 替换 Streaming 脚本如果做完 Hadoop 还想继续延伸最顺滑的路线是把 Streaming 的 mapper/reducer 替换成 Spark 的 ALS输出格式保持userId,itemId,ratingSpringBoot 的读取逻辑一行都不用改。数据仍放 HDFS批量计算改成spark-submit提交项目就从 Hadoop 特色自然过渡到大数据技术特色骨架不变。我自己在这类项目上吃过最大的亏是急着调算法参数最后发现是电影 ID 有重复。现在习惯是先让数据主键、编码、分区目录全部过一遍校验再开机器学习。数据管道干净了算法随便调都有好结果。希望帮到你。本文还有配套的精品资源点击获取