基于用户行为的租房推荐与可视化平台:Python全链路实战解析
每年临近四月总有朋友拿着手机来问毕业设计到底选什么题。我的看法一直很明确——别做只有登录注册加增删改查的管理系统也别交一个孤零零的模型训练脚本最好挑一个能把数据采集、清洗、算法、展示串成完整闭环的方向。基于用户行为的租房推荐与可视化平台这类题目就是典型的高性价比选择整个链路走的是Python技术栈用Scrapy爬虫抓取真实房源数据用Pandas完成数据分析和特征工程用推荐系统把用户行为转化为个性化结果最后用可视化大屏把房源市场、用户画像、推荐效果一并展示最近两年还流行叠加一点大模型辅助的玩法。我陆续帮几个朋友完整走过这套项目的开发流程今天把选题评估、爬虫工程、行为数据建模、推荐算法落地、可视化设计以及源码包里几乎不会写的踩坑经验从头到尾梳理一遍给正在准备毕设或者想独立做一套数据全流程应用的朋友一份可以直接参考的实战笔记。1. 选题评估与技术栈取舍这个题目为什么性价比高1.1 一个题目同时覆盖四条技术主线毕业设计最怕的是单薄。做一个管理系统技术含量基本集中在增删改查评委一眼就能看到天花板做一个纯算法题目又往往缺少工程落地和数据展示论文很难写厚。租房推荐平台恰好不同它天然就是一条数据流水线。第一数据采集层用Scrapy爬虫涉及页面解析、反爬策略、去重清洗展示的是工程能力第二数据加工层用Pandas做清洗、统计和特征工程展示的是数据分析能力第三推荐层用内容相似度和协同过滤把用户行为变成推荐结果体现算法理解第四展示层用Flask接口加ECharts大屏把结果可视化覆盖全栈。一条链路下来爬虫、分析、算法、前端四个方向都有了落点论文的每个章节也都能对应到真实代码而不是空谈概念。从选题背景来说租房决策本身也特别适合做推荐。房源信息维度多——价格、面积、户型、区域、通勤距离用户决策链路清晰——搜索、浏览、收藏、联系看房每一步行为都有明确意图。评委不需要你解释为什么这个场景需要推荐系统他们自己租房时就能体会到信息过载的痛点。这种无需铺垫就能讲清楚业务价值的题目答辩时天然占优势。1.2 技术栈选择逻辑全Python链路和存储方案我推荐这套技术栈核心逻辑是一门语言贯穿到底每一层都有成熟组件。爬虫层选Scrapy而不是requests加BeautifulSoup主要赢在工程组织上。Scrapy把请求调度、并发下载、中间件、数据Pipeline全部内置配置好之后就能获得一个可暂停、可续爬、自动去重的采集框架。requests方案写个小脚本没问题但爬几万条数据之后线程管理、失败重试、断点续爬这些事会把人拖进泥潭。存储层用MySQL加Redis。房源是结构化数据价格、面积、区域、户型都适合放进关系型数据库后期写统计接口用SQL聚合非常方便用户行为是高频写入的日志型数据先写Redis再定时批量落MySQL避免频繁写库拖慢页面响应。可视化后端用Flask而不是Django因为这个项目接口数量就十来个Django的ORM、Admin等重量级能力用不上Flask更轻、更好讲解。可视化选用ECharts而不是Matplotlib或Plotly理由也很直接ECharts对地理地图、大屏交互、动态数据更新的支持最好视觉表现力强答辩演示时的观感差异非常明显。模块常见替代方案本项目选择选择理由爬虫requests BeautifulSoupScrapy并发调度、中间件、Pipeline 组织清晰存储CSV / Excel文件MySQL Redis支持SQL统计、支持高并发行为写入后端DjangoFlask接口规模小轻量、易讲解可视化Matplotlib / PlotlyECharts地图热力、大屏交互、演示效果好数据在系统里的流转路径大致是Scrapy采集到的房源明细经Pipeline清洗后写入MySQL前端埋点把用户的搜索、点击、收藏、联系行为上报到Flask接口先落Redis再定时汇总到MySQL行为表Pandas脚本离线读取两张表做特征工程后构建用户偏好向量和房源相似度矩阵推荐引擎依据这些数据计算TopN结果通过API提供给页面ECharts大屏再从MySQL聚合市场统计和推荐结果一起展示。理解了这个流向后面每个模块的代码都不会走偏。2. Scrapy爬虫层房源数据采集的工程细节与健壮性设计2.1 页面结构分析与字段建模写爬虫之前第一步永远是分析目标站点的页面结构而不是急着写代码。我一般会先在浏览器里打开列表页和详情页把URL规律、翻页参数、字段所在的DOM节点记录成一张表。以某主流租房平台为例列表页是一张张房源卡片卡片里能看到标题、区域、价格、面积等摘要信息点进详情页才有完整的户型、朝向、楼层、地铁距离、房源标签和发布时间。字段建模这一步决定了后面所有环节的体验一定要在Item里提前定义清楚。我的建议是宁可多留几个字段也不要后期返工。import scrapy class HouseItem(scrapy.Item): house_id scrapy.Field() # 房源唯一ID用于去重和关联 title scrapy.Field() # 标题用于文本特征 district scrapy.Field() # 行政区 bizcircle scrapy.Field() # 商圈/板块 price scrapy.Field() # 租金元/月 area scrapy.Field() # 面积平方米 layout scrapy.Field() # 户型如 1室1厅1卫 orientation scrapy.Field() # 朝向 floor scrapy.Field() # 楼层 metro scrapy.Field() # 地铁距离描述 tags scrapy.Field() # 房源标签列表如 精装、近地铁 publish_time scrapy.Field() # 发布时间 url scrapy.Field() # 详情页URL字段设计有几个容易忽略的点。第一house_id一定要用目标站点自带的房源编号而不是自己生成的ID否则后续增量采集和去重都很难做第二tags最好保留成列表不要直接存成逗号分隔的字符串后面做文本特征还要拆第三metro这种自由文本要保留原始描述不要试图在爬虫层就解析成结构化数字很容易解析出错宁可后面用规则统一处理。2.2 健壮性设计低频率、高完整、可恢复很多第一次写爬虫的同学看到数据就兴奋把下载间隔调到0.1秒结果爬了两百条就被限制后面整个节奏全乱。我的经验是追求低频率、高完整、可恢复不抢速度但保证每一页都抓到中途挂了还能从断点继续。Scrapy里有几个配置组合使用效果很好。下载间隔设置为1到3秒之间的随机值配合随机化开关让请求间隔不规则降低被规则检测的风险User-Agent通过中间件随机轮换不要所有请求顶着同一个UA打开自动限速让Scrapy根据响应速度动态调节延迟比自己拍脑袋设一个固定值更平滑。目标站点随时可能改版所以失败重试和错误日志必须提前设计好。我习惯给每个请求挂错误回调请求失败时把URL写进错误集合等主流程跑完再用一个修复任务去重放失败链接。增量采集是这个项目里很加分的设计。房源数据不是爬一次就完事每天都会有新上架和下架的房源。最简单的做法是维护一张已采集URL表每次启动爬虫前先加载已见集合解析列表页时发现URL已存在就跳过详情页请求同时记录房源的上架、下架状态下架房源在统计时标记为已失效避免推荐结果里出现一堆已经租掉的房子。还有一件事必须单独说爬虫是有边界和规范的采集频率要控制在合理范围不要对目标站点造成压力也不要采集隐私类和平台明确禁止的数据。这个项目只采集公开的房源挂牌信息用于学习演示这个原则一定要守住。2.3 Item Pipeline去重、清洗、入库一条龙Scrapy的Pipeline是数据流入库之前的加工车间我一般会挂三个Pipeline去重Pipeline、清洗Pipeline、入库Pipeline顺序在配置里指定。去重用URL哈希最直接——同一个房源往往会被多个中介重复发布标题可能不一样但详情页URL通常会指向同一个房源编号。import hashlib class DuplicatesPipeline: def __init__(self): self.seen set() def process_item(self, item, spider): uid hashlib.md5(item[url].encode(utf-8)).hexdigest() if uid in self.seen: raise scrapy.exceptions.DropItem(f重复房源: {item[url]}) self.seen.add(uid) return item清洗Pipeline做的是把原始字符串变成数值。页面上价格经常是4900元/月面积是45㎡朝向可能是南或南 北这些都要在入库前统一成float或标准枚举同时过滤明显异常的数据价格小于等于0、面积小于等于0的直接丢弃。class CleanPipeline: def process_item(self, item, spider): price item.get(price, ) area item.get(area, ) if 元/月 in price: item[price] float(price.replace(元/月, ).strip()) if ㎡ in area: item[area] float(area.replace(㎡, ).strip()) if item.get(price, 0) 0 or item.get(area, 0) 0: raise scrapy.exceptions.DropItem(价格或面积异常) return item入库的时候我建议用批量插入而不是一条条insert效率差距非常大。SQL语句里的存在则更新逻辑天然支持增量采集同一个house_id再次出现在爬虫结果里时更新价格和发布时间即可。这个设计看起来不起眼但能让数据集保持新鲜推荐系统算出来的结果才不至于偏离市场。3. 用户行为数据的建模推荐系统真正的弹药3.1 行为类型与权重设计房源数据只是推荐系统的库存用户行为数据才是真正的弹药。一个用户点进了一套房、收藏了一套房、最终还是联系了中介看房这三种行为透露的意图强度完全不同。我在项目里给行为定义了权重这张权重表是整个推荐系统的基础后面算用户偏好向量全靠它。行为类型权重意图说明搜索关键词1弱意图只是开始找点击浏览房源2初步感兴趣详情页停留超过30秒4深度关注收藏房源5明确意向联系看房 / 咨询10强转化意图行为数据从哪来如果系统上线后有真实用户访问就通过前端埋点上报但多数毕设场景没有真实用户这时候用脚本生成模拟行为数据是可以接受的前提是分布要合理。我在模拟脚本里加入了几条约束大多数用户的行为集中在自己预算范围内的区域晚间和周末的行为量明显多于工作时间同一个用户不会跨五个以上商圈反复浏览。这样生成的用户画像才符合现实逻辑推荐效果也能被直观感受到。行为上报接口的格式要提前定好前端只需要向后端接口发送一个JSON包含用户ID、房源ID、行为类型、时间戳。服务端先把事件追加到Redis列表再写一个定时任务每五分钟批量刷入MySQL。用Redis缓冲而不是直接写库是为了避免用户高频浏览时把数据库连接打满。3.2 特征工程把房源和用户都变成可计算的数字干净的原始数据还不足以直接喂给推荐算法中间必须经过特征工程。核心思路是把房源和用户都变成向量后面计算相似度才有数学基础。房源特征我分为四类。第一类是数值特征价格、面积、每平米租金price/area这类特征要用归一化缩放到0到1之间避免价格数值太大压过面积的影响第二类是类别特征行政区和商圈做独热编码户型解析成室、厅、卫三个数值第三类是文本特征把标题、标签、地铁描述拼起来做TF-IDF向量第四类是衍生特征比如根据地铁描述解析出直线距离数值或者给商圈打一个配套成熟度评分。用户特征本质上是从行为表里聚合出来的偏好向量。以区域偏好为例先把行为表按用户和区域分组用行为权重加权求和再做一个归一化处理这组数值就代表了用户对各区域的偏好强度。价格偏好也是一样把价格切成若干个区间统计用户在不同价格区间里的加权行为量。有了用户偏好向量和房源特征向量推荐引擎计算相似度就是一次向量运算的事。behavior_df[weight] behavior_df[action].map(ACTION_WEIGHT) user_district_pref ( behavior_df.groupby([user_id, district])[weight] .sum() .reset_index() )顺便提醒一句特征工程做完一定要检查每一列的量纲和取值分布。我见过不止一个同学把未归一化的价格和独热编码的区域拼在一起算余弦相似度结果价格维度的差异完全压过其他特征推荐的房子清一色全是低价房源这就是特征没对齐造成的。3.3 数据质量校验脏数据会直接毁掉推荐结果推荐系统有个残酷的特性模型可以简单但数据不能脏。数据一旦脏了再高级的算法也救不回来。最典型的问题是异常值。有些挂牌价标成1元/月或者面议解析出来就是1或者0这种数据一旦进入推荐候选集会被算成极端相似样本。处理办法不是简单删除而是用箱线图法标记。以每平米租金为例先算第一四分位数Q1和第三四分位数Q3把低于Q1-1.5倍四分位距或高于Q31.5倍四分位距的样本标记出来人工复核。之所以不直接按标准差删除是因为租金分布通常右偏用四分位距对偏态分布更稳健。Q1 houses[unit_price].quantile(0.25) Q3 houses[unit_price].quantile(0.75) IQR Q3 - Q1 houses houses[ (houses[unit_price] Q1 - 1.5 * IQR) (houses[unit_price] Q3 1.5 * IQR) ]另一个常见坑是重复房源。同一套房子被多个中介发布URL可能不同但地址、面积、朝向完全一致价格上下浮动。这种重复数据会让推荐列表里连续出现好几条看起来差不多的房源体验很差。我的做法是在Pandas里按商圈面积户型分组组内价格方差小于5%的合并成一条保留价格最低的那条。最后是中文乱码问题。MySQL表结构务必统一用utf8mb4字符集Python脚本连接数据库时也要显式指定字符集不然数据入库时偶尔会冒出一堆问号这类问题排查起来非常费时间最好在项目第一天就统一编码规范。4. 推荐算法落地冷启动、混合推荐与大模型的实际分工4.1 冷启动新用户进来该推什么推荐系统绕不开冷启动问题。刚注册的用户没有任何行为历史内容相似度和协同过滤全都失效这时候不能什么都不推得有一条兜底策略。我的兜底方案分两步。第一步是规则过滤用户注册时可以选择区域和价格预算相当于用户自己给出了明确的约束条件先把候选房源过滤到用户愿意看的范围第二步是基于热度排序热门分综合近7天的点击量、收藏量、联系量同时加入时间衰减越新的行为权重越高避免一个月的旧热门永远霸榜。def hot_score(click, fav, contact, days_ago): base 0.3 * click 0.5 * fav 0.2 * contact decay math.exp(-days_ago / 7.0) return base * decay新建的房源也会遇到冷启动没有任何行为数据。我的做法是给新上架房源一个基础曝光分加成让它在热门榜里有一定露出机会等积累到一定行为量后再进入正常的推荐计算。这套兜底策略看着简单但它在答辩时特别有用——评委一定会问没有行为数据的用户怎么办你能直接给出明确的规则设计而不是含糊其辞。4.2 内容相似度与协同过滤混合推荐的组合方式进入正题之后核心推荐逻辑我用了两路召回加一路兜底最后加权融合排序。第一路是内容相似度召回。先把房源特征向量化TF-IDF文本向量、数值特征、类别特征拼接后做余弦相似度计算对用户最近浏览或收藏的房源找出最相似的一批候选。这条路线的优势是可解释性强推荐理由可以直接写这套房和您看过的某小区房源类似缺点是容易同质化用户看了一居室就永远推一居室。第二路是物品协同过滤。这里我特意选了物品协同而不是用户协同。原因有两个一是房源数量远小于用户数量物品相似矩阵规模小、计算快、更新更轻量二是看了这套房的人还看了哪些这个逻辑对用户来说更直观推荐理由更好写。物品相似度不是靠房源属性算的而是靠用户行为算的——两套房被同一批用户点击、收藏说明它们在用户眼里是替代品。def recommend_hybrid(user_id, top_n10): seen get_user_seen(user_id) content_scores content_recall(user_id, seen) # 内容相似度分数 cf_scores item_cf_recall(user_id, seen) # 物品协同过滤分数 pop_scores hot_candidates(seen) # 热度兜底 final {} for hid in set(content_scores) | set(cf_scores) | set(pop_scores): score (0.4 * content_scores.get(hid, 0) 0.4 * cf_scores.get(hid, 0) 0.2 * pop_scores.get(hid, 0)) final[hid] score return sorted(final.items(), keylambda x: -x[1])[:top_n]这里有一个非常关键的操作细节三路分数在加权之前必须先各自做归一化统一到0到1的量纲。如果不归一化某个分数天然量级大权重系数设得再合理也没用。我通常对每一路分数做最小最大值归一化再套上面的加权公式。混合推荐虽然简单但在这种规模的数据集上非常实用而且答辩时你能清楚地讲出每路算法的职责和取舍。4.3 大模型辅助用在解释生成和语义理解上近几年大模型成了毕设加分项但这个题目的正确姿势是辅助增强不是全盘替代。我的实际分工有三处。第一处是用大模型做语义向量替代一部分TF-IDF的文本匹配。TF-IDF只能按词共现算相似近地铁和步行3分钟到地铁站在词面上差异很大但语义上是一回事。用文本向量模型把房源描述和用户偏好摘要都转成向量再做余弦相似度能明显提升内容召回的语义理解能力。第二处是生成推荐解释。推荐系统不只要给出结果还要给理由。给定用户偏好画像和候选房源的属性让大模型生成一段推荐语比如根据您近期在望京商圈对一居室的关注推荐这套45平、月租4900元、距离地铁站500米的房源。带具体数字的解释比空泛的为您推荐可信得多展示效果也更好。第三处是辅助信息抽取。房源的长描述文本里经常藏着2020年装修民水民电这类关键信息用大模型做一次标签抽取能补全房源特征表的稀疏列。必须提醒的是毕设演示现场网络不一定可靠大模型接口响应慢或者超时会直接毁掉整场演示。我的建议是提前把推荐解释离线预计算为用户和房源的组合生成好推荐语缓存到Redis或数据库前端展示时直接读缓存。这样既保留了大模型参与的技术亮点又不会让演示被外部网络状况绑架。5. 可视化大屏设计图表布局、API联动与演示叙事5.1 从业务问题出发选图表而不是从图表出发可视化这部分最容易犯的毛病是堆图表。见过不少项目往页面里塞了七八张酷炫大图被问到想表达什么答不上来。我的原则是每一张图都必须回答一个具体的业务问题。这个项目里我保留了几类图表每一类都有明确的问题锚点。区域均价热力图回答哪个区租房最贵商圈均价TOP榜回答具体板块的性价比差异户型占比饼图回答市场供给结构面积与价格散点图回答面积和租金的关系用户画像雷达图回答当前用户到底偏好什么推荐结果卡片区回答这个用户现在最适合哪些房。技术选型上ECharts基本是标准答案地图热力图、雷达图、散点图都有成熟示例。如果想让大屏更完整可以加一个顶部KPI卡片区展示房源总数、采集天数、用户数、行为总数这几个核心数字。大屏布局我习惯用上下左右的结构顶部是KPI横条左侧放区域热力图右侧放商圈TOP榜和户型占比中间最显眼的位置留给推荐结果和用户画像雷达。用户一眼扫过去既能看到市场全貌也能看到推荐效果。5.2 Flask API设计与数据缓存策略前端图表不直接连数据库所有数据都走Flask提供的JSON接口。接口设计要规范统一返回包含状态码、消息和数据的结构前端只需处理一种格式省去大量兼容代码。接口路径方法功能说明/api/stat/overviewGET顶部KPI汇总/api/stat/district_priceGET区域均价供热力图/api/stat/layout_distGET户型分布供饼图/api/stat/price_scatterGET面积-价格散点/api/user/profile/user_idGET用户画像雷达数据/api/recommend/user_idGET个性化推荐列表/api/behaviorPOST行为上报统计类接口的性能优化很关键。很多同学直接对全表做分组聚合数据量小还好房源到了几十万条就卡得非常明显。我的做法是统计结果定时离线算好存进一张聚合表或者Redis缓存API接口只负责读缓存过期时间设置为五分钟。推荐接口也一样同一个用户的推荐结果在行为没有变化之前是相同的没必要每次请求都重算一遍按用户ID缓存半小时上报新行为时再主动失效对应缓存一举两得。app.route(/api/recommend/int:user_id) def recommend(user_id): cache_key frec:{user_id} data redis.get(cache_key) if data: return jsonify({code: 0, msg: ok, data: json.loads(data)}) recs recommender.recommend(user_id, top_n10) redis.setex(cache_key, 1800, json.dumps(recs)) return jsonify({code: 0, msg: ok, data: recs})前端ECharts的配置相对模板化重点是把API返回的字段名和图表的data属性对齐。比如区域热力图需要地图数据里的name和value前端只需要把后端返回的区域名和均价组装成对应的对象数组就行。写一个统一的网络请求工具函数让所有图表走同一条数据获取逻辑能减少大量重复代码。5.3 演示动线让评委看到推荐系统真的在工作可视化大屏搭好了不会演示等于白做。我推荐的演示动线是一条完整的业务闭环最好能当场讲成一个故事。第一步先用采集了多少房源、积累了多少用户行为的KPI页面开场讲清楚数据规模。第二步登录一个有充分行为记录的账号打开用户画像雷达图讲解系统是怎么从行为数据里推断出用户偏好的。第三步展示这个用户的推荐列表让评委看到推荐结果和画像雷达是吻合的。第四步现场再点击浏览几套房最好选和画像偏好有差异的房源然后刷新推荐列表展示推荐结果发生了变化。第五步切回市场大屏用区域热力图和商圈榜给房源市场做总结。这条动线的精髓在于变化两个字。推荐系统不是静态榜单它必须让评委亲眼看到行为输入导致推荐输出改变这个闭环一旦展示出来项目档次立刻不一样。光放一张静态推荐列表评委很难判断你到底做了推荐还是只做了个带条件的排序接口。6. 源码包之外的实战经验从开发到答辩的完整避坑清单6.1 开发链路里最容易翻车的几个环节源码包给人的错觉是代码齐全就能跑真正动手开发才会发现坑全在代码外面。首先是字段口径不统一。爬虫里定义的行政区是朝阳区可视化统计里用的也是朝阳区听起来一致但只要有一个地方用了简称或者带后缀的写法统计结果马上对不上。我建议在项目开始就建立一张数据字典表把每个字段的取值枚举和维护口径写清楚所有模块都参考这张表开发。其次是MySQL索引问题。房源表几万条的时候查询还没感觉到了几十万条不带索引的过滤查询能把接口拖到几秒。对区域、价格、发布时间这些高频过滤字段建联合索引行为表对用户ID和房源ID建索引这几条别省。第三是模拟行为数据的合理性。行为分布不符合现实推荐结果就会显得诡异。比如模拟脚本用均匀分布生成区域偏好用户画像雷达图就会变成一片平平无奇的形状答辩时完全没法讲。生成模拟数据时一定要让用户有明确的偏好区域和预算区间。第四是环境一致性。我吃过一次亏开发环境装的依赖版本和说明文档里写的不一样换了一台机器全新安装后跑不起来。项目交付前把依赖清单重新生成一遍然后找一台没有装过项目的干净环境完整跑一遍流程从爬虫到可视化全程验证这一步能挽回大量答辩现场的尴尬。6.2 答辩提问的高频问题与应对思路根据我陪练答辩的经验评委对这类项目的高频问题其实非常集中提前准备好就能从容应对。为什么用协同过滤而不用深度学习这个问题的正确回答不是深度学习效果不好而是从数据量、可解释性、项目周期三个角度解释当前数据集规模有限深度学习收益不明显协同过滤配合内容相似度可以输出直观的解释理由毕设时间有限混合推荐已经能覆盖需求深度学习可以留作扩展方向。推荐效果怎么评估如果回答说感觉还不错就完了。正确做法是提前把行为数据按时间切分成训练集和测试集用前7天的行为做训练预测后3天的点击和收藏计算精确率和召回率。即使结果一般只要你有评测流程和数字就说明具备评估意识。大模型在这里起什么作用对应前面讲过的三个用途说清楚语义向量召回、推荐语生成、标签抽取。同时强调大模型是辅助增强核心推荐链路仍然由传统算法承担这样既展示了技术视野又不会暴露外部接口依赖的问题。数据量变大怎么办准备一个扩展思路的回答爬虫层可以改造为分布式采集数据加工换批处理框架推荐计算做成离线任务加在线服务两层。不需要真的实现但要有清晰的技术演进路径。6.3 如果还有时间这三个扩展方向最值得做如果基础版本已经跑通富余时间还够我建议优先考虑下面三个扩展方向投入产出比都不错。第一个是离线评测模块。给推荐结果加一个后台评测页面展示训练集、测试集划分和召回率、精确率等指标曲线。这一步把推荐系统从演示品升级成有评测依据的系统论文里也能多一章实验分析。第二个是排序模型升级。在混合召回得到的候选集之上用梯度提升模型做精排序特征包括行为统计、相似度分数、价格匹配度、时间衰减因子等。候选集规模控制在几十条以内训练和推理都很快效果提升却很明显。第三个是工程化交付。写一个容器编排文件把MySQL、Redis、爬虫任务、Flask后端、前端页面一键编排起来。最终交付时评委或下一届同学不用再手动安装一堆依赖一条命令就能复现整个系统这对项目的完成度观感是实打实的加分项。做了这么多年数据项目我最大的体会是这类毕设真正拉开差距的地方不在推荐算法有多复杂而在数据链路是否完整、诚实、可演示。如果你把爬虫字段、行为日志、特征口径全部统一清楚哪怕推荐算法用的只是最简单的加权混合答辩效果也远好过一个听起来很高深但根本跑不通的方案。先保证链路通再去折腾模型复杂度顺序反了项目大概率烂尾。