每年到了毕设季最常被问的一句话就是“有没有那种又有技术含量、又不会太冷门、还能讲得清楚的题目”。出行方式推荐系统恰好是我个人觉得性价比极高的一类选题。它表面上看是一个推荐系统底下却踩中了数据采集、数据预处理、分布式存储、离线计算、Web框架、可视化这几条主线无论你是想拿来做毕设还是单纯想把它当一个练手项目从头到尾跑一遍都能在这个题目里找到足够深的水又不会深到游不回来。这篇文章就围绕我实际做过的这套系统展开从选题思路、技术栈取舍、算法设计到爬虫清洗、Django和Hadoop的对接最后把答辩时可能被问到的问题和我自己踩过的坑一并交代清楚。为了照顾不同基础的读者我在描述时把“为什么这样做”放在和“怎么做”同等重要的位置上尽量做到看完整篇就敢动手。1. 为什么我推荐用“出行方式推荐”作为大数据毕设题目先说个很现实的问题毕设选题最怕的是“什么都做什么都浅”。如果题目是简单的在线购书或学生管理系统技术点基本上一次请求、一次查询就讲完了答辩时PPT撑不满十分钟老师一追问系统瓶颈在哪里很容易哑火。如果题目过于偏底层比如自研分布式文件系统或大规模实时计算引擎工作量又明显超出本科生能驾驭的范畴容易把自己劝退。出行方式推荐系统刚好站在中间课题足够清晰但是延展开来的分支非常多。这个题目真正有意思的地方在于“推荐”的前提是你必须先有数据。而数据怎么来、数据有多大、清洗到什么程度、如何存进HDFS、怎么用MapReduce跑出一个统计或相似度结果这些环节每一个都有独立的技术命题连成一条线之后就是完整的闭环。哪怕你最终只把推荐做成了“基于用户历史出行行为给候选方案打分排序”也已经覆盖了一个简易推荐系统的全部要素。合适的学生群体大概是两类一类是毕设选了它需要快速理解并亲手复现另一类是准备面试作品集想在一到两个月内产出一个有说服力的项目。还有一个容易被忽视的好处这个题目极其好讲故事。答辩时你不需要让老师凭空想象系统好不好用只要打开页面输入一个起终点系统给出公交、地铁、骑行、打车组合方案旁边是ECharts展示的热力图和柱状图视觉冲击力直接拉满。大数据方向的老师最关心“你有没有真的处理过大数据”你把HDFS存储的日志结构、MapReduce任务的结果文件往PPT上一放比任何空话都有力。2. 技术栈选型Django、Hadoop、MySQL三者到底怎么分工做这个系统之前最需要想明白的一件事是Django和Hadoop分别负责什么。很多人容易陷入两个极端要么把Django当成全部数据和计算全塞进MySQL最后发现“大数据”只是挂个名要么把Hadoop神化恨不得每张表都放HDFS换来一堆完全没有必要的开发成本。我的处理原则是四个字冷热分离。高频写入、事务性强、需要秒级响应给Web页面渲染的数据比如用户信息、历史订单、起点终点记录全部放在MySQL里。采集来的原始出行日志、爬虫产生的半结构化JSON、以及离线计算的中间结果这些数据量庞大、不要求实时读取、主要用于批量分析计算则放进HDFS。Django作为Web应用框架只负责“接请求、调业务逻辑、读写数据库、返回渲染结果”。Hadoop则扮演后台计算引擎的角色定期把HDFS里的原始数据跑成统计结果再回写MySQL供网页查询。这样一套组合下来答辩被问到“Hadoop在你的系统里到底做了什么”的时候你能给出非常清晰的链路爬虫采集日志 → 原始数据写入HDFS → MapReduce任务定时统计城市各区域出行热度、交通方式占比、高峰时段分布 → 结果输出到MySQL → Django从MySQL读结果做推荐权重调整。Hadoop没有被架空Django也没有被拖慢各得其所。环境方面我强烈建议在Ubuntu上装Hadoop的伪分布式模式先跑通流程等逻辑全部验证通过了再考虑多台机器的集群方案。伪分布式足够毕设使用也足够你理解NameNode、DataNode、ResourceManager各自的作用。开发机保持本机安装Python、Django、MySQL即可Hadoop用虚拟机或远程服务器都行注意Java版本要和Hadoop版本匹配这一点后文专门讲。3. 推荐算法怎么设计协同过滤打底TF-IDF做文本相似度补充推荐系统核心不在“推荐”这两个字而在“系统怎么学习人的偏好”。我这个项目的推荐策略分了两层第一层是传统的用户协同过滤第二层用TF-IDF对候选出行方案的描述文本做相似度排序把两层结果加权融合最终的分数决定展示的顺序。用户协同过滤的思路其实非常简单一句话和你喜欢过同样东西的人他们喜欢的其他东西你也更可能喜欢。落到出行场景如果用户A坐过地铁2号线从甲地到乙地用户B也有类似的通勤历史那B在某次查询相似路线时系统会优先把地铁方案放在前面。计算过程主要分三步第一步构建用户—出行方式的评分矩阵评分来源可以是真实点击次数、收藏次数、方案使用完成后的评分以及按时间衰减的权重第二步计算用户之间的相似度。相似度公式我实际用的是修正余弦相似度因为原始余弦相似度对“部分用户评分普遍偏高”的情况敏感会高估两个都爱打高分的人修正后先减去每个用户的平均分再算余弦效果稳定很多。计算过程大致是对两个用户共同评过分的那些出行方案分别求出评分向量再代入公式计算夹角余弦值得到1到-1之间的相似度。第三步是为相似用户没体验过的候选方案做预测评分。公式是目标用户对方案s的预测分等于“相似用户对s的评分加权平均”权重就是相似度。为了防止热门方案被无脑推上来我额外对权重做了归一化处理预测分除以该相似用户群的相似度绝对值之和再乘一个惩罚系数这个系数和方案的热门程度成反比。这样设计之后冷门但符合用户习惯的骑行路线也能冒出来不会永远被地铁霸榜。第二层TF-IDF出场解决的问题是“第一印象”。新用户没有任何历史行为时协同过滤直接失灵因为矩阵里根本没有这个用户的向量。这时候我会走文本匹配把每个候选出行方案描述成一段标签文本比如“地铁2号线 → 换乘公交318路 → 步行400米 总时长48分钟 花费4元 途经商圈3个”再用TF-IDF提取这段文本的特征词向量。用户第一次输入起终点后系统从历史相似行程的描述中提取关键词和候选方案的向量做余弦相似度作为初始推荐分。由于词向量维度不高计算量可以接受不需要上Spark单机跑完全没问题。推荐结果最终融合公式可以简单设置成最终得分 0.7×协同过滤预测分 0.3×文本相似度分。如果是新用户协同过滤分为0则自动退化为纯文本匹配如果老用户数据足够多文本匹配项会慢慢弱化避免模板化推荐。整个权重逻辑我在后台单独建了一张权重配置表方便随时调整不用改代码。4. 出行数据从哪来爬虫、公开接口与数据清洗的完整流程再好的算法没有数据就是空中楼阁。这个项目最花时间的环节就是数据采集我前前后后大概花了四天时间才凑齐能够让推荐算法跑起来的数据集。这里优先建议两条合规渠道一是各大地图服务商提供的开放接口这类数据噪声小、请求规则清晰二是爬取公开的公交线路、地铁站点、骑行路线信息。不推荐用过于激进的方式去获取受限的商业数据学术练手项目没必要冒这个风险。我把数据分为三类用户行为数据、出行路线方案数据、城市POI数据。用户行为数据是模拟出来的根据用户画像生成100个左右的虚拟用户每人生成20到50条不等的出行记录字段包括用户ID、出发地、目的地、交通方式、日期、天气、时长、满意度。这样规模的行为数据在算法验证阶段已经完全够用真假用户混在一起。路线方案数据通过调用接口获取分别拉取地铁方案、公交方案、骑行方案和打车方案记录总耗时、总花费、步行距离、换乘次数。城市POI数据用来给路线打语义标签比如终点靠近大学城、商圈、医院配合TF-IDF做特征扩展。写到爬虫代码时有个很容易被忽略的细节单线程爬接口容易超时但不加节流又会触发服务端限流。我实际的做法是维护一个Redis队列每个任务带一个置信区间使用两个线程的并发度每个线程在两次请求之间随机sleep 0.3到0.8秒配合重试三次、指数退避的策略。整体速度不算快但胜在稳定。数据落地的格式我全部统一成JSON行文件每行一条记录以后无论是直接丢入HDFS还是用MapReduce解析都极其方便。数据清洗这一步必须单独拿出来说因为它在毕业论文里也是最容易写出工作量的一部分。我清洗环节处理的主要问题有四类。第一类是被反爬拦截返回的空数据或错误描述识别后直接丢弃成无效记录。第二类是坐标漂移同一出发地因为GPS精度不同出现多个相差几十米的经纬度我用geohash取6位精度做聚类把同一格子内互相邻近的坐标归一化到同一个代表点上。第三类是重复路线同一对起终点在同一天可能被爬虫抓了两遍用MD5哈希去重。第四类是语义冲突比如时长字段出现负数、换乘次数是小数、金额超过城市出租车起步价的正常人能想象的范围这些都是明显的脏数据要么按字段规则修正要么整条删除。清洗完成后我做了两个关键统计动作。第一个是按天聚合生成“日期-城市-区域-交通方式-记录数”的汇总表既方便ECharts画热力图也是MapReduce跑业务指标的原始输入。第二个是按用户聚合生成用户行为稀疏矩阵矩阵的行是用户ID列是出行方案ID值是该用户对该方案的综合评分。这个矩阵直接导出成CSV后续协同过滤计算只需要读这一个文件不用反复查数据库。5. Django项目结构怎么搭后台管理页面对整个系统的价值看很多毕设系统页面做得花里胡哨后台却乱成一团。我的经验是Django项目结构越清晰答辩时越好讲因为你能直接说“这个Controller只做请求转发推荐逻辑在Service层数据访问在DAO层”。虽然Django官方不强制分层但按这个思路组织代码可读性和可维护性都会好很多。具体的项目目录参考如下主工程目录放着配置文件下面按业务模块拆成users、travelHistory、recommendation、statistics、crawlerManagement、dataManager六个子应用。每个子应用再各自包含urls.py、views.py、models.py、services.pyservices.py里放全部的业务逻辑views.py只保留精瘦的接口代码。模板放在统一的templates目录静态资源统一走Django的static目录。这样的好处是后期调试推荐算法时完全不用碰视图层给你省下大量时间。数据库模型设计上我建的核心表大概有六张用户表、出行记录表、方案候选表、用户评分表、方案标签表、推荐权重配置表。其中出行记录表和评分表是数据量最大的两张也是你在论文里描述“数据量大”的底气所在。对于日期时间字段务必使用带时区的DateTimeField不然部署到服务器上容易出现时间偏差爬虫调度任务也容易出问题。有一个实用技巧是用Django自带的Admin后台对整个项目做管理。把这个后台作为管理员的操作入口把出行记录、推荐的权重配置、定时任务的执行状态全部注册进去。老师看你答辩演示时打开管理页面动一下权重数字再切回首页看推荐结果变化这种“可干预的推荐系统”远比一个黑盒系统更有说服力。前端展示方面我用Bootstrap做基础页面框架ECharts负责可视化。首页做成一个起终点输入框输入后分别请求推荐接口和统计接口。推荐接口返回排名列表统计数据则渲染三张图城市各区域出行热力图、不同交通方式的使用占比饼图、一周内各时段出行流量折线图。这里有个细节值得提一下接口返回的结构要前后端协商固定我的统一格式是{code, message, data:{recommendations, statistics}}Django侧用JsonResponse返回前端用fetch解析这样联调时基本不用对代码。6. Hadoop对接与排障从伪分布式到定时计算我是怎么跑通的Hadoop部分最完整的落地环节有三个数据入HDFS、写MapReduce任务、定时调度。先说数据入HDFS这一步很多教程会让你手动hdfs dfs -put但真实项目中这种方式低效。我让爬虫在采集完一天的原始日志后自动调用Hadoop命令行接口把当天的目录put进去同时Django的定时任务负责定期执行这个动作。具体命令大概是hdfs dfs -put /data/raw_logs/2024-xx-xx.json /user/recommend/raw_logs/2024-xx-xx/。用日期作为目录层级既是HDFS的常见组织方式也方便后续增量计算时按目录过滤数据。MapReduce部分我用Java写了一个交通方式统计任务输入是JSON行文件输出是各交通方式在不同时间段的使用次数。整个过程要注意的是JSON解析库的依赖以及输出Key的写法。我用的Key是“交通方式#小时”这样Combiner和Reducer可以直接按小时归并最后输出的结果文件按“方式-小时-计数”的格式存放在HDFS的结果目录下。Django侧则通过一个独立的同步脚本把结果文件下载到本地解析后写入MySQL的统计表。实际跑一个几十MB的数据集伪分布式模式下大概需要二十秒左右这个时间完全可接受。关于Hadoop环境最值得说的坑是我调了整整两天的Java版本问题。Hadoop对Java版本非常敏感用OpenJDK 17跑Hadoop 3.1.x启动时能遇到各种不明所以的报错换回OpenJDK 8之后就顺了。Hadoop配置文件里的core-site.xml、hdfs-site.xml、yarn-site.xml每一项参数的作用都值得花半小时读一读官方文档特别是副本数至少设为1、临时目录权限、NameNode和DataNode的端口配置任何一个不对都可能导致进程起不来。这里给出我当时排障的一个完整链路供大家复现排查思路某次系统重启后Django后台能看到日志存储失败每分钟往HDFS写入都超时。第一时间没有怀疑集群而是先看业务日志有没有异常确认日志显示connection refused于是判断问题出在Hadoop端口。下一步执行jps命令发现DataNode没有启动。查看DataNode日志文件看到“File /data/hadoop/tmp/dfs/data/current/VERSION has an incompatible version”这个报错。原因就很清楚了是因为多次格式化NameNode导致DataNode的namespaceID与NameNode不一致。解决办法是先清空DataNode数据目录再执行hdfs namenode -format最后重新启动各进程。很多教程不会提醒你格式化前要清空DataNode目录导致网上大量同学卡在这一步。这一整套排查链路本身就是很好的答辩素材。再补充一个定时任务的经验Django的django-crontab可以方便地设置定时任务把HDFS同步、MapReduce结果同步、推荐模型评分更新这三个任务写成三个管理命令并分别设置不同执行频率。比如爬虫数据入HDFS每天凌晨执行MapReduce统计任务每天凌晨一点执行评分更新任务每小时执行一次。注意django-crontab默认使用服务器本地时区部署时要注意时间偏差建议每次配置完用manage.py crontab show查看任务时间确认无误再enable。7. 答辩高频提问与准备好它就是加分项的话术答辩环节问得最多的问题通常不是“你的代码是怎么写的”而是“为什么这么设计”和“有没有考虑过某些场景”。我把当时自己和同学被问过的高频问题整理成一张表但这里只做文字版说明避免篇幅过长。第一个高频必问“你这个推荐系统是实时推荐还是离线推荐”最稳的回答是“混合模式”。用户操作过程中的相似方案召回可以实时算但用户相似度矩阵和TF-IDF特征向量是离线预计算的每天更新一次。实时部分优先保证响应速度离线部分保证推荐质量。这个答案有两个好处一方面说明你没有把所有计算都塞给在线服务另一方面也回答了算法复杂度和系统延迟的平衡问题。第二个高频必问“冷启动问题怎么解决的”你一定要能流畅地说出新用户冷启动是先用内容特征匹配顶上的新方案冷启动是先用地理位置和POI标签给用户看的而不是直接推“热门”就完事。这一步在论文里相当加分因为老套的方案只写协同过滤完全没有冷启动处理老师一问就露馅。第三个高频必问“数据量多大为什么用Hadoop而不是MySQL”回答思路是原始日志接近几十万条加上计算吞吐量和未来扩展性已经超过单机MySQL的分析性能边界Hadoop解决的是离线批量分析场景MySQL解决的是在线事务场景两者服务于不同的需求。这里切忌说“因为大家都用Hadoop所以我用”一定要从需求反推。第四个高频必问“推荐效果怎么评估”这个问题最容易暴露短板。我的处理方案是在模拟数据里预留10%的行为记录离线划分训练集和测试集计算推荐列表的精确率、召回率和覆盖率。指标不用追求极高重点是你要能解释清楚为什么低因为出行数据本身稀疏用户往往集中在少数几条通勤路线上长尾推荐拉低了精确率。把这个原因讲出来反而显得你想过这个问题。8. 踩坑实录那些不跑一遍根本发现不了的问题这一节集中讲我在开发中反复挣扎过的问题每一条都是真金白银的教训。第一个坑是MySQL中文乱码。Django建库时如果字符集没有指定为utf8mb4中文写入后极大概率变成一堆问号。解决方法是建库时显式指定DEFAULT CHARSETutf8mb4 COLLATE utf8mb4_unicode_ci同时确保MySQL服务器配置文件里的default-character-set在mysqld段也设成utf8mb4。后来我把数据库连接选项里加上charsetutf8mb4才彻底稳定。这个问题在上传源码时因为没有建库语句别人一跑就跪所以写文档时一定要附带完整的建库命令。第二个坑是Hadoop伪分布式重启问题。我在开发过程中反复修改代码、重启虚拟机经常遇到DataNode和NameNode的Block上报失败。后来统一总结出标准流程Hadoop挂掉后不要反复格式化先看jps确认哪个进程不在再查看对应日志如果决定格式化务必清空DataNode数据目录否则版本不一致的问题会一直存在。第三个坑是Django的定时任务时区问题。我用django-crontab配置凌晨任务时发现任务总是晚8小时才执行。查了源码才发现它读取的是服务器本地时间而不是Django设置里的TIME_ZONE。解决方法是直接对服务器系统时区进行统一修改/etc/timezone并重启cron服务。第四个坑是协同过滤算法的内存问题。当用户数和方案数扩大后用户相似度矩阵直接以嵌套字典的形式放在内存里计算几十万条记录能把开发机内存吃爆。这里有一个很实用的优化策略只计算有共同评分行为的用户对。构建评分矩阵时先把每个方案被哪些用户评过分建一个倒排表再用倒排表反向生成两两用户对。这样实际需要运算的用户对数量会急剧下降因为大多数用户之间根本没有共同评分记录它们的相似度本来就是0大量稀疏计算直接被跳过了。第五个坑是ECharts地图坐标对不上。地图热力图需要的是经纬度点集加权重值而路线推荐返回的是文本化起终点。我当时花了不少时间统一地理编码格式最后用gps映射到主城区网格再把网格编号作为热力图坐标的Key效果才正常。这个问题也提醒我们前后端数据口径必须在文档里提前写清楚两个人各做各的联调时一定对不上。9. 这个系统后续还可以这样扩展毕设做完了不代表项目就到此为止这个课题本身可以对标很多新技术方向。如果你还有精力我最推荐的三个扩展方向第一个是把离线推荐升级为实时推荐用消息队列做用户行为流的缓冲再用流计算框架做分钟级的统计与推荐结果刷新相当于把推荐系统的时效性提高一个档次第二个是引入知识图谱比如把城市POI和地铁站、公交站等实体做关联用户在地图选址时就能语义理解周边环境推荐出来的是“适合雨天的地铁步行最少方案”这类语义化很强的结果第三个是在同样的Django骨架基础上把出行推荐替换成餐饮推荐或景区路线推荐模板代码几乎可以复用适合你拿去参加课程设计或竞赛。从我个人的实际操作体会来说出行推荐系统和一般的管理系统有一个本质差异它逼着你同时理解数据、算法、工程三个层面的问题。单纯“能跑起来”很容易但要做到推荐结果不让人哭笑不得通常要反复调权重、清洗数据、修正特征这恰恰是这份经验最宝贵的部分。我把这套方案的思路和排坑过程写下来就是希望后来者能少走一点弯路把时间省下来真正花在理解系统为什么这样设计、数据为什么这样流动上。项目源码、文档结构和演示数据我在自己的实践里都已经整理好只要按这篇的思路把顺序理顺跑通并不是难事。
阅读完成 · 觉得有帮助?