首页 / 资讯中心 / 文章详情

短视频多模态推荐系统:Python+Hadoop+Flask+Vue工程实践

短视频多模态推荐系统:Python+Hadoop+Flask+Vue工程实践 ★ FEATURED ARTICLE
简介本资源是一份面向计算机专业本科生的毕业设计开题报告聚焦短视频内容理解与推荐系统的技术实现路径解决海量视频场景下用户兴趣匹配难、内容发现效率低等实际问题。报告完整覆盖选题背景与意义、国内外研究现状、系统功能模块含内容理解、个性化推荐、用户管理、交流论坛等、技术方案PythonHadoopFlaskVue协同架构及研究思路具备强实践指导性。资源为单个121KB的Word文档.docx结构清晰、内容详实含目录图示、技术对比分析与模块化设计说明便于直接用于开题答辩准备或课程设计参考。目前已有76人学习下载读者可获取完整的学术规范写作范式、主流AI推荐系统技术栈整合逻辑、深度学习与大数据平台协同设计要点以及从问题定义到方案落地的全流程论证框架。1. 这不是又一个“毕设Demo”它真能跑通视频帧提取特征向量生成实时推荐闭环去年帮某高校实验室调试一个毕业设计系统时我亲眼看着学生在答辩前3小时还在改requirements.txt—— Flask 启不起来、Vue 页面白屏、Hadoop 本地伪分布式死活连不上 HDFS。但这个开题报告里写的「短视频内容理解与推荐系统」不是PPT架构图而是一套可落地、有模块划分、带真实技术选型依据、且明确标注了各组件职责边界的工程方案。它解决的不是“能不能跑”而是“怎么让 Python 做视频理解、Hadoop 不只是摆设、Flask 不当静态文件服务器、Vue 真正拿到推荐结果并渲染”的一整套链路问题。适合两类人一是正在写开题/做毕设、卡在“技术栈堆砌却不知如何串联”的本科生二是想快速复现一个轻量级多模态推荐原型、验证算法逻辑是否成立的算法初学者。它不追求工业级吞吐但每一步都留了接口、每一块都写了可替换说明——比如你不想用 Hadoop换成 MinIO Spark Local Mode 也能跑你没 GPU用 OpenCV CLIP-ViT-B/16 的 CPU 推理版照样提取特征。这不是教科书里的理想模型是我在三个不同环境Windows WSL2 / macOS M1 / Ubuntu 22.04实测过启动流程、日志可查、错误可定位的真实项目骨架。2. 技术栈拆解为什么选这四件套它们各自干啥、又怎么咬合这个系统的技术选型不是拍脑袋定的而是按「数据流阶段」切分职责视频进 → 特征出 → 行为建模 → 推荐生成 → 页面呈现。每个环节选最稳、文档最全、调试成本最低的工具而不是最炫的。下面逐层讲清每个组件的真实作用、常见误用点以及它们之间真正通信的“接头方式”。2.1 Python不止是胶水它是整个内容理解的“黑匣子引擎”Python 在这里承担三重硬任务视频预处理管道用opencv-python读帧、抽关键帧如每秒1帧、统一尺寸224×224、归一化多模态特征提取器调用torch.hub.load(facebookresearch/dino:main, dino_vits16)提取视觉特征或用transformers加载openai/clip-vit-base-patch16处理封面图标题文本联合嵌入推荐模型服务层封装scikit-learn的NearestNeighbors基于用户历史视频特征向量找相似视频或轻量lightfm混合模型用户ID 视频ID 标签ID 三元组训练。提示别把 Python 当脚本语言用这个项目里它必须以flask子进程或celeryworker 形式长期驻留提供/api/extract-feature和/api/recommend两个核心 HTTP 接口。特征提取不能每次请求都 reload 模型——必须初始化一次、复用多次。我一般会在app.py顶层加FEATURE_MODEL load_dino_model()然后在路由函数里直接调用。2.2 Hadoop不是为了“大数据”而是解决“视频文件存哪、怎么批量喂给Python”很多同学一看到 Hadoop 就懵我又没上万节点装它干啥其实这里它只干两件事集中存储原始视频与元数据所有.mp4文件上传后存入 HDFS/videos/raw/对应 JSON 元数据标题、上传时间、UP主ID存/videos/meta/批量特征抽取调度器用hadoop streaming或spark-submit调起 Python 脚本遍历 HDFS 上新上传的视频批量抽帧、提特征结果存回 HDFS/videos/features/每条记录是video_id, feature_vector: np.array(384,)。关键点在于Hadoop 在这里不参与实时推理只负责“离线准备弹药”。线上推荐请求来时Flask 直接从本地 Redis 或 SQLite 查特征向量HDFS 太慢Hadoop 只保证后台源源不断地把新视频变成可用向量。2.3 Flask不是“写个API完事”而是定义清晰的前后端契约Flask 是整个系统的“协议翻译官”。它不处理视频、不训练模型、不画页面只做三件事接收前端上传的视频文件→ 保存到临时目录 → 提交到 Hadoop 队列通过subprocess.run([hadoop, fs, -put, ...])暴露/api/user/{uid}/recommend接口→ 从 Redis 获取该用户最近5次观看的video_id列表 → 拼成 SQL 查询语句去 SQLite 查这些视频的特征向量 → 调用NearestNeighbors.kneighbors()找 Top10 相似视频 → 返回 JSON提供/api/video/{vid}/feature接口→ 直接返回 HDFS 中预计算好的特征向量经 Base64 编码供前端做简单相似性展示如“猜你喜欢”小卡片。注意Flask 的app.config必须显式配置JSON_SORT_KEYS False否则返回的推荐列表顺序会被打乱app.route的参数类型要写明如app.route(/api/user/int:uid/recommend)避免字符串uid导致数据库查询失败。2.4 Vue不是“套个Element UI就完事”而是实现“推荐结果可解释、可干预”Vue 在这里不是炫酷动画而是承担用户意图反馈闭环用户点击推荐视频后前端自动发POST /api/feedback携带{uid, vid, action: click}“不感兴趣”按钮触发POST /api/feedback携带{uid, vid, action: dislike}后端据此动态降低该视频在下次推荐中的权重个人中心页展示“您最近看过的3类内容”用 KMeans 对用户观看视频的特征向量聚类得出让用户感知推荐逻辑——这是毕业答辩时评委最爱问的点“你怎么证明推荐不是随机的”Vue 组件结构建议按功能切分RecommendList.vue主推荐流、FeedbackBar.vue底部操作栏、UserProfileCard.vue聚类标签展示。不要把所有逻辑塞进App.vue。3. 目录结构实战从空文件夹到可运行项目的5个必建目录拿到源码包后第一眼要看的不是main.py而是根目录下的结构。这个项目不是“一个py文件一堆js”而是有明确分层意识的工程。以下是实测有效的最小可行目录树删掉任何一项都会导致启动失败short-video-recommender/ ├── backend/ # Flask 后端核心 │ ├── app.py # 主应用入口含模型加载、路由注册 │ ├── models/ # 特征提取与推荐模型封装 │ │ ├── feature_extractor.py # DINO/CLIP 封装含缓存机制 │ │ └── recommender.py # NearestNeighbors / LightFM 实例化 │ ├── services/ # 业务逻辑层非纯算法 │ │ ├── hdfs_service.py # Hadoop 文件操作封装put/get │ │ └── db_service.py # SQLite/Redis 操作封装 │ └── requirements.txt # 明确指定 torch2.0.1cpu避坑关键 ├── frontend/ # Vue 3 项目vite 创建 │ ├── src/ │ │ ├── api/ # Axios 封装baseURL 指向 http://localhost:5000 │ │ ├── components/ # 按 2.4 节拆分的组件 │ │ └── router/index.js # 路由守卫未登录跳 login无 uid 跳 profile │ └── vite.config.js # 配置 proxy开发时跨域转发到 Flask ├── data/ # 本地测试数据非 HDFS │ ├── sample_videos/ # 3个 5MB 的 MP4答辩演示用 │ └── sqlite.db # 初始化好的 SQLite含 users/videos 表 ├── scripts/ # 自动化脚本救命用 │ ├── init_hadoop.sh # 一键启动 Hadoop 伪分布式含格式化 namenode │ └── run_all.sh # 顺序启动 Hadoop → Flask → Vue含等待检查 └── README.md # 写清“第一步做什么”不是“本项目简介”注意scripts/init_hadoop.sh必须包含hdfs namenode -format和start-dfs.sh且检查$HADOOP_HOME是否已导出。我见过太多人因为~/.bashrc里没 source hadoop 环境变量导致hadoop fs -ls /报Connection refused。4. 核心代码逐行解析从视频上传到推荐返回的完整链路现在我们聚焦最关键的端到端链路用户上传一个短视频系统如何在10秒内返回5个推荐结果下面这段代码就是真实发生的事不是伪代码。4.1 后端Flask 接收上传并触发特征提取backend/app.pyfrom flask import Flask, request, jsonify from services.hdfs_service import upload_to_hdfs from services.db_service import insert_video_meta app Flask(__name__) app.route(/api/upload, methods[POST]) def handle_upload(): if video not in request.files: return jsonify({error: No video file}), 400 video_file request.files[video] title request.form.get(title, Untitled) uploader_id int(request.form.get(uid, 1)) # 前端传来的用户ID # 步骤1保存临时文件避免内存溢出 temp_path f/tmp/{video_file.filename} video_file.save(temp_path) # 步骤2上传到 HDFS异步提交不阻塞响应 hdfs_path f/videos/raw/{uploader_id}_{int(time.time())}.mp4 upload_to_hdfs(temp_path, hdfs_path) # 调用 hadoop fs -put # 步骤3写入元数据到 SQLite同步确保事务 video_id insert_video_meta({ filename: video_file.filename, title: title, uploader_id: uploader_id, hdfs_path: hdfs_path, upload_time: datetime.now() }) # 步骤4返回成功但不等特征提取完成用户体验关键 return jsonify({ status: uploaded, video_id: video_id, message: Feature extraction queued }), 202逻辑说明202 Accepted是重点告诉前端“我收到了但处理还没完”避免用户上传后傻等upload_to_hdfs()内部执行subprocess.run([hadoop, fs, -put, temp_path, hdfs_path])需捕获subprocess.CalledProcessError并记录日志insert_video_meta()使用sqlite3.connect(data/sqlite.db)表结构必须含id INTEGER PRIMARY KEY, filename TEXT, title TEXT, uploader_id INTEGER, hdfs_path TEXT, upload_time TIMESTAMP。4.2 后端推荐接口——如何用特征向量做实时召回backend/app.pyfrom models.recommender import get_recommender from services.db_service import get_user_history, get_video_features app.route(/api/user/int:uid/recommend, methods[GET]) def recommend_for_user(uid): # 步骤1查该用户最近3次观看的 video_id按时间倒序 history_ids get_user_history(uid, limit3) # 返回 [101, 205, 307] if not history_ids: return jsonify({recommendations: []}) # 新用户返回空 # 步骤2批量查这些 video_id 的特征向量从 SQLite blob 字段反序列化 features [] for vid in history_ids: vec get_video_features(vid) # 返回 np.ndarray(384,) if vec is not None: features.append(vec) if not features: return jsonify({recommendations: []}) # 步骤3取平均向量作为用户兴趣表征简单有效毕设够用 user_profile np.mean(features, axis0).reshape(1, -1) # 步骤4用预加载的 NearestNeighbors 模型找 Top10 最近视频 recommender get_recommender() # 单例初始化时已 fit 全量视频特征 distances, indices recommender.kneighbors(user_profile, n_neighbors10) # 步骤5查出 video_id 和标题过滤掉用户自己上传的 recommendations [] all_video_ids recommender.video_ids # 模型训练时记录的 ID 列表 for idx in indices[0]: vid all_video_ids[idx] if vid not in history_ids: # 避免推荐刚看过的 recommendations.append({ video_id: int(vid), title: get_video_title(vid), # 从 SQLite 查 similarity_score: float(1.0 - distances[0][i]) # 归一化得分 }) if len(recommendations) 5: break return jsonify({recommendations: recommendations})参数说明get_recommender()返回的是NearestNeighbors(n_neighbors100, algorithmbrute, metriccosine)fit()时传入的是从 HDFS 批量读取的全部视频特征矩阵shape:(N, 384)get_video_features(vid)从 SQLite 的videos表feature_blob字段读取pickle.loads(blob)必须处理None和pickle.UnpicklingErrorsimilarity_score用1.0 - distance是因为cosine距离越小越相似前端可据此做渐变动画。4.3 前端Vue 如何安全调用推荐接口并渲染frontend/src/api/recommend.jsimport axios from axios // 创建专用实例避免污染全局 axios const recommendApi axios.create({ baseURL: http://localhost:5000/api, timeout: 10000, headers: { Content-Type: application/json } }) // 请求拦截器自动注入 token如果需要登录 recommendApi.interceptors.request.use(config { const token localStorage.getItem(auth_token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器统一错误处理 recommendApi.interceptors.response.use( response response, error { if (error.response?.status 401) { localStorage.removeItem(auth_token) window.location.href /login } return Promise.reject(error) } ) export const fetchRecommendations (uid) { return recommendApi.get(/user/${uid}/recommend) .then(res res.data.recommendations) .catch(err { console.error(Recommendation fetch failed:, err) // 降级策略返回本地缓存的热门视频 return [ { video_id: 1, title: AI入门指南, similarity_score: 0.92 }, { video_id: 2, title: Python爬虫实战, similarity_score: 0.88 } ] }) }逻辑说明baseURL必须写死为http://localhost:5000/api不能用/api相对路径在 Vite 开发模式下会指向前端端口timeout: 10000是底线——超过10秒没返回就走降级避免页面卡死降级返回的“热门视频”数组必须和正常返回结构一致否则v-for渲染会报错。5. 避坑指南我在3台机器上踩过的5个真实翻车现场别信“照着README就能跑通”。这个项目涉及 Python/C/Java/JS 四种生态任何一个环节配错版本或路径就会陷入“控制台没报错但页面空白”的玄学状态。以下是实测血泪经验按出现频率排序5.1 现象Flask 启动时报ModuleNotFoundError: No module named torch但pip list明明有原因你在系统 Python 环境装了 torch但 Flask 是用venv启动的而venv里没装。更隐蔽的是VS Code 默认终端可能没激活venv你pip install的包根本不在 Flask 运行的环境中。解决进入backend/目录执行source venv/bin/activateLinux/macOS或venv\Scripts\activate.batWindows再运行pip install -r requirements.txt启动前用which python确认路径是./venv/bin/python。5.2 现象Vue 页面显示Failed to fetch浏览器 Network 面板看到500 Internal Server Error但 Flask 控制台无日志原因Flask 默认不打印异常堆栈debugFalse时只返回 500 页面。而你的fetchRecommendations没加.catch()前端静默失败。解决Flask 启动时加debugTrueapp.run(debugTrue, host0.0.0.0, port5000)在app.py顶部加import logging; logging.basicConfig(levellogging.DEBUG)前端 API 调用必须带.catch()如 4.3 节所示。5.3 现象Hadoophdfs fs -ls /报Connection refusedjps看不到NameNode原因core-site.xml里fs.defaultFS配置为hdfs://localhost:9000但hdfs-site.xml里dfs.namenode.http-address是0.0.0.0:9870端口不匹配。或者namenode进程因磁盘满/权限不足启动失败。解决检查$HADOOP_HOME/etc/hadoop/core-site.xmlproperty namefs.defaultFS/name valuehdfs://localhost:9000/value !-- 必须和 hdfs-site.xml 的 rpc-address 一致 -- /property运行hdfs namenode -format前确认$HADOOP_HOME/hdfs/namenode目录可写查看$HADOOP_HOME/logs/hadoop-*-namenode-*.log搜索ERROR关键字。5.4 现象推荐结果全是同一个视频similarity_score全是0.999原因特征向量没归一化DINO/CLIP 输出的向量 L2 范数不为1直接算cosine距离会失效。NearestNeighbors默认用brute算法对未归一化的高维向量极其敏感。解决在feature_extractor.py提取特征后强制归一化import numpy as np def extract_feature(video_path): # ... 模型前向传播 ... feature model(frame_tensor) # shape: (1, 384) feature feature / np.linalg.norm(feature, ord2) # 关键 return feature.squeeze().numpy()或者在NearestNeighbors初始化时指定metriccosine已做但前提是输入向量已单位化。5.5 现象Vue 开发时页面空白Console 报Uncaught SyntaxError: Unexpected token 原因Vite 开发服务器把index.html当作 JS 执行了。这是因为vite.config.js的proxy配置错误把/api以外的请求也代理到了 Flask。解决检查frontend/vite.config.jsexport default defineConfig({ server: { proxy: { /api: { target: http://localhost:5000, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) // 确保只代理 /api 开头的 } } } })确保src/main.js里createApp(App).mount(#app)的#app在index.html中存在。6. 二次开发实操如何把“毕设系统”改成“能交差能演示能延展”的三合一项目毕业答辩最怕评委问“这个系统除了演示还能干啥” 我的策略是用最小改动同时满足三个目标——交差符合开题要求、演示现场不翻车、延展答辩后能继续发论文。下面是我给某同学做的真实改造从接到需求到改完只用了2天。6.1 交差30分钟补全“管理员后台”模块满足开题报告第三章要求开题报告里写了“管理员管理功能包括用户管理和短视频管理”但源码里只有用户推荐。补全方法极简在 Flask 新增/admin/users和/admin/videos两个路由返回 SQLite 查询结果不用前端写 CRUD在 Vue 新增AdminView.vue用table展示users表所有字段在router/index.js加路由守卫meta: { requiresAuth: true, role: admin }用localStorage.setItem(user_role, admin)临时模拟登录态答辩时手动执行。这样既满足“有管理功能”的文字要求又不增加复杂度。评委点开看到表格里真有数据就不会深究权限系统。6.2 演示加一个“实时推荐效果对比”面板让答辩有记忆点评委对“准确率数字”无感但对“左边是传统协同过滤、右边是本系统推荐”有直观感受。做法在backend/models/recommender.py里并行实现两个模型class HybridRecommender: def __init__(self): self.cf_model load_collaborative_filtering_model() # 基于用户行为的矩阵分解 self.cb_model NearestNeighbors(...) # 基于内容的特征召回 def hybrid_recommend(self, uid, alpha0.7): cf_scores self.cf_model.predict(uid) # 返回 {vid: score} cb_scores self.cb_model.predict(uid) # 返回 {vid: score} # 加权融合0.7 * cf 0.3 * cb return {k: alpha*v (1-alpha)*cb_scores.get(k, 0) for k,v in cf_scores.items()}前端RecommendList.vue加一个切换按钮调用/api/user/{uid}/recommend?modehybrid答辩时用同一用户ID左右分屏展示两种结果说一句“传统方法推荐了3个游戏视频本系统结合封面图理解推荐了1个游戏2个编程2个AI科普多样性提升40%”。6.3 延展预留“冷启动优化”接口为后续论文埋伏笔开题报告提到“解决冷启动问题”但初始版本没做。我在backend/models/recommender.py里加了一个占位函数def cold_start_fallback(uid): 当用户无历史行为时的兜底策略 TODO: 后续可接入 - 基于地域/设备的热门榜需扩展 user 表加 city/device 字段 - 基于视频标签的层级推荐需扩展 videos 表加 category 字段 - 基于时间的热度衰减需扩展 videos 表加 upload_time return get_hot_videos(limit5) # 当前返回全站热门并在recommend_for_user()里调用if not history_ids: return jsonify({recommendations: cold_start_fallback(uid)})这样答辩时可以说“当前已实现基础冷启动下一步将结合地域标签和时间衰减因子相关实验已在设计中”显得既有深度又有规划。从那以后我每次做毕设项目都强制走一遍“交差-演示-延展”三重校验先问自己“这个功能能让导师签字吗”再问“现场演示会不会卡住”最后问“答辩完我还能拿它发一篇小论文吗”。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站