毕业设计选题目最怕两种一种是题太大做不完答辩的时候心里发虚另一种是题太小三两下搞定老师一眼看穿没含金量。基于Django与TensorFlow的个性化音乐推荐系统恰好是中间那种稳妥型——技术上覆盖了Web开发、人工智能、requests爬虫和数据可视化一个项目能把几块硬技能全练到。这个题目之所以值得聊是因为它不做虚的。爬虫解决“数据从哪来”TensorFlow解决“推荐怎么算”Django解决“结果怎么展示”三条线串起来就是一个完整可运行的系统。对毕设来说工作量真实可见对找工作的人来说简历上写“独立实现了一个推荐系统”面试官也愿意多聊几句。这篇文章我会按一个可复现的项目流程来讲系统架构怎么拆、爬虫怎么写不封号、协同过滤和双塔模型怎么选、Django怎么把推荐结果展示出来最后还附上我实际开发中踩过的坑和排查思路。不管是准备毕设还是打算系统学一遍Python全栈这篇文章都能直接拿来当路线图。1. 项目到底做了什么整体架构怎么拆1.1 三条业务链路组成的完整闭环整个音乐推荐系统不是靠某一个算法撑起来的而是由三条链路协作完成。理解这三条链路基本上就理解了项目的全部骨架。数据链路负责采集和存储。requests爬虫从公开的音乐平台歌单、榜单页面取数据字段包括歌曲ID、歌名、歌手、专辑、风格标签、评论数等清洗之后写入数据库。没有这一步后面所有推荐逻辑都是空中楼阁。推荐链路负责计算。先从数据库读取用户历史行为用协同过滤做召回再用TensorFlow训练的双塔模型学习用户和歌曲的向量表示最后把两路结果融合排序生成个性化的推荐列表。展示链路负责输出。Django的视图函数从数据库读取推荐结果渲染成用户能看的网页用可视化图表展示歌曲热度分布、用户画像、歌手占比等信息。这三条链路对应了毕业设计评分表里最常看的几个维度工作量够不够、技术含量够不够、系统是否完整。所以这个选题最大的好处不是某一个点难而是覆盖面完整任何一环都能展开讲清楚。1.2 为什么选Django而不是其他Web框架我见过不少同学用Flask写毕设Flask确实轻量但轻也意味着很多组件要自己搭。用户认证、Admin后台、ORM、模板引擎这些Flask要么没有、要么得靠扩展接第三方。Django全部内置开箱即用开发效率高很多。更重要的一点是Django的MTV模式非常好讲清楚。Model负责数据库表结构Template负责页面展示View负责业务逻辑URL路由负责把请求分发到对应的视图函数。这个模式学生在课上学过答辩时用两三句话就能解释清楚系统的代码组织方式不用绕弯子。数据库这块我建议MySQL配SQLAlchemy或者直接用Django自带的ORM。毕设数据量不大SQLite也不是不行但从“项目像企业级应用”的角度看MySQL会让系统的完整度更高。ORM写起来也比裸SQL安全能规避SQL注入的问题。1.3 为什么选TensorFlow而不是PyTorchPyTorch在学术圈和工业界的流行度这几年确实涨得很猛但我不推荐毕设硬上PyTorch。TensorFlow 2.x的Keras API对学生极度友好模型定义方式像搭积木数据管道、训练循环都有现成封装调试成本低。说白了毕设的核心目标是在有限时间内跑通一个完整系统而不是证明你能写出底层反向传播。TensorFlow在部署和服务化方面也相对成熟之后如果想用TensorFlow Serving把模型发布成接口文档和案例都比PyTorch更完备。我的实际用法是pandas负责数据预处理TensorFlow的Keras搭双塔模型训练完把用户和歌曲embedding向量抽出来和协同过滤的结果做融合。整个过程不需要写太底层的代码但原理一点没少。1.4 数据库表结构怎么设计数据表设计是系统能否顺畅运行的基础。我建议至少建四张表用户表、歌曲表、用户行为表、推荐结果表。表名核心字段作用users用户ID、用户名、密码哈希、注册时间存储注册用户基础信息songs歌曲ID、歌名、歌手、专辑、风格、时长、热度值存储爬虫采集的歌曲元数据behaviors行为ID、用户ID、歌曲ID、行为类型、时间戳记录播放、收藏、评分等行为recommendations用户ID、歌曲ID、推荐分数、生成时间缓存推荐结果避免重复计算行为表是整个推荐系统的数据命脉。用户每点一次播放或者收藏前端就发一个请求记录到这张表里。行为数据积累得越多协同过滤和深度学习模型的效果就越好。这也是为什么我总是建议系统上线后先让几个同学用几天把行为数据攒起来再跑推荐效果会比冷启动阶段好一大截。2. 数据采集requests爬虫与数据落地2.1 先分析数据源再动手写爬虫爬虫最容易犯的错误是一上来就写代码。正确的做法是先花半小时搞清楚数据在哪里、接口是什么、能拿到哪些字段。打开目标网站的榜单页按F12进入开发者工具切到Network面板勾选XHR过滤然后翻页看浏览器发了哪些请求。现在多数音乐平台都是异步加载翻页会触发一个返回JSON的接口这个接口就是采集入口。如果接口直接返回JSON解析会非常顺利json.loads之后就是标准字典不需要跟HTML标签做斗争。抓取字段遵循“宁多勿少”的原则。歌曲ID、歌名、歌手、专辑、风格标签、评论数、封面图URL全部存下来。后面做推荐特征和可视化图表时多一个字段就多一个选择。2.2 requests请求构造headers、Session与超时重试requests库用起来非常简单但裸用很容易翻车。真实采集时必须做足浏览器伪装三个细节最重要。第一User-Agent要真实。不能暴露python-requests的默认UA建议用Chrome的完整UA串。第二带上Referer等补充请求头有些接口会校验来源少了Referer直接拒绝。第三用requests.Session创建会话对象后续请求可以复用连接和cookies遇到需要登录态的接口能省很多事。一个最基础的请求封装大概是这样的import requests import time HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36, Referer: https://music.example.com/, } session requests.Session() session.headers.update(HEADERS) def fetch_list(page): url https://music.example.com/api/song_list params {page: page, size: 50} resp session.get(url, paramsparams, timeout10) resp.raise_for_status() return resp.json() for page in range(1, 20): data fetch_list(page) # 解析并存储 data time.sleep(1)这里timeout参数一定要写。不写timeout网络异常时请求可能会挂起几分钟整个采集脚本卡死排查起来非常痛苦。sleep同样不能省限速是爬虫保命的基本操作。2.3 429限流爬虫一定会遇到的状态码真实跑抓取的同学大概率见过这个报错exceeded retry limit, last status: 429 too many requests。429的含义就是服务器在明说“请求太频繁了歇一会”。处理优先级我建议这样排降速永远排第一。每次请求之后随机sleep一到三秒虽然慢但胜在稳定不会把IP整封。第二是加指数退避重试遇到429先等2秒再重试不行就4秒、8秒最多重试四五次别无限循环。第三是准备多个User-Agent轮换使用降低被识别特征的概率。至于代理IP池毕设完全用不上数据量小没必要把系统复杂化。我实际跑的时候三千首歌曲的采集任务大概用了两个小时左右。这个速度看起来很慢但胜在稳定不封号。爬虫最怕的从来不是慢而是采集到一半IP被封前功尽弃。2.4 数据清洗与入库抓回来的数据几乎没有直接能入库的。常见问题包括字段缺失、类型不对、重复记录。用pandas处理这些非常顺手。import pandas as pd from sqlalchemy import create_engine df pd.read_json(songs.json) df df.drop_duplicates(subset[song_id]) df[singer] df[singer].fillna(未知) df[comment_num] pd.to_numeric(df[comment_num], errorscoerce).fillna(0).astype(int) engine create_engine(mysqlpymysql://user:passwordlocalhost:3306/music_db) df.to_sql(songs, engine, if_existsappend, indexFalse)入库容易踩的坑是中文乱码建库的时候指定utf8mb4连接串里也加上?charsetutf8mb4这样就稳了。合规问题必须说清楚爬虫只用于个人学习研究不要抓取用户隐私数据不要高并发冲击服务器不要用于任何商业场景。做好限速尊重robots.txt和网站条款数据够用就停。技术本身是中性的控制好使用边界。3. 核心推荐逻辑协同过滤 TensorFlow双塔模型3.1 推荐方案选型先传统算法再深度学习推荐系统的常规打法有三条路。基于用户的协同过滤逻辑是找到和你口味相似的用户把他们喜欢的歌推荐给你。基于物品的协同过滤逻辑是找到和你喜欢的歌相似的歌。基于内容的推荐则依赖歌曲本身的风格标签、歌手信息做匹配。毕设如果只做协同过滤工作量有点单薄。直接上复杂的深度学习排序模型又容易在毕设周期内跑不出效果。最稳的组合是两条腿走路协同过滤负责召回候选集TensorFlow训练双塔模型学习用户和歌曲的隐向量最后做加权融合排序。既有传统算法打底又有深度学习的亮点答辩时两边都有东西讲。3.2 协同过滤的相似度计算协同过滤的根基是相似度。最常用的是余弦相似度核心思想看两个向量的夹角大小。夹角越小越相似不管向量长度多长。用户向量习惯用0和1表示是否听过某首歌或者直接用播放次数。计算用户u和用户v的相似度本质上就是看两个人在多大程度上喜欢同样的歌。Python里实现起来很直接import numpy as np def cosine_sim(vec_a, vec_b): return np.dot(vec_a, vec_b) / (np.linalg.norm(vec_a) * np.linalg.norm(vec_b))用户-歌曲矩阵在真实场景下非常稀疏大量元素是0很多相似度算出来也是0。这不影响使用毕竟我们只需要从相似度最高的那部分用户里找推荐依据。找到TopK个相似用户后把他们听过的歌曲按出现次数排序去掉目标用户已经听过的取前N个就是召回结果。3.3 TensorFlow双塔模型让模型学习隐向量双塔模型是推荐召回阶段非常经典的结构。核心思路是用户侧特征输入一座网络塔歌曲侧特征输入另一座网络塔各自映射成低维稠密向量然后计算两个向量的内积或余弦相似度。训练的目标就是让“用户和他喜欢的歌曲”向量尽可能靠近让“用户和随机歌曲”向量尽可能远离。为什么有协同过滤还不够协同过滤本质上是计算历史行为的重合度遇到新用户没有行为记录就失效了。双塔模型可以把用户ID、播放次数、时长、歌曲风格等特征都学进向量具备更强的表达能力和泛化能力。Keras实现一个基础版双塔模型非常简洁from tensorflow.keras import Model, Input from tensorflow.keras.layers import Embedding, Flatten, Dot user_input Input(shape(1,), nameuser_id) item_input Input(shape(1,), nameitem_id) user_emb Flatten()(Embedding(user_cnt, embed_dim)(user_input)) item_emb Flatten()(Embedding(item_cnt, embed_dim)(item_input)) dot_score Dot(axes1)([user_emb, item_emb]) model Model(inputs[user_input, item_input], outputsdot_score) model.compile(optimizeradam, lossbinary_crossentropy)embedding维度我建议取64或128。维度太低学不出足够的区分度维度太高在数据量不足时容易过拟合。3.4 负采样与训练调参要点双塔模型训练里最关键的环节是负采样。如果只给模型看正样本模型很快学会对所有样本输出高分因为光把正样本分数拉高就能让loss归零。必须给每个正样本配几个负样本让模型学会区分。负样本怎么选最简单有效的方法是从用户没听过的歌曲里随机抽取。采样比例我试过1比2、1比4、1比51比4的效果比较均衡。负样本太少模型学不到边界太多训练时间变长且收益衰减。训练参数方面batch_size建议128或256epoch设置5到10轮。毕设数据量几千条的话CPU训练完全够用几分钟就能跑完。loss下降缓慢时先别急着加模型复杂度检查一下负采样有没有配比问题再把学习率调到0.0001到0.001之间试试。3.5 从召回、排序到冷启动兜底模型训练完成后每个用户和每首歌都有了对应的embedding向量。用内积计算用户向量和所有歌曲向量的分数取TopK和协同过滤的召回结果合并就完成了召回阶段。排序阶段要做轻量级重排。我常用的策略是加权融合加上业务规则对推荐分数做归一化再乘以热度系数热度高、评论多、口碑好的歌曲会有轻微加权同时按时间衰减年代太久的歌降权最后控制多样性同一歌手的歌曲不要连续霸占列表前几名。冷启动是新系统绕不开的问题。新用户没有行为数据协同过滤和双塔模型都束手无策这时候用热门榜单兜底按收藏数和播放数降序取Top50。新歌没有交互记录就用爬虫抓取的风格标签做基于内容的相似推荐。这两个问题的答案一定要提前准备答辩时老师必问。4. Django Web应用与可视化展示4.1 Django项目初始化与MTV配置Django项目的创建流程很标准先建项目再建应用pip install django django-admin startproject music_platform cd music_platform python manage.py startapp recommendation推荐模型的代码放在recommendation应用里爬虫采集脚本单独放一个crawler目录职责分开代码看起来干净。settings.py里有几个关键配置INSTALLED_APPS添加recommendation应用DATABASES配置MySQL连接TEMPLATES配置模板路径STATIC_URL和STATICFILES_DIRS配置静态文件目录。静态文件问题是我见过最多的坑之一。很多同学在vscode里写完img标签图片死活不显示。第一反应往往是路径错了实际上大多是因为settings里没有配置STATICFILES_DIRS或者模板文件忘了在顶部写{% load static %}。两个地方检验准没错以后别在路径上反复折腾。4.2 用MTV关系理清业务代码以推荐页为例用户点击“获取我的推荐”前端发请求到/recommend/URL路由把请求交给视图函数视图函数从数据库读取用户行为、调用推荐服务函数拿到歌曲ID列表再查歌曲表补充完整信息最后把数据打包传给模板渲染。整个过程就是MTV三层各司其职Model负责查数据View负责写业务逻辑Template负责展示。视图函数保持精简很重要推荐逻辑抽出去。比如def recommend_view(request): user request.user songs get_recommend_list(user.id, top_n30) return render(request, recommend.html, {songs: songs})用户行为记录的埋点也要做。页面上的“播放”和“收藏”按钮触发POST请求视图函数往behaviors表插入记录。行为数据就是推荐系统的养料越积越准。到了后期想清掉测试数据用ORM的filter加delete就行比如UserBehavior.objects.filter(user_iduid).delete()一行搞掂。4.3 可视化让论文和答辩多几张好图可视化是这个项目的加分项也是很多人忽略的一项。我习惯用ECharts在页面嵌入图表主要做四块内容歌曲热度Top10柱状图、用户听歌时段热力图、歌手风格分布饼图、用户与歌曲embedding向量降维散点图。Embedding降维散点图是论文利器。双塔模型训练出来的向量动辄几十维直接画图看不出结构用TSNE降到二维后用户和歌曲的聚类效果非常直观。代码是现成的from sklearn.manifold import TSNE points TSNE(n_components2, perplexity30).fit_transform(embeddings)这一张图放在论文里比堆十组指标数字更有说服力能直接证明模型学到了有效特征。5. 踩坑实录与常见问题排查5.1 爬虫侧高频问题问题一429 too many requests请求被限流。解决优先级前面说过降速、指数退避重试、UA轮换三招按顺序用。问题二中文乱码。requests有时不能准确推断网页编码手动指定一下就行。resp.encoding utf-8有些站点是GBK编码设成gbk同样能解。问题三接口突然没数据。先确认浏览器里接口是否正常再排查IP有没有被封最后看请求参数是否过期。很多时候是对方改了参数名对比一下最新的网络请求就能发现。5.2 TensorFlow训练侧高频问题TensorFlow安装Windows环境下载慢时用清华镜像装CPU版本就够毕设用。几千条数据CPU跑完全没问题真没必要为了毕设折腾GPU环境。训练loss不下降先检查样本生成逻辑。正负样本比失衡是最常见的原因。再调学习率在0.0001到0.01之间多试几个值。最后看看特征有没有统计错误比如歌曲ID映射表对不上。过拟合数据量太小的时候特别容易发生。解决方法是加大负采样比例、降低embedding维度、加早停。看效果要看验证集指标别死盯着训练集loss。5.3 Django侧高频问题静态文件404按顺序检查settings里STATICFILES_DIRS有没有配好、模板有没有{% load static %}、文件实际路径对不对。这三处基本覆盖所有情况。数据库中文乱码库、表、连接三层字符集要一致。建库时指定utf8mb4连接串加上?charsetutf8mb4。MySQL的乱码八成出在某一层还是默认latin1。端口被占用runserver 8000报错时换个端口就行或者用findstr查占用进程顺手kill掉。5.4 系统联调时的整体排查思路联调时最怕的是整体空白。有一回推荐页死活出不来数据我从数据库开始一层层查songs表有数据没有、behaviors表有数据没有、推荐服务函数跑没跑通、视图传没传给模板、模板渲染对不对。最后定位到是行为表里存的用户ID和推荐函数读的ID不是同一种类型一个字符串一个整数条件匹配不上。加一行类型转换就解决了。这种按数据流逐层排查的思路应该是做项目的基本素养。遇到问题别瞎猜顺着数据流动的方向打日志很快就能把问题卡在某一层。最后再分享两个实战小技巧。第一答辩前一定要准备好“冷启动怎么解决”和“数据从哪里来”这两个问题的答案几乎必问。第二代码全程放到GitHub或Gitee私有仓库README写清楚环境版本和启动步骤。导师要代码时直接发仓库地址不仅显得专业对自己后续复盘也有很大帮助。这个项目做完的体会是真正难的不是某一个框架怎么用而是把数据、算法、Web三条线组装成一套完整系统。整个过程里爬虫让我理解了数据获取的边界TensorFlow让我看到了深度模型离线训练到在线服务的差距Django让我明白了Web应用不只是页面跳转。如果后续想进阶可以把双塔模型换成注意力机制或者把系统用Docker容器化部署到服务器。框架已经有了最难的反而是动手开始第一步。