如果今年你抽到的是“基于Django与TensorFlow的个性化音乐推荐系统”这个毕业设计题目,那恭喜你,这绝对是一个性价比很高的选题。它一头连着Web开发,一头连着人工智能与大数据,既有爬虫采集,又有算法建模,还有可视化展示,几乎把计算机专业本科阶段的核心技能点都串起来了。更关键的是,这套系统做出来之后,你的毕业答辩可以从“数据从哪来、模型怎么算、系统怎么跑”三个角度反复讲,完全不愁没东西说。
我之所以敢这么讲,是因为我自己带过不少类似的项目,也帮人改过好几版这类系统。今天这篇文章,我就把整套系统的设计与实现思路拆开揉碎,从爬虫到推荐算法,从Django后端到可视化大屏,完整讲一遍。无论你是准备拿它当毕业设计,还是单纯想练手一个全栈AI项目,这篇文章都能让你少走很多弯路。
1. 项目整体设计与思路拆解
1.1 需求定位:这套系统到底要做什么
个性化音乐推荐系统,表面看就是一个“给用户推荐歌曲”的网站,但真正落地的时候,你需要拆成三个核心问题来思考。
第一个问题是数据从哪来。推荐系统是数据驱动型的应用,没有用户行为数据,算法再先进也是白搭。真实场景中,音乐平台会对用户画像与行为日志进行深度建模,但对个人开发者来说,拿到这种数据基本不现实。因此,用requests爬虫去抓取公开的音乐榜单、歌曲信息、评论数据,就成了最合理的方案。公开数据源的数据量足够训练一个演示级别的推荐模型,抓取过程本身也是毕业设计的重要工作量。
第二个问题是怎么推荐。这里需要引入人工智能算法。TensorFlow是当前最重要的深度学习框架之一,在推荐领域有非常成熟的落地案例。我们可以基于用户对歌曲的播放、收藏、跳过等行为,训练一个协同过滤模型或者深度神经网络模型,让系统能够根据用户历史行为预测他可能喜欢的新歌。
第三个问题是怎么呈现。推荐系统不能只跑在命令行里,你需要一个可视化的Web平台,让用户能注册登录、试听歌曲、查看推荐结果,还要让管理员能看到数据统计和推荐效果。这部分用Django来承载非常合适,Django的MTV架构、ORM机制和模板引擎能极大提升开发效率。
整体来看,这个选题覆盖了大数据采集、人工智能建模、Web系统开发三个核心板块,工作量饱满但不至于失控,非常适合作为本科毕业设计。
1.2 技术选型:Django、TensorFlow、requests、ECharts各司其职
先说我为什么坚持用Django而不是Flask或FastAPI。Django最大的优势是“全家桶”,自带Admin后台、用户认证、ORM数据库操作、CSRF防护等功能,一个人开发整套系统时效率极高。而且Django的MTV模式对新手来说非常友好,M(Model)负责数据模型,T(Template)负责页面模板,V(View)负责业务逻辑。你不需要像做Flask那样自己组装一堆第三方库,Django已经帮你安排好了最佳实践。
TensorFlow的选择则有两层考虑。一是毕业设计需要体现“人工智能”元素,用深度学习框架做推荐模型是门槛较低、展示效果较好的方式。二是TensorFlow的Keras接口非常友好,写一个Embedding加Dense的推荐网络只要几十行代码,对不擅长算法的同学也很友好。相比PyTorch,TensorFlow在部署和可视化工具链上更成熟,TensorBoard能直接展示训练曲线,放在论文和答辩PPT里效果很好。
爬虫端用requests是最稳妥的选择。Scrapy虽然功能更强,但学习成本更高,而且对一个演示项目来说有点杀鸡用牛刀。requests搭配BeautifulSoup或正则表达式,再配合JSON解析,完全可以满足需求。更重要的是,requests是很多高校《Python网络爬虫》课程的重点内容,答辩时分步讲解更占优势。
前端可视化我用的是ECharts。Django的模板引擎负责页面骨架,ECharts通过Ajax请求后端接口拿JSON数据,然后渲染成交互图表。前后端通过接口通信,职责清晰,后期想拆成前后端分离项目也容易。
1.3 数据流向与整体架构
这套系统的数据流主线是:requests爬虫把外部平台的歌曲信息抓下来 → 写入MySQL或SQLite数据库 → DataFrame加载数据做清洗和特征工程 → TensorFlow读取清洗后的行为数据训练推荐模型 → Django启动时加载模型权重 → 用户点击“获取推荐”时,后端把用户特征送入模型推理 → 返回Top N歌曲列表 → ECharts渲染推荐结果和统计图表。
这里要特别注意,爬虫、数据处理、模型训练、Web服务这四块尽量解耦。我的建议是爬虫脚本单独放一个目录,模型训练写成专门的脚本,Django项目负责对外提供服务。各模块独立之后,训练的时候不依赖Web服务,Web服务也不需要每天重新训练模型,维护起来轻松很多。
2. 数据采集与预处理实战
2.1 爬虫目标与合规边界
写爬虫之前,先把合规红线画清楚。我最常跟学生强调的一句话是:爬虫只爬公开数据,不碰登录态,不做高并发,不加逆向破解。
网上很多帖子讲爬虫都会提到绕过登录、破解加密参数、模拟JS逆向这些东西,这类内容看看就好。一个是技术上容易翻车,另一个是法律风险极高。热搜词里那些“因爬虫入狱”“逆向”相关的信息就足以说明问题。做毕业设计,我们的目标是拿到足够训练模型的数据,不是当黑客。
所以我的建议是爬取无需登录即可访问的公开榜单页面,比如排行榜、歌单广场这些入口。抓取频率控制在每秒1到2个请求,代码里主动设置time.sleep随机延时,并且在robots.txt允许的范围内采集。这样的爬虫既是合规的,演示时也完全经得起导师追问。
2.2 抓取流程:requests请求、解析与入库
以某个公开音乐榜单为例,整个抓取思路是这样的。
先构造请求头,模拟浏览器访问。很多站点会对不带User-Agent的请求直接拒绝,所以请求头一定要伪装到位。然后发送GET请求拿到页面内容,如果返回的是JSON接口就解析JSON,如果返回的是HTML页面就用BeautifulSoup解析,或者用正则表达式提取歌曲名、歌手、专辑、播放链接、封面图等字段。
import requests import time import random import pandas as pd 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/", "Accept": "application/json, text/plain, */*" } def fetch_song_list(page): url = f"https://music.example.com/api/toplist?page={page}" resp = requests.get(url, headers=headers, timeout=10) if resp.status_code != 200: print(f"请求失败:{resp.status_code}") return [] data = resp.json() songs = [] for item in data.get("data", {}).get("list", []): songs.append({ "song_id": item.get("id"), "title": item.get("name"), "artist": item.get("singer"), "album": item.get("album"), "duration": item.get("interval"), "play_count": item.get("play_cnt"), "cover_url": item.get("pic"), "source": "公开榜单" }) return songs all_songs = [] for page in range(1, 20): songs = fetch_song_list(page) all_songs.extend(songs) time.sleep(random.uniform(1, 3)) df = pd.DataFrame(all_songs) df.drop_duplicates(subset=["song_id"], inplace=True) df.to_csv("songs_raw.csv", index=False, encoding="utf-8-sig")这段代码里我做了几个关键处理。超时设置是必须的,不然某个请求卡住会拖死整个爬虫。去重也重要,分页抓取很容易因为接口数据交叉出现重复的歌曲ID。最后存成CSV是为了方便后面用pandas做数据清洗,实际项目中你也可以直接写入数据库。
2.3 数据清洗与特征构造
抓到原始数据后不能直接拿去训练模型,需要先做清洗。这一步听着没什么技术含量,但实际上非常影响推荐效果。
首先要处理缺失值。歌曲名、歌手这些核心字段如果为空,直接删除该行,因为缺失关键信息的歌曲无法用于推荐。播放次数这种数值字段如果为空,可以用均值填充或者置为0。其次要处理异常值,比如时长超过一小时或者为负数的记录,基本都是脏数据。播放量分布通常非常不均衡,头部歌曲几百万播放,长尾歌曲只有几十次,建议做一个log1p变换压缩量纲,避免数值差距过大影响模型训练。
接着是构造用户行为数据。真实场景里,用户对歌曲的操作有播放、收藏、下载、跳过,不同行为的权重是不一样的。我在这个项目里构造了一套模拟行为数据,参照真实场景设定用户ID、歌曲ID、行为类型、行为时间等字段。行为类型按权重赋值:播放记1.0,收藏记2.0,下载记3.0,跳过记-0.5。最终得到一张交互表,每行就是一个用户对某首歌的一次操作。
import numpy as np df["play_count_log"] = np.log1p(df["play_count"]) df["duration_min"] = df["duration"] / 60 # 过滤异常时长 df = df[(df["duration_min"] > 0.5) & (df["duration_min"] < 20)] # 行为权重映射 behavior_weight = {"play": 1.0, "collect": 2.0, "download": 3.0, "skip": -0.5} df["rating"] = df["behavior"].map(behavior_weight)特征构造的本质是把原始字符串转成模型能计算的数值。歌曲ID要编码成从0开始的整数索引,用户ID同理。这样后面在TensorFlow里做Embedding时才能直接查表。
2.4 反爬限流与请求失败处理
爬虫写好后,最常见的报错就是exceeded retry limit, last status: 429 too many requests。429状态码的意思是请求太频繁,被服务器限流了。
遇到429不能硬刚,越刚越容易被封。我的处理方案是三重退避。第一,每次请求之间加随机延时,哪怕延时区间是1到2秒,也能极大降低触发限流的概率。第二,检测到429响应后,读取响应头里的Retry-After字段,如果服务器明确告诉你等多久,就老老实实等到时间再重试。第三,增加最大重试次数和指数退避逻辑,第一次失败等2秒,第二次等4秒,第三次等8秒,连续失败超过5次就放弃当前请求,记入日志。
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type class RateLimitError(Exception): pass @retry( stop=stop_after_attempt(5), wait=wait_exponential(multiplier=2, min=2, max=30), retry=retry_if_exception_type((RateLimitError, requests.exceptions.Timeout)) ) def fetch_song_list_with_retry(page): # 省略请求细节... if resp.status_code == 429: retry_after = int(resp.headers.get("Retry-After", "5")) time.sleep(retry_after) raise RateLimitError("触发限流,等待重试") return songs这种带重试和退避的爬虫,才是生产环境里能用的爬虫。答辩的时候这一手就能帮你体现工程素养。
3. 推荐算法核心:TensorFlow建模实战
3.1 算法选型:为什么用协同过滤加Embedding
拿到用户行为数据后,推荐算法怎么选?我建议用协同过滤思路,配合TensorFlow的Embedding层来实现。原因很简单,协同过滤不依赖歌曲的内容特征,只用“用户和物品的交互矩阵”就能做推荐,数据要求低、效果好、可解释性强。
具体实现上,我们用双塔结构,一个塔编码用户,一个塔编码歌曲,最后通过点积或余弦相似度计算匹配得分。用户塔输入用户ID,通过Embedding层转成稠密向量;歌曲塔输入歌曲ID,也通过Embedding层转成稠密向量。两个向量做内积,输出一个得分,然后用真实交互数据做监督训练。
这种方案其实就是YouTube深度推荐系统和DSSM双塔模型的简化版,做毕业设计完全够用,而且给后面讲模型演进留了伏笔。
3.2 模型结构设计与Keras实现
模型结构我用TensorFlow的Keras接口来实现,代码量不大,但每一层要讲清楚作用。
import tensorflow as tf from tensorflow.keras import layers, Model embedding_dim = 32 num_users = 1000 num_items = 5000 user_input = layers.Input(shape=(1,), name="user_id") item_input = layers.Input(shape=(1,), name="item_id") # Embedding层:把离散ID映射成稠密向量 user_embedding = layers.Embedding( input_dim=num_users, output_dim=embedding_dim, name="user_embedding" )(user_input) item_embedding = layers.Embedding( input_dim=num_items, output_dim=embedding_dim, name="item_embedding" )(item_input) user_vec = layers.Flatten()(user_embedding) item_vec = layers.Flatten()(item_embedding) # 点积计算相似度得分 dot_product = layers.Dot(axes=1)([user_vec, item_vec]) output = tf.sigmoid(dot_product) model = Model(inputs=[user_input, item_input], outputs=output) model.compile( optimizer=tf.keras.optimizers.Adam(0.001), loss="binary_crossentropy", metrics=[tf.keras.metrics.AUC()] ) model.summary()这里Embedding层的维度我设成了32维。维度太小模型表达能力不足,维度太大容易过拟合,而且训练变慢。对于本科毕设的数据量,32维是性价比较高的选择。
训练时还有一个细节,就是负样本采样。用户的交互记录里绝大多数都是正样本,如果只拿正样本训练,模型会学成一个“永远预测1”的废物。所以每条正样本要配两三条负样本,从用户没听过的歌里随机抽取生成0标签数据。
def generate_negative_samples(user_id, interacted_items, all_items, num_neg=3): candidate_items = list(set(all_items) - set(interacted_items)) neg_items = random.sample(candidate_items, min(num_neg, len(candidate_items))) return [(user_id, item_id, 0) for item_id in neg_items]这样训练数据就是正负样本混合的,模型才能真正学会区分喜欢和不喜欢。
3.3 训练流程与评估指标
训练时把用户ID、歌曲ID作为输入特征,把行为权重值二进制化后的0/1作为标签。数据集按照8:1:1切分训练集、验证集、测试集,确保同一个用户的行为都被均匀切分,避免随机切分导致验证集里全是训练过的物品。
评估指标上,除了AUC之外,我强烈建议加一个推荐领域最常见的指标:Top-K命中率。简单说就是模型对用户所有歌曲打分后,取分数最高的K首歌曲,看用户实际交互过的歌曲有多少首在其中。这个指标最直观,答辩时给导师展示也最容易理解。
def hit_rate_at_k(model, user_ids, item_ids, labels, k=10): hits = 0 total = 0 for user_id in user_ids.unique(): user_items = item_ids[labels[user_ids == user_id] == 1] if len(user_items) == 0: continue # 为该用户所有未交互歌曲打分 scores = model.predict([...]) top_k_items = pick_top_k(scores, k) hit = len(set(top_k_items) & set(user_items)) hits += hit total += min(k, len(user_items)) return hits / total训练过程我会用model.fit加上EarlyStopping回调,监控验证集损失,连续三个epoch不下降就停止。整套训练在CPU上也能跑,数据集小的情况下几分钟就能结束,不需要为毕设专门去租GPU服务器。
3.4 进一步优化的方向
如果时间和能力允许,推荐模型还有三个可以改进的方向。
第一个方向是从双塔变成三塔,加入歌曲的类别特征、歌手特征,让模型对歌曲的理解更全面。第二个方向是用注意力机制替代简单的Embedding加内积,比如参考DIN模型,让模型根据用户历史行为动态计算候选歌曲与历史的相似度,这个可以结合热搜里提到的Transformer回归案例来理解,本质上都是让特征交互更灵活。第三个方向是图神经网络,用LightGCN建模用户和歌曲的二部图关系,效果更好,但实现复杂度会上一个台阶。
我的建议是,毕业设计到双塔加负采样这一步已经能达到不错的演示效果。后面这些优化选一个方向做出来并写在“展望”里,既显得你有思考深度,又不至于把自己拖进大坑。
4. Django Web端开发与可视化实现
4.1 MTV三层结构如何在项目里落地
Django的MTV模式是很多人的理解难点,其实完全可以对应到实际代码里说清楚。
M层就是models.py里定义的模型类,一个模型类对应数据库里一张表,字段对应列。T层是templates/目录下的HTML模板,负责最终呈现。V层是views.py里的视图函数,它接收用户请求、处理业务逻辑、调用模型数据、渲染模板返回页面。
在我们这个音乐推荐系统里,用户注册、登录、查看推荐、点赞收藏这些操作都能用MTV模式来落地。比如用户点击“获取推荐”按钮后,前端发送请求到Django的/recommend路由,路由交给对应的视图函数处理,视图函数先查数据库拿到当前用户的交互历史,然后调用训练好的TensorFlow模型计算Top 10歌曲,最后把推荐列表渲染到模板页面。
这个过程里没有复杂的架构设计,但在答辩时能把逻辑链讲清楚,本身就是加分项。
4.2 Django项目初始化与数据模型设计
项目初始化这块我直接给命令。django-admin startproject music_system创建工程,python manage.py startapp music创建App,然后在settings.py里注册App并配置数据库。
数据模型是整个系统的基础,我设计了三张核心表。
from django.db import models from django.contrib.auth.models import User class Song(models.Model): song_id = models.CharField(max_length=32, unique=True, verbose_name="歌曲ID") title = models.CharField(max_length=128, verbose_name="歌曲名") artist = models.CharField(max_length=128, verbose_name="歌手") album = models.CharField(max_length=128, blank=True, verbose_name="专辑") duration = models.IntegerField(default=0, verbose_name="时长(秒)") play_count = models.BigIntegerField(default=0, verbose_name="播放次数") cover_url = models.URLField(blank=True, verbose_name="封面") created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = "song" verbose_name = "歌曲信息" class Interaction(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户") song = models.ForeignKey(Song, on_delete=models.CASCADE, verbose_name="歌曲") behavior = models.CharField(max_length=16, verbose_name="行为类型") rating = models.FloatField(default=1.0, verbose_name="行为权重") timestamp = models.DateTimeField(auto_now_add=True) class Meta: db_table = "interaction" unique_together = ("user", "song", "behavior")这里要注意,歌曲表里的song_id对应爬虫抓下来的ID,主键依然用Django默认的自增ID,两者不冲突。Interaction表是用户和歌曲的关联表,推荐模型就是基于这张表来训练的。
4.3 推荐接口与模型推理集成
模型跑完之后会保存成h5文件或者SavedModel格式,Django启动时加载这个文件,然后在视图函数里调用模型做推理。
import numpy as np from django.shortcuts import render from django.contrib.auth.decorators import login_required from .models import Song, Interaction model = None def load_model(): global model if model is None: model = tf.keras.models.load_model("model/music_rec_model.h5") return model @login_required def recommend(request): user = request.user interacted = list(Interaction.objects.filter(user=user).values_list("song_id", flat=True)) # 获取候选歌曲:全部歌曲中按播放量取前200首,再排除用户听过的 candidates = Song.objects.exclude(id__in=interacted).order_by("-play_count")[:200] user_id = user.id results = [] scores = [] for song in candidates: rating = model.predict([[user_id], [song.id]])[0][0] scores.append((song.id, rating)) scores.sort(key=lambda x: x[1], reverse=True) top_song_ids = [sid for sid, _ in scores[:10]] songs = Song.objects.filter(id__in=top_song_ids) return render(request, "music/recommend.html", {"songs": songs})这里有一个性能问题要注意,实时对200首候选歌逐一调用predict效率很低。实际开发中更好的方式是批处理预测,把200个候选歌曲ID一次性灌入模型,一次返回全部评分,速度会快很多。这个优化我在实际项目里也踩过坑,刚开始逐条预测,用户点一次要等好几秒,后来改成批量后基本无感。
candidate_ids = [song.id for song in candidates] batch_user_ids = np.array([user_id] * len(candidate_ids)) batch_item_ids = np.array(candidate_ids) scores = model.predict([batch_user_ids, batch_item_ids]).flatten()4.4 ECharts可视化的两种接入方式
可视化的作用不只是好看,更是让“个性化推荐”效果直观可见。我用ECharts实现了四个图表:歌曲播放量Top10柱状图、用户行为分布饼图、推荐结果命中率折线图、用户听歌时段热力图。
ECharts的接入有两种方式。第一种是页面加载时通过Ajax请求后端接口拿JSON数据,然后在前端渲染图表。这种方式的优势是数据和页面分离,刷图表的前提是后端接口做好了。第二种是把数据直接渲染成Django模板变量嵌入页面,再用<script>标签中的变量初始化图表。这种方式简单直接,适合数据量小、不需要动态刷新的场景。
我通常推荐第一种方式。后续在答辩现场,如果导师想看不同用户的推荐结果,或者想看交互数据变化后图表怎么更新,Ajax接口配合ECharts的动态数据更新能力会很从容。
fetch("/api/statistics", { method: "GET", headers: {"Content-Type": "application/json"} }) .then(res => res.json()) .then(data => { const chart = echarts.init(document.getElementById("playChart")); chart.setOption({ title: {text: "播放量Top10歌曲"}, xAxis: {type: "category", data: data.titles}, yAxis: {type: "value"}, series: [{type: "bar", data: data.play_counts}] }); });4.5 Django静态文件与部署中的常见坑
静态文件问题是Django新手最容易踩的坑,尤其是配好ECharts后突然发现图表不显示、页面全是404。最典型的场景是本地开发一切正常,部署到服务器后style.css、echarts.min.js这些静态资源全部加载失败。
遇到这种问题,先检查三处配置。settings.py里的DEBUG是否设置为False,STATIC_URL是否配置正确,STATICFILES_DIRS是否指向了静态文件所在的目录。开发环境用python manage.py runserver时,Django会自带静态文件服务,但部署阶段DEBUG=False后,这个服务就失效了,需要额外配置静态文件处理逻辑。建议直接用python manage.py collectstatic收集所有静态文件,再由Web服务器提供服务。热搜里“vscode写img标签 在django的static文件中显示不了”就是这类问题的典型代表。
另外强调一件事:settings.py里的ALLOWED_HOSTS必须加上服务器域名或IP,否则部署后访问会直接报错DisallowedHost。
5. 常见问题与排查技巧实录
5.1 requests爬虫频繁429被动怎么办
热搜词里反复出现exceeded retry limit, last status: 429 too many requests,说明很多人都栽在这上面。429的本质是触发了服务端的限流,最常见的诱因就是请求频率过高。
我的排查顺序是这样的。第一步检查代码里有没有设置请求头,UA缺失很容易被反爬策略直接拦截。第二步检查请求频率,有的场景隔0.1秒发一个请求,不触发429才奇怪。第三步看有没有接入代理池或重试机制,如果都没有,那一旦触发429就只能干等着。
有一个实用的小技巧是抓包看看正常浏览时浏览器发送的请求头字段,包括Accept、Accept-Language、Referer等。把这些字段完整复制到requests的headers里,爬虫的“拟人度”会大幅提升。加上随机延时和指数退避后,429问题基本能解决。如果仍然被限,那就不是代码问题了,是数据源的风控等级太高,建议换个公开数据源。
5.2 TensorFlow环境安装与版本冲突
TensorFlow安装是很多新手项目启动阶段就卡住的地方。常见报错是Could not load dynamic library 'cudart64_*.dll'或者No module named 'tensorflow'。
解决方案是严格匹配Python版本和TensorFlow版本。TensorFlow 2.10及以后版本对Python版本要求比较严格,建议使用官方文档指定的Python版本。毕设项目规模根本用不上GPU,直接安装CPU版即可,安装命令是pip install tensorflow-cpu。如果你同时装了多个Python版本,记得检查pip对应的是哪个环境,可以通过python -c "import sys; print(sys.executable)"确认当前Python路径。
如果安装时遇到网络问题导致下载中断,可以更换国内镜像源,pip install tensorflow-cpu -i https://pypi.tuna.tsinghua.edu.cn/simple。装完之后跑一句python -c "import tensorflow as tf; print(tf.__version__)"立即验证。
5.3 推荐结果总是重复或偏向热门歌曲
模型上线后,最常见的现象是推荐列表怎么刷新都差不多,而且全是热门歌曲。原因有两个。
第一个原因是候选集太小。如果你的歌曲库只有一两百首歌,那无论怎么排,推荐结果都逃不出那几首。解决办法是把候选集扩充到上千首,或者从不同分类和来源中均衡采样。第二个原因是模型训练不充分,或者负采样做得太少,导致模型没有学到用户的个性化偏好。检查一下训练日志里的损失曲线,如果验证集AUC始终在0.5附近晃,说明模型和随机猜测差不多,需要调整学习率、增加训练轮次或者补充负样本。
冷启动问题也会导致推荐偏向热门。新注册用户没有行为记录,模型无法计算他的偏好。我的处理方案是给冷启动用户分配“热门兜底推荐”,先推播放量最高的歌曲,等用户产生几次点击行为后,再切换到个性化推荐。这个逻辑虽然简单,但是在答辩时体现系统完整性的重要环节。
5.4 前端图表打不开与接口数据格式不匹配
ECharts图表一直空白不渲染,九成是接口返回的JSON字段名和前端代码里引用的字段名对不上。比如后端返回{"song_name": "晴天"}而前端写的是data.titles,数据取不到,图表自然空着。
排查方法很简单,打开浏览器开发者工具的Network面板,查看接口返回的原始JSON结构,然后跟前端代码逐字段对应。还有一种情况是后端返回的是一个嵌套结构,前端却按扁平结构取值。我建议在Django里把返回数据统一格式化为一个小工具函数,确保接口输出的数据结构稳定,前端拿到的永远是同一套字段。
def response_ok(data=None, message="success"): return JsonResponse({"code": 0, "message": message, "data": data}) def response_error(message="error"): return JsonResponse({"code": 1, "message": message})5.5 常见问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 爬虫频繁429 | 请求频率过快、缺少请求头 | 添加随机延时,伪装浏览器头部,指数退避重试 |
| TensorFlow安装失败 | Python版本与TensorFlow不匹配 | 创建虚拟环境,安装tensorflow-cpu并验证版本 |
| 推荐结果全是热门歌 | 候选集太小、模型训练不足 | 扩充候选集,增加负样本,调整训练参数 |
| 静态文件404 | DEBUG设置为False后静态服务失效 | 配置STATIC_ROOT并运行collectstatic |
| 图表一直空白 | JSON字段名不匹配 | 用浏览器开发者工具对照接口返回字段 |
| 训练时内存溢出 | 一次加载全量数据 | 使用tf.data.Dataset批量读取 |
| 部署后无法访问 | ALLOWED_HOSTS未配置 | 在settings.py中添加服务器域名或IP |
写在最后的个人建议
整套系统从零开发到稳定运行,我建议你按照“爬虫入库 → 训练模型 → Django展示 → 可视化优化”这条主线推进,先把最小闭环跑通,再逐步加功能。以我自己的经验来看,很多同学喜欢一上来就把界面做得很炫,结果数据全是写死的,模型也跑不通,最后只能靠表演糊弄过去,这样反而最容易被答辩导师拆穿。
不如先把“爬虫抓数据、TensorFlow训练、Django推荐”这三件事老老实实跑通,哪怕界面朴素一点,你也能理直气壮地从数据流向讲到算法原理。这个项目后续还有很多可以扩展的空间,比如把模拟用户行为换成真实埋点采集、用Redis缓存排名结果、把双塔模型升级成注意力机制版本。任何一个方向做出来,都是论文里扎实的一章。
最后再分享一个小技巧:答辩演示前,把推荐的Top10结果和用户听歌历史打出来贴在PPT旁边。导师看到系统确实是根据历史行为给出的推荐,而不是随机凑数,对你的项目可信度会立刻上一个台阶。毕设这东西,做完不难,做扎实才值钱。