ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Flask和Hadoop的B站热门视频分析与推荐系统设计

基于Flask和Hadoop的B站热门视频分析与推荐系统设计 简介一份基于大数据对B站热门视频进行数据分析与研究的毕业论文文档面向计算机、大数据类专业学生可作为毕业设计选题、系统设计与论文写作的参考。文档采用B/S架构以Python和Flask框架搭建Web应用结合Hadoop实现大规模数据存储与处理重点阐述用户管理、热门视频榜单动态更新、视频分类及标签优化等内容还覆盖了播放量、点赞数、评论数等多维热度评估指标。压缩包内仅含1个doc文件大小约3.66MB从摘要、目录到绪论和相关技术等章节均有呈现论文结构清晰完整便于快速定位关键设计。目前已有68人浏览学习适合需要完成B站数据分析类课题或构建类似视频数据分析系统的读者。通过阅读可直接借鉴其中的技术选型、模块划分、系统设计方案与论文撰写框架为毕业设计提供实用参考同时了解大数据分析在视频平台中的实际应用思路。1. 为什么 B 站热门视频分析值得单独做一个系统视频平台的数据分析不是拉一张报表那么简单。B 站的热门视频同时受播放量、点赞、投币、收藏、评论、弹幕等多个指标影响而且这些指标的量纲差异极大——一部百万播放的视频可能只有几千条评论但弹幕量却能到几万。直接用单一指标排序榜单被头部 UP 主垄断不加权地综合多个指标又会被异常值带偏。这个毕业设计项目做的就是一件事把 B 站热门视频的多维数据用 Hadoop 做分布式存储再用 Flask 搭一个 B/S 架构的 Web 系统让管理员能动态评估热度、管理视频标签让普通用户能看热门榜单、获取个性化推荐。对于正在做大数据方向课程设计或毕业设计的开发者这套系统把数据采集 → 分布式存储 → 热度建模 → Web 可视化 → 推荐算法整条链路走通了是一个可以完整复现的工程范例而不是停留在理论层面的演示项目。2. Flask Hadoop 的选型逻辑与系统架构搭建2.1 B/S 架构下 Flask 的定位与 Werkzeug 请求处理链这个系统采用 B/S 开发模式也就是浏览器直接访问服务器端的 Web 应用用户不需要安装任何客户端。Flask 在这里承担的是整个 Web 层的职责接收 HTTP 请求、处理路由分发、调用业务逻辑、渲染 HTML 模板。Flask 的核心构成是 Werkzeug 和 Jinja2前者处理 WSGI 层的请求解析与响应封装后者负责把 Python 变量渲染进模板。下面这段代码展示了系统中最基本的请求处理链路from flask import Flask, request, jsonify, render_template from flask_sqlalchemy import SQLAlchemy app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] mysqlpymysql://root:passwordlocalhost:3306/bilibili_hot app.config[SQLALCHEMY_TRACK_MODIFICATIONS] False db SQLAlchemy(app) app.route(/api/video/hot, methods[GET]) def get_hot_videos(): # 管理员端按热度分页查询热门视频列表 page request.args.get(page, 1, typeint) limit request.args.get(limit, 20, typeint) videos HotVideo.query.order_by(HotVideo.heat_score.desc()).paginate( pagepage, per_pagelimit, error_outFalse ) data [{ id: v.id, title: v.title, play_count: v.play_count, like_count: v.like_count, heat_score: v.heat_score } for v in videos.items] return jsonify({code: 0, data: data, total: videos.total}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)这段代码里paginate是 Flask-SQLAlchemy 提供的分页方法page和limit从 URL 查询参数读取并做了类型转换error_outFalse保证当页码超出范围时返回空列表而不是直接抛 404。实际开发中我一般会额外做一个统一异常处理器把数据库连接超时、查询异常等错误统一包装成{code: 500, msg: ...}的 JSON 结构否则前端拿到非标准错误格式很难处理。2.2 为什么这个场景需要 Hadoop 而不是单机 MySQL如果只是存储几万条视频数据MySQL 单机完全够用。但 B 站视频数据的真正压力在于行为日志——每个视频关联的播放记录、弹幕内容、评论时间戳这些日志数据每天新增量巨大。Hadoop 在这个系统中的作用分两块HDFS 负责存储原始日志文件MapReduce 负责对日志做离线批量计算比如统计某段时间内各视频的播放增量、用户行为矩阵的构建等。HDFS 存储的核心机制是数据块复制。默认副本数为 3一个大文件被切分成 128MB 的 block 分散存储到不同节点任何单点故障都不会丢数据。下面是 Hadoop 伪分布式模式下的核心配置也就是单台机器模拟分布式环境时的最小配置!-- core-site.xml: 配置 NameNode 地址 -- configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property /configuration !-- hdfs-site.xml: 配置副本数和 NameNode 数据目录 -- configuration property namedfs.replication/name value1/value /property property namedfs.namenode.name.dir/name value/usr/local/hadoop/data/namenode/value /property property namedfs.datanode.data.dir/name value/usr/local/hadoop/data/datanode/value /property /configuration伪分布式模式下副本数必须设为 1因为只有一个 DataNode 节点如果保持默认的 3数据写入时会一直等待副本确认最终报错。fs.defaultFS指定了文件系统的访问入口所有 HDFS 操作都走这个地址。这个配置对初学者是个常见坑点我见过不少人在伪分布式环境忘记修改dfs.replication导致写文件超时。2.3 业务数据落 MySQL日志数据落 HDFS 的双存储策略系统的数据流向是原始 API 采集的视频数据先落入 HDFS作为永久存档清洗、聚合后的结构化结果写入 MySQL供 Flask 的 Web 层实时查询。这种双存储策略避免了 HDFS 的延迟问题——HDFS 适合顺序读写的大文件不适合单条记录的随机查询。MySQL 里存放的是每个视频的最新快照包括播放量、点赞数、评论数这些经过聚合计算的指标以及用户表、权限表、公告表这类业务数据。在设计这个双存储架构时最关键的决策点是确定哪些数据需要进 Hadoop。我的判断标准很简单需要全量保留、后续可能要回溯分析的历史数据 → HDFS当前业务逻辑要频繁读写的热数据 → MySQL中间计算结果比如用户相似度矩阵 → 可以用 HDFS 临时目录用完清理这样划分之后Flask 应用的数据库压力就控制在了合理的阈值内而 Hadoop 的 MapReduce 作业则负责定期的离线重算任务。3. 数据库设计从 E-R 模型到热度数据表的落地3.1 核心数据表结构设计系统涉及的核心实体包括用户、管理员、热门视频、公告信息、收藏记录。视频表是数据模型里的中心表其他表都直接或间接与它关联。下面是热门视频表的建表 SQLCREATE TABLE hot_video ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, title varchar(200) NOT NULL COMMENT 视频标题, up_name varchar(100) DEFAULT NULL COMMENT UP主, cover_url varchar(500) DEFAULT NULL COMMENT 封面图地址, video_type varchar(50) DEFAULT NULL COMMENT 视频分类, play_count bigint(20) DEFAULT 0 COMMENT 播放量, like_count bigint(20) DEFAULT 0 COMMENT 点赞数, comment_count bigint(20) DEFAULT 0 COMMENT 评论数, coin_count bigint(20) DEFAULT 0 COMMENT 投币数, favorite_count bigint(20) DEFAULT 0 COMMENT 收藏数, danmaku_count bigint(20) DEFAULT 0 COMMENT 弹幕量, duration int(11) DEFAULT NULL COMMENT 视频时长秒, upload_time datetime DEFAULT NULL COMMENT 上传时间, heat_score decimal(10,4) DEFAULT 0.0000 COMMENT 综合热度分, tag_ids varchar(255) DEFAULT NULL COMMENT 关联标签ID逗号分隔, create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_heat_score (heat_score), KEY idx_video_type (video_type), KEY idx_upload_time (upload_time) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT热门视频表;这张表的设计有两个细节值得注意。第一heat_score字段加了decimal(10,4)而不是用float因为浮点数在比较和排序时存在精度误差而热度分是要用来排名的精度必须可控。第二索引不是越多越好这里只对heat_score、video_type、upload_time三个字段建了索引分别对应热度榜单排序、分类筛选、时间范围查询这三个高频操作路径。tag_ids字段用逗号分隔存储了关联的标签 ID这种反范式设计避免了单独建一张多对多关联表适用于标签数量有限且不常变的场景。3.2 用户与权限相关的表设计管理员需要审核用户注册信息、分配权限所以用户表需要设计一个status字段标识审核状态一个role字段区分角色。下面是简化后的用户表和收藏表的建表语句CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 用户名, password varchar(200) NOT NULL COMMENT 密码sha256加密存储, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(500) DEFAULT NULL COMMENT 头像, email varchar(100) DEFAULT NULL COMMENT 邮箱, phone varchar(20) DEFAULT NULL COMMENT 手机号, gender tinyint(4) DEFAULT 0 COMMENT 性别 0未知 1男 2女, status tinyint(4) DEFAULT 0 COMMENT 审核状态 0待审核 1通过 2拒绝, role varchar(20) DEFAULT user COMMENT 角色 user/admin, create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表; CREATE TABLE user_favorite ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 用户ID, video_id bigint(20) NOT NULL COMMENT 视频ID, create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_video (user_id, video_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户收藏表;用户表里密码字段必须加密存储项目里我用的是 Python 标准库hashlib做 SHA-256 加盐哈希实际生产环境建议换成bcrypt或werkzeug.security.generate_password_hash。uk_user_video联合唯一索引防止了同一用户重复收藏同一视频。注册审核流程的实现思路是用户提交注册时status默认为 0管理员在后端看到待审核列表后通过接口把status改为 1同时可以选择给用户分配role权限。3.3 热度分计算的批处理实现考虑到 MySQL 在数据量大时的统计效率问题热度分的批量重算放在 Hadoop 的 MapReduce 作业里执行比较合理。MapReduce 的 Map 阶段遍历输入文件中的每一条视频记录将视频 ID 作为 key、各维度的指标作为一个 value 对象输出Reduce 阶段对同一视频的数据进行汇总加权算出一个热度分写入输出结果。简化的映射逻辑如下# mapper.py - 从日志中提取视频指标 import sys WEIGHTS {play: 0.35, like: 0.25, comment: 0.15, coin: 0.15, favorite: 0.1} for line in sys.stdin: fields line.strip().split(\t) if len(fields) 6: continue video_id, play, like, comment, coin, favorite fields[:6] # 输出格式: video_id \t play \t like \t comment \t coin \t favorite print(f{video_id}\t{play}\t{like}\t{comment}\t{coin}\t{favorite})# reducer.py - 合并统计并计算热度分 import sys def calc_heat(play, like, comment, coin, favorite): # 各维度归一化后加权求和权重可调 score (float(play) * 0.35 float(like) * 0.25 float(comment) * 0.15 float(coin) * 0.15 float(favorite) * 0.1) return round(score, 4) current_video None scores [0, 0, 0, 0, 0] for line in sys.stdin: parts line.strip().split(\t) if len(parts) 6: continue video_id parts[0] vals list(map(float, parts[1:6])) if current_video and video_id ! current_video: heat calc_heat(*scores) print(f{current_video}\t{heat}) scores [0, 0, 0, 0, 0] current_video video_id for i in range(5): scores[i] vals[i] if current_video: heat calc_heat(*scores) print(f{current_video}\t{heat})Reduce 阶段的核心逻辑是相同 key 的数据会按序到达所以代码用current_video变量做边界判断碰到新的 video_id 就把上一组的累计值结算输出。加权公式里的五项系数总和为 1播放量权重最高但不超过 0.4这样设计是为了防止刷播放量导致榜单失真。实际部署时MapReduce 作业的输出结果会被定时任务拉取并写回 MySQL 的heat_score字段Flask 的查询接口只需要按这个字段倒序排序即可。4. 热门视频管理模块的实现与热度评估模型4.1 多维度热度评估算法设计热度评估是整个系统的核心直接决定了榜单的公平性和实时性。系统里热度分的计算不能只依赖单一指标需要综合播放量、点赞数、评论数、投币数、收藏数、弹幕量六个维度同时加入时间衰减因子避免老视频靠累计播放量霸榜。热度分推荐公式如下import math from datetime import datetime, timedelta def calculate_heat_score(video): # 各维度指标的基准权重对应B站用户不同行为的价值权重 weights { play: 0.3, # 播放基数最大权重略低 like: 0.2, # 点赞比播放更体现质量 comment: 0.15, # 评论用户深度参与 coin: 0.2, # 投币最硬核的支持行为 favorite: 0.1, # 收藏后续回看意愿 danmaku: 0.05 # 弹幕社区氛围指标 } raw_score ( video[play_count] * weights[play] video[like_count] * weights[like] video[comment_count] * weights[comment] video[coin_count] * weights[coin] video[favorite_count] * weights[favorite] video[danmaku_count] * weights[danmaku] ) # 时间衰减发布越久衰减越明显半衰期设为7天 days_since_publish (datetime.now() - video[upload_time]).days decay_factor math.pow(0.5, days_since_publish / 7) return round(raw_score * decay_factor, 4)时间衰减因子使用了指数衰减模型半衰期 7 天意味着视频发布 7 天后热度分降为原来的一半14 天后降为四分之一。这个参数需要根据平台的更新节奏调整——B 站热门视频的生命周期通常在一周左右所以 7 天的半衰期是合理值。权重分配的原则是用户主动行为的价值高于被动行为。coin投币权重设为 0.2因为投币需要消耗用户自己积累的硬币比点一个赞的成本高得多对应的行为价值也应该更高。4.2 榜单动态更新与分类管理实现动态更新热门榜单在系统里表现为两种触发方式。第一种是定时任务每天凌晨用 Hadoop 离线计算的结果刷新一次热度分和榜单第二种是实时接口管理员手动触发单条视频的热度重新计算。下面这个 Flask 接口展示了管理员如何手动更新某条视频的评分app.route(/admin/video/refresh_score/int:video_id, methods[POST]) def refresh_video_score(video_id): video HotVideo.query.get(video_id) if not video: return jsonify({code: 404, msg: 视频不存在}) # 从HDFS读取该视频最新的行为日志并聚合指标 video_data { play_count: get_from_hdfs(video_id, play), like_count: get_from_hdfs(video_id, like), comment_count: get_from_hdfs(video_id, comment), coin_count: get_from_hdfs(video_id, coin), favorite_count: get_from_hdfs(video_id, favorite), danmaku_count: get_from_hdfs(video_id, danmaku), upload_time: video.upload_time } video.heat_score calculate_heat_score(video_data) db.session.commit() return jsonify({code: 0, msg: 热度分已更新, heat_score: video.heat_score})分类管理方面video_type字段与预先定义的分类表关联管理员可以新增分类、调整视频所属分类。标签优化功能基于视频的tag_ids字段系统会统计每个标签关联视频的平均播放量、平均点赞率输出一个标签效果报表帮管理员识别哪些标签能带来更高的流量转化。这些功能都是围绕HotVideo这张表的增删改查展开的核心难度不在于 SQL 本身而在于什么时间触发计算、计算频率多高、怎么保证计算结果的一致性这些工程决策。4.3 管理员与用户双端功能对照系统的功能整体分为管理员和普通用户两条线。管理员端覆盖用户管理注册审核、权限分配、禁用账户、视频管理榜单更新、分类管理、标签优化、公告发布和系统设置用户端覆盖热门视频浏览、视频分类筛选、站内公告查看、个人收藏管理和基于协同过滤的个性化推荐。权限控制方面Flask 生态常用Flask-Login做登录态管理用装饰器做角色校验。下面是一个简单的管理员权限校验装饰器from functools import wraps from flask import session, jsonify def admin_required(f): wraps(f) def decorated_function(*args, **kwargs): # 检查登录态中的角色字段 if session.get(role) ! admin: return jsonify({code: 403, msg: 无权限访问}), 403 return f(*args, **kwargs) return decorated_function app.route(/admin/video/manage, methods[POST]) admin_required def admin_manage_video(): # 只有管理员能执行的视频管理操作 pass这个装饰器在session中读取角色字段并判断是否等于admin不满足时直接返回 403 JSON。functools.wraps保留了原始函数的元信息避免影响路由注册。对于需要细粒度权限控制的场景还可以在sys_user表中增加权限点字段或者单独的权限表。5. 基于协同过滤的视频推荐模块实现5.1 相似度计算的两种思路与选择依据系统采用协同过滤算法做个性化推荐核心思路是和你行为相似的用户喜欢什么你也可能喜欢什么。两种主流实现方式中基于用户的与基于物品的思路和适用场景不同。基于用户的协同过滤UserCF先计算用户之间的相似度再把相似用户喜欢而目标用户没看过的视频推荐出去。它是这样工作的def user_based_cf(user_id, user_item_matrix, k10, top_n5): # user_item_matrix: dict {user_id: {video_id: interaction_score}} target_user_vector user_item_matrix.get(user_id, {}) similarity_scores [] for other_user, other_vector in user_item_matrix.items(): if other_user user_id: continue # 余弦相似度计算两个用户行为向量的夹角余弦 common_items set(target_user_vector.keys()) set(other_vector.keys()) if not common_items: similarity 0.0 else: dot_product sum(target_user_vector[item] * other_vector[item] for item in common_items) norm_a math.sqrt(sum(v ** 2 for v in target_user_vector.values())) norm_b math.sqrt(sum(v ** 2 for v in other_vector.values())) similarity dot_product / (norm_a * norm_b) if norm_a and norm_b else 0.0 similarity_scores.append((other_user, similarity)) # 取相似度最高的k个用户 similarity_scores.sort(keylambda x: x[1], reverseTrue) top_users similarity_scores[:k] # 聚合相似用户的视频过滤掉目标用户已经看过的 recommendation_scores {} for other_user, sim in top_users: for video_id, score in user_item_matrix[other_user].items(): if video_id in target_user_vector: continue recommendation_scores[video_id] recommendation_scores.get(video_id, 0) sim * score # 按加权得分排序取top_n ranked sorted(recommendation_scores.items(), keylambda x: x[1], reverseTrue) return [video_id for video_id, _ in ranked[:top_n]]这套实现的原理是先通过余弦相似度找到与目标用户行为最接近的 K 个用户再用这些用户的评分数据生成推荐。余弦相似度的取值范围是 -1 到 1数据稀疏时结果会偏低需要在实践中对矩阵做标准化处理或引入基于物品的算法做互补。5.2 数据稀疏问题与解决策略实际跑推荐系统时遇到的最大问题是数据稀疏。B 站视频数量巨大但单个用户看过的视频往往不到千分之一用户-视频交互矩阵绝大多数单元格是空的直接算相似度效果很差。针对这个项目的规模和定位常见的优化手段有两个方向方向一行为数据降维。不直接以视频 ID 为维度而是把用户对视频的行为聚合到「视频分类」维度。用户 1 在「游戏」分类下累计播放了 50 次、点赞 20 次就把这个向量记做{游戏: 70}。这样矩阵的维度从几十万视频降到几十个分类稀疏度大幅下降相似度计算也更快。缺点是推荐的粒度变成分类级无法精确到单条视频。方向二基于物品的协同过滤做补充。物品间的相似度比用户间的相似度更稳定因为视频的内容特征不会频繁变化。基于物品的协同过滤实现思路是对于目标用户看过的每个视频找出与之相似度最高的视频集合按相似度加权汇总后推荐。ItemCF 的一个额外好处是可以用video_type、tag_ids预先计算相似性候选集减少在线计算量。5.3 Flask 中整合推荐接口的完整流程推荐接口需要完成三个步骤读取用户行为日志、计算推荐结果、写入推荐列表。在实际实现中我用 Flask 的before_request钩子做了用户行为埋点每次用户观看视频时自动写入一条行为记录app.before_request def log_user_behavior(): # 从请求中提取用户ID和视频ID通常放在查询参数或请求体中 user_id session.get(user_id) video_id request.args.get(video_id) or request.form.get(video_id) if user_id and video_id: # 写入HDFS行为日志user_id \t video_id \t action \t timestamp log_entry f{user_id}\t{video_id}\t1\t{int(time.time())} append_to_hdfs(/bilibili/logs/user_action.log, log_entry)行为日志记录到 HDFS 后MapReduce 作业定期消费这些日志生成用户-视频交互矩阵并缓存到 Redis 或本地文件。推荐接口直接读取缓存矩阵计算结果app.route(/api/video/recommend, methods[GET]) def recommend_videos(): user_id session.get(user_id) # 从缓存读取预处理好的交互矩阵 matrix load_interaction_matrix_from_cache() recommended user_based_cf(user_id, matrix, k10, top_n20) videos HotVideo.query.filter(HotVideo.id.in_(recommended)).all() # 保持推荐的排序顺序 video_map {v.id: v for v in videos} ordered_videos [video_map[vid] for vid in recommended if vid in video_map] data [{id: v.id, title: v.title, heat_score: v.heat_score} for v in ordered_videos] return jsonify({code: 0, data: data})这里有一个实现细节SQL 查询用IN子句查出的结果不保证顺序和传入的 ID 列表一致所以必须用字典映射后再按原顺序组装。推荐接口本身不承担计算任务离线算好、在线读取是推荐系统的标准架构——在线计算相似度矩阵的时间开销无法满足 Web 请求的响应要求。5.4 冷启动问题的应对话术新用户没有任何行为记录协同过滤算不出相似用户推荐自然失效。这个系统里的应对策略是冷启动阶段返回热门榜 Top 20 作为兜底推荐等用户积累了至少 5 条行为记录后再切换到协同过滤结果。判断逻辑很简单def get_recommendation_for_user(user_id): matrix load_interaction_matrix_from_cache() user_behavior_count len(matrix.get(user_id, {})) if user_behavior_count 5: # 冷启动返回全局热门榜 hot_videos HotVideo.query.order_by(HotVideo.heat_score.desc()).limit(20).all() return [_video_to_dict(v) for v in hot_videos] else: # 行为足够走协同过滤 return recommend_videos_by_cf(user_id)至少 5 条行为这个阈值不是拍脑袋定的我测试过不同阈值下的推荐准确率3 条以下时推荐的覆盖率太低8 条以上时新用户等待太久5 条是一个在冷启动容忍度和推荐质量之间比较平衡的取值。项目交付时这个参数应该做成可配置的放在配置文件里而不是硬编码。6. 推荐效果验证与召回率指标计算推荐接口写完后必须验证推荐结果是不是真的有效否则协同过滤算出来的可能只是一堆相似度很高的冷门视频。这个系统里我建议用两个指标做离线验证准确率和召回率。计算方式是把用户的视频行为按时间分成两部分前 80% 作为训练集用于生成推荐后 20% 作为测试集用于验证。def evaluate_recommendation(user_id, train_matrix, test_items, top_n10): # 基于训练集生成推荐列表 recommended user_based_cf(user_id, train_matrix, k10, top_ntop_n) recommended_set set(recommended) # 召回率 推荐的视频中有多少比例出现在测试集里 hit_count len(recommended_set test_items) recall hit_count / len(test_items) if test_items else 0 # 准确率 推荐列表中有多少是用户真实看过的 precision hit_count / top_n return precision, recall评估时有一个注意点test_items中的视频可能是在推荐生成之后才上传的推荐系统不可能预测到未来发布的视频所以做评估时要过滤掉那些不在训练集视频范围内的测试项。更稳妥的做法是设定评估时间窗口只取用户在这个时间窗口内看过的已有视频作为测试集。召回率偏低不一定代表算法有问题。B 站视频长尾分布极其明显用户看的视频集中在少数几个分类但推荐的视频如果过于集中在头部热门准确率虽然好看用户的个性化体验反而差。所以我会同时看推荐的分类分布from collections import Counter def check_recommendation_diversity(recommended_video_ids): videos HotVideo.query.filter(HotVideo.id.in_(recommended_video_ids)).all() type_dist Counter(v.type_name for v in videos) # 如果超过60%的推荐视频来自同一个分类说明多样性偏低 top_type_ratio max(type_dist.values()) / len(recommended_video_ids) if recommended_video_ids else 1 return top_type_ratio多样性指标建议控制在 0.6 以下超过这个值说明推荐结果被单一分类主导需要在排序时加入一个类别抑制因子强制压低同一分类的连续出现比例。验证完这两个指标之后整个系统的数据链路才算真正闭环了Hadoop 存储行为日志Flask 提供业务接口热度模型驱动榜单协同过滤驱动推荐评估指标反过来指导参数调整。这也是这个项目作为毕业设计最有价值的地方——它不是简单的增删改查而是把大数据处理和 Web 开发串成了一个完整的产品。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进