又到了一年毕业季,每年这个时候我都会收到大量私信,问得最多的就是“毕设选题怎么选”“网上那些源码能用吗”。如果你打开过这个标题——Python得物鞋类数据可视化大屏与协同过滤推荐系统,你会发现它同时踩中了电商数据分析、推荐算法、可视化大屏这三个毕业设计最热门的方向。这个项目用 Django 框架把数据采集、数据分析、协同过滤推荐算法、ECharts 大屏展示串成了一条完整的链路,既不是那种纯 CRUD 的“管理系统”,也不是纯理论堆砌的算法论文,而是一个真正能跑、能讲、能演示的综合性系统。这篇文章我就以实际开发者的视角,把这个项目从设计思路到落地实现的每一个环节掰开揉碎讲清楚,包括你大概率会踩的坑、答辩时老师会追问的点,以及我自己的实操心得。
1. 项目整体设计与需求拆解
1.1 标题背后隐含的三大核心需求
这个标题看起来很长,但拆解之后其实就三个关键词:数据、算法、展示。数据对应“得物鞋类商品数据”,算法对应“协同过滤推荐算法”,展示对应“数据可视化大屏”。这三者在实际系统中不是孤立的,而是一条完整的数据流水线:先有数据,才能做分析;分析结果用来驱动推荐;大屏则把分析和推荐的结果直观地呈现给用户。
先说数据端。得物作为一个潮流电商平台,鞋类商品天然带有丰富的结构化特征:品牌、款式、配色、发售价格、市场交易价、销量、热度、评价标签等。这些字段既是可视化的素材,比如按品牌统计销量、按价格区间分布、看热度 Top 榜单,也是推荐算法的基础特征,比如用户浏览过 AJ1 高帮,系统就能根据颜色、品牌、价位等维度找到相似商品。所以这个项目的数据层并不需要做得特别“重”,但字段设计一定要有前瞻性,否则后面做算法特征工程时你会想回去重爬。
再说算法端。协同过滤是目前推荐系统里最经典、最容易落地、也最容易被答辩老师认可的算法。它不需要复杂的深度学习环境,不需要 GPU,一个普通的笔记本跑 sklearn 或者自己手写余弦相似度计算都能应对。它分为基于用户的 UserCF 和基于物品的 ItemCF,在电商场景下 ItemCF 通常更实用,因为物品之间的相似关系相对稳定,用户的兴趣漂移对推荐结果影响更小。这个项目里,你完全可以把两种算法都实现,然后做对比,这在答辩时是非常加分的亮点。
最后说展示端。数据可视化大屏在很多毕设里只是“有一个页面放几个图表”,但真正能打的大屏项目需要考虑数据怎么来、图表怎么刷、布局怎么排、异常值怎么处理。用 Python 的 Django 做后端接口,用 ECharts 做前端图表渲染,是目前性价比最高的组合之一。Django 自带的 ORM 和 Admin 后台可以帮你快速搭建数据管理能力,ECharts 的文档和社区生态非常成熟,各种大屏模板随手就能改。
1.2 为什么选 Django + ECharts 这套组合
先说说 Django。现在 Python 后端框架基本就是 Django 和 Flask 二分天下,很多人纠结选哪个。我的判断很直接:如果你的项目涉及数据模型较多的管理系统、需要后台管理页面,那 Django 是更稳的选择。它自带 ORM、Admin、Session、CSRF、迁移工具,项目结构在创建时就是规范的 MTV 模式,你不用在“怎么组织代码”上花太多心思,可以把精力集中在算法和可视化这两个真正的核心点上。Flask 的灵活性虽高,但数据库迁移、后台管理这些都要自己拼装,对毕设项目来说反而容易把时间耗在无关紧要的地方。
再来说 ECharts。它是百度开源的一个 JavaScript 可视化库,图表类型极其丰富:折线图、柱状图、饼图、雷达图、地图、词云、漏斗图、关系图,基本覆盖了大屏展示的所有需求。它最大的优势是配置项高度结构化,一个 option 对象就描述了图表的一切,方便后端动态生成或前端根据接口数据动态组装。而且它支持 canvas 和 svg 双渲染引擎,大数据量下的表现也够用。近两年 ECharts 还在持续更新,生态很活跃,遇到问题在社区基本都能搜到解决方案。
还有一个现实原因:这套组合对“毕业设计”这个场景特别友好。你不需要额外装数据库中间件以外的任何重型组件,Django 默认就用 SQLite,ECharts 就是一个 JS 文件,前后端分离也好、不分离也好,都能快速跑起来。部署演示时只要一台电脑、一个浏览器,不需要申请服务器和域名,演示成本和故障概率都降到了最低。
提示:在选型之前一定要先想清楚“演示时如果断网怎么办”。如果大屏页面的 ECharts 依赖 CDN 在线加载,现场的稳定性就不可控。建议把 echarts.min.js 下载到本地放到 static 目录下,这是一种低成本但极其有效的稳定性保障。
1.3 系统功能模块划分
一个完整可运行的系统,我会把它划分成四个层次:
- 数据采集层:负责从得物平台获取鞋类商品的基础信息、价格、销量数据,以及模拟构造用户行为数据。这层决定了整个系统能不能“转”起来。
- 数据存储层:用 Django ORM 建表,核心表包括商品表、品牌表、用户表、用户行为表(浏览/收藏/购买)、推荐结果表。
- 业务逻辑层:封装数据分析逻辑和协同过滤推荐算法,提供标准的内部接口,供视图层调用。
- 可视化展示层:包括数据大屏页面和推荐结果页面,通过 Ajax 请求后端 JSON 接口,用 ECharts 完成渲染和定时刷新。
这四层说起来简单,但每一层都有很多细节,接下来我按照实际开发顺序,把每一层的设计和实现展开细讲。
2. 数据采集与预处理实操
2.1 模拟用户行为数据的构造与字段设计
真正要爬取得物全量数据是很费劲的,反爬、签名、风控、账号限制都很麻烦。在毕设场景下,我建议采用“少量真实 + 大量模拟”的策略:先用爬虫拿到几百条真实商品数据作为样本和可视化素材,然后基于真实商品列表构造一批模拟的用户行为数据(浏览、收藏、加购、购买)。这样既保证了数据的真实性,又避免了在反爬上消耗太多时间。
商品表的关键字段可以这样设计:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int | 主键 |
| title | varchar | 商品名称 |
| brand | varchar | 品牌 |
| price | decimal | 当前售价 |
| original_price | decimal | 发售价 |
| sales | int | 累计销量 |
| heat | int | 热度值 |
| color | varchar | 主要配色 |
| release_date | date | 发售日期 |
| image_url | varchar | 图片地址 |
| tags | varchar | 标签(如“联名”“限量”“篮球鞋”) |
用户行为表则建议用这样的结构:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int | 主键 |
| user_id | int | 用户ID |
| item_id | int | 商品ID(外键关联商品表) |
| behavior_type | varchar | 浏览/收藏/加购/购买 |
| score | float | 行为评分(浏览=1、收藏=2、加购=3、购买=4) |
| create_time | datetime | 行为发生时间 |
这里有一个非常关键的设计点:协同过滤算法需要用户对物品的评分矩阵,但真实场景下电商平台很少让用户打分,更多的是隐式反馈。所以要把浏览、收藏、加购、购买这些行为映射成不同的分值。浏览给 1 分,收藏给 2 分,加购给 3 分,购买给 4 分,用加权方式把隐式反馈转成显式的评分数据。这个思路在推荐系统业界很常见,答辩时解释清楚会显得你确实理解了推荐系统的本质。
2.2 爬虫实现与合规边界
虽然我建议“少量真实数据”,但少量也总得有。爬取得物数据的方式,一般是通过分析移动端接口或者 Web 端接口拿到 JSON 数据。常规的操作是:用 requests 库带上 User-Agent、Referer、Cookie 请求商品列表页接口,解析返回的 JSON,提取上述字段,然后写入数据库。
爬虫的代码框架大致如下:
import requests import json import time def fetch_product_list(page): url = "https://api.dewu.com/xxx/product/list" headers = { "User-Agent": "Mozilla/5.0 (Linux; Android 10)", "Referer": "https://www.dewu.com/", "Cookie": "your_cookie_here" } params = {"page": page, "categoryId": "shoes"} resp = requests.get(url, headers=headers, params=params, timeout=10) if resp.status_code == 200: data = resp.json() return data.get("data", {}).get("list", []) return [] def parse_item(item): return { "title": item.get("title"), "brand": item.get("brandName"), "price": item.get("price"), "original_price": item.get("originalPrice"), "sales": item.get("sales"), "heat": item.get("heatScore"), "image_url": item.get("picUrl") } for page in range(1, 6): items = fetch_product_list(page) for item in items: parsed = parse_item(item) # 写入数据库,省略 time.sleep(2)这里有几个实战经验:第一,频率要低,建议两次请求之间至少间隔 2 秒,否则很容易触发风控;第二,要用完整的 Cookie,很多接口在未登录状态下返回的数据是不完整的;第三,要加失败重试机制,因为反爬策略会动态变化,偶发 403 是常态,重试三次基本都能过。
但我也要强调一点,爬虫只是毕设的一部分,而且合规性很重要。建议只爬取少量公开数据用于学习和演示,不做商业用途,不长期高频抓取,并且在文档里明确注明数据用途。如果条件允许,也可以先在网上找一些开源的鞋类商品数据集做预演,等系统跑通后再用爬虫补充“新鲜数据”。这样既保证了项目进度,又规避了不必要的法律风险。
2.3 数据清洗与特征加工
数据拿到手之后不能直接入库,因为真实数据大概率有各种问题:价格字段可能是字符串、销量可能是空值、品牌命名不统一(“Nike”“耐克”“Nike 耐克”都出现)、图片链接是相对路径等。我的做法是写一个清洗函数,统一在入库前处理:
import re def clean_price(raw): if not raw: return 0 # 去掉货币符号和千分位逗号 return float(re.sub(r"[¥,]", "", str(raw))) def clean_brand(raw): # 统一品牌名映射表 mapping = {"Nike": "Nike", "耐克": "Nike", "AJ": "Air Jordan"} return mapping.get(str(raw).strip(), str(raw).strip()) def clean_sales(raw): # 部分销量显示为 "1000+" 或 "1.2万" text = str(raw).strip() if text.endswith("+"): return int(text[:-1]) if text.endswith("万"): return int(float(text[:-1]) * 10000) return int(float(text))除了清洗,特征加工也很重要。价格分桶是可视化和算法都要用到的:199 元以下属于低价款、200-599 元属于入门款、600-1499 元属于中端款、1500 元以上属于高端款。有了价格区间字段,大屏上可以直观展示不同价位段的商品数量分布,推荐算法在计算相似商品时也可以优先考虑同价位段的候选集。颜色、标签这些字段我还做了独热编码处理,供算法直接使用。
注意:价格分桶的边界值是可以自己定义的,但要保证在文档里说明清楚。老师在答辩时如果问“为什么 600 元是中端的分界线”,你可以解释为参考了该平台商品的主流价格中位数和消费分层,这是合理的业务逻辑说明,比随口说“猜的”要有说服力得多。
3. 数据可视化大屏的核心实现
3.1 大屏页面布局与主题风格设计
大屏项目的观感直接影响答辩现场的演示效果。很多学生把大屏做成了“一个页面塞满了表格”,这非常减分。真正的大屏讲究的是信息层级和视觉动线:核心数据放在中心,辅助数据放在两侧,指标卡放最顶部。
我建议的布局是 1920x1080 的栅格:顶部是标题和当前时间,中间一行放三个 KPI 指标卡(今日浏览量、在售商品数、平台热度指数),中间主体区域左侧放“品牌销量 Top10”和“价格区间分布”,中间放“销量趋势折线图”和“热门商品词云”,右侧放“地区销售地图”和“推荐 Top 商品列表”。这样的布局在视觉上有中心焦点、有数据层级,演示时不用讲解就能让观众看懂逻辑。
主题风格我建议走深色科技风,背景用深蓝或黑色渐变,图表配色统一用亮蓝、橙色、绿色三色体系,字体用无衬线字体,数字可以用带有一定科技感的数码字体。ECharts 里内置了 dark 主题,可以直接引入,然后再根据自己的配色偏好微调。这样成品效果会比默认主题高级一个档次。
3.2 Django 后端数据接口设计
大屏的所有数据都不能直接写死在前端,必须通过 Django 接口动态获取,这是体现“前后端分离”思维的关键。我在 Django 项目里按功能模块划分了 URL 和视图,比如:
- /api/dashboard/summary——返回 KPI 指标数据
- /api/dashboard/brand_sales——返回品牌销量 Top10
- /api/dashboard/price_distribution——返回价格区间分布
- /api/dashboard/sales_trend——返回销量趋势
- /api/dashboard/hot_words——返回热门词云数据
- /api/dashboard/recommend?user_id=1——返回当前用户的推荐商品
视图层用 Django ORM 做聚合查询,序列化成 JSON 返回。一个典型的品牌销量接口实现大概是这样的:
from django.http import JsonResponse from .models import Product def brand_sales(request): rows = ( Product.objects .values("brand") .annotate(total_sales=Sum("sales")) .order_by("-total_sales")[:10] ) data = { "brands": [r["brand"] for r in rows], "sales": [r["total_sales"] for r in rows], } return JsonResponse(data)这里有一个优化点:如果前端的图表需要数据实时刷新,每一次请求都实时聚合数据库会导致大量无谓的重复计算。对于毕设项目数据量不大,其实无所谓,但我更建议在模型里加一层缓存,比如每 5 分钟把聚合结果写入 Django Cache 或一张统计表里,前端请求的是缓存数据。这虽然不是必须的,但写在文档里、讲在答辩时,能够体现你对性能问题的敏感度,属于低成本高收益的优化思路。
3.3 ECharts 图表选型与配置细节
大屏上最常用的图表包括:柱状图(品牌销量对比)、饼图/环形图(价格区间占比)、折线图(销量趋势)、词云(热门标签)、地图(地区销量分布)。每一类图表的 ECharts 配置都有不同的技巧。
柱状图的关键在于“由大到小排列”和“突出前几名”。销量 Top 榜如果前三名和其他名次有显著差异,可以用不同的颜色标记 Top3,比如前三名用亮橙色,其余用蓝色,一眼就能看出头部效应。实现上就是用 visualMap 组件或者 itemStyle 的颜色回调函数:
color: function(params) { if (params.dataIndex < 3) return '#ff9900'; return '#4096ff'; }词云在大屏里是非常讨喜的图表。ECharts 本身有词云扩展插件 echarts-wordcloud,需要单独引入。热门词来源于商品标题中的高频词汇,比如“AJ”“空军一号”“联名”“限量”“篮球”,把标题做分词统计,取 top 50 词渲染成词云,字越大代表出现频率越高。这种图表视觉冲击力强,演示效果很好。
地图组件如果需要展示不同地区的销量,我在数据字段里加了 province 字段,通过 GeoJSON 注册中国地图。为了简化,我实际用的是 ECharts 自带的 china 地图数据(需要额外下载),如果你的 ECharts 版本不支持,也可以换成柱状图按省份排名展示。从演示效果上看,两者不分高低,关键是数据要真实、有梯度,不要所有省份数值都一样。
3.4 大屏动态刷新与数据推送方案
大屏的“动态感”是演示时的加分项,实现方式有两种:轮询和 WebSocket。
轮询是最简单的方案,在前端用 JavaScript 的 setInterval 定时器每 5 秒向后端接口请求一次数据,更新图表配置。代码如下:
setInterval(function() { fetch('/api/dashboard/sale_trend') .then(res => res.json()) .then(data => { trendChart.setOption({ series: [{ data: data.values }] }); }); }, 5000);这种方式够用且稳定,但效率略低,适合数据量小、不需要即时推送的场景。如果你的项目想展示更高级的技术点,可以在标题范围内加入 WebSocket 推送:后端定时从数据库计算出最新销售额后,通过 Django Channels 推送到前端页面,前端收到消息后只更新对应图表。
WebSocket 的架构相比轮询要复杂一些,涉及异步消费者、通道层、前端 WebSocket 连接管理。如果你时间充裕、想在简历上多一个亮点,我建议实现 WebSocket 方式,但做好降级方案:在 WebSocket 断开时自动切换回轮询。在答辩现场,网络环境往往不稳定,如果你只有 WebSocket 一种方式,一旦通道断开会很难收场。
4. 协同过滤推荐算法落地
4.1 UserCF 与 ItemCF 的原理对比与选型
协同过滤的核心思想并不复杂:如果两个用户对若干商品的评价相似,那么他们对新商品的评价大概率也会相似,这是 UserCF 的逻辑;如果两个商品被同一群用户喜欢,那么它们在用户心中是相似的,喜欢其中一个的用户也会喜欢另一个,这是 ItemCF 的逻辑。
实操中你只需要记住一个选型原则:用户数量远大于商品数量时,用 ItemCF 更划算。因为在毕设项目里,商品数量不过几百件,但模拟的注册用户可能有几千上万人。用 ItemCF 构建的是商品-商品相似度矩阵,矩阵规模是商品数乘以商品数,计算量远远小于 UserCF 的用户-用户矩阵。而且在电商场景下,商品相似关系比用户相似关系更稳定,新增的交互数据不会立刻改变商品之间的相似度结构,不需要频繁重算相似度矩阵,对系统性能更友好。
但在新人菜鸟阶段,我建议你两个都实现,然后做个对比实验。原因很务实:第一,毕设论文需要对比分析,只有一种算法显得单薄;第二,两个算法实现的代码量并不大,核心也就是相似度计算和 TopN 排序,共用一套评分矩阵即可。实现一遍之后你对协同过滤的理解会非常扎实,答辩时老师无论从哪个角度追问,你都有真实经验打底。
4.2 评分矩阵构建与相似度计算详解
评分矩阵是协同过滤的地基。在 Python 里处理稀疏矩阵,普通字典嵌套就可以满足小数据量的需求,但更专业的方式是用 pandas 构造透视表,再用 numpy 做向量运算。
我的做法是:先从用户行为表里把 user_id、item_id、score 读取出来,构造一个 user-item 评分矩阵,矩阵的行是用户,列是商品。模拟用户的评分区间是 0 到 1(经过归一化的行为分值),没有行为的格子补 0。当然,用全 0 填充并不是最优解,更严谨的协同过滤实现会考虑置信度加权,比如用户浏览次数多说明他真的喜欢,但这是加分项,不一定要做。
相似度计算我用的是余弦相似度,这是最经典、最好解释的度量方式:
import numpy as np def cosine_similarity(vec_a, vec_b): dot = np.dot(vec_a, vec_b) norm_a = np.linalg.norm(vec_a) norm_b = np.linalg.norm(vec_b) if norm_a == 0 or norm_b == 0: return 0 return dot / (norm_a * norm_b)为了提升计算效率,我提前把评分矩阵存成了 numpy 二维数组,商品之间的相似度计算用矩阵乘法和归一化一步完成。例如计算 ItemCF 的相似度矩阵,核心代码可以非常简洁:
# rating_matrix: (user_num, item_num) # 商品相似度矩阵 = 转置评分矩阵 点乘 评分矩阵,再按列归一化 item_sim = np.dot(rating_matrix.T, rating_matrix) # 归一化 item_sim = item_sim / (np.linalg.norm(item_sim, axis=0, keepdims=True) + 1e-8)这段代码当数据量小的时候跑得飞快,几百个商品的相似度矩阵几乎瞬间算完。把计算结果缓存到本地文件或者内存里,供推荐接口直接调用,避免每来一个请求就重算一次矩阵。
4.3 推荐 TopN 与打分公式
有了商品相似度矩阵之后,给用户推荐商品就是对用户已经产生行为的商品做加权聚合排序。ItemCF 经典的打分公式是:
def recommend_for_user(user_id, top_k=10): # 获取用户行为商品及评分 user_items = get_user_items(user_id) # 获取候选商品 scores = {} for item_id, score in user_items.items(): sim_list = item_sim[item_id] # 相似商品及相似度 for sim_item_id, sim_val in sim_list: if sim_item_id in user_items: continue # 排除用户已经买过/浏览过的商品 scores[sim_item_id] = scores.get(sim_item_id, 0) + sim_val * score # 按分数排序取 topN ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:top_k] return [item_info(item_id) for item_id, _ in ranked]注意两个细节。第一,推荐时要过滤掉用户已经有过行为的商品,否则推荐系统会一直把用户买过的东西推给他,这在逻辑上说不通。第二,打分公式用的是“相似度 × 用户行为分值的累加”,而不是简单的相似度求和。这样用户购买过的商品(权重 4)会比浏览过的商品(权重 1)对推荐结果有更大的影响,推荐结果更贴合用户的真实兴趣。
另外,为了提高推荐质量,我加了品牌过滤和价格区间过滤:候选商品里,如果与用户历史行为商品的品牌完全不一致、且价格区间不同,可以在排序时给一个小幅度的降权而不是直接剔除,这样既保证推荐内容的个性化,又不至于让推荐列表太单一。这些“小规则”在论文里都可以展开成“混合推荐策略”的章节。
4.4 冷启动问题的兜底策略
协同过滤有一个天生缺陷:新用户没有行为数据、新商品没有交互记录,算法无法给出有效推荐。这在毕设评审中几乎是必问的问题,所以必须有兜底方案。
新用户冷启动很简单,就是“热门商品兜底”。当系统检测到当前用户没有存储任何行为记录时,直接从商品表按销量和热度综合排序返回 TopN 热门商品。这样用户在页面看到的不再是“推荐失败”,而是“热销榜”,体验上没有任何违和感。
新商品冷启动则结合内容特征做相似匹配:新商品虽然没有交互记录,但它的品牌、价格区间、标签、配色都是可用的,可以从这些特征和已有商品计算内容相似度,找最相近的“老商品”,再把喜欢老商品的用户群体视作新商品的潜在受众。这其实已经接近混合推荐了——协同过滤做主推,基于内容的推荐做补充。从项目角度讲,这能显著扩展你的论文技术栈,也能让系统在面对稀疏数据时更健壮。
5. 系统集成与部署实践
5.1 Django 项目结构规划
一个规范、可维护的项目结构,不仅让你自己写代码舒服,也能让审代码的老师眼前一亮。我的推荐结构如下:
shoes_recommend/ ├── manage.py ├── config/ │ ├── settings.py │ └── urls.py ├── apps/ │ ├── products/ │ │ ├── models.py │ │ ├── views.py │ │ ├── urls.py │ │ └── services.py │ ├── dashboard/ │ │ ├── views.py │ │ └── urls.py │ └── recommend/ │ ├── algorithms.py │ ├── views.py │ └── urls.py ├── static/ │ ├── css/ │ ├── js/ │ └── echarts/ ├── templates/ │ └── dashboard.html └── data/ └── products.csvapps 目录下按业务模块拆分了三个子应用:product 管商品数据、dashboard 管大屏接口、recommend 管推荐算法。services.py 放数据清洗和业务逻辑层代码,algorithms.py 放协同过滤算法,这样视图层很薄,算法层与路由层解耦。以后你想换算法、换图表,只需要改动对应的模块,不需要动其他代码。
5.2 前后端数据联动交互
大屏页面上不仅要看数据图表,还应该能和推荐系统联动,这样项目的整体感会更强。我在页面左侧放了一个用户选择器(下拉框,里面是模拟出的用户 ID),切换用户后,右侧的“为你推荐”区域会通过 Ajax 请求该用户的推荐接口,渲染对应的商品卡片列表(封面图、商品名、价格、推荐理由)。
推荐理由可以这样生成:用 ItemCF 推荐出来的商品,找出用户历史上行为评分最高的三个商品,把“因为你浏览了 A、B、C,所以推荐 D”这样的逻辑作为推荐说明展示出来。这个设计在演示时效果极好——评审老师能直观感受到“推荐不是随机给的,而是有依据的”,一下子把项目的可解释性拉高了一个档次。
大屏与推荐的联动实现起来不算复杂,无非是在 dashboard.html 里监听下拉框的 change 事件,调用对应的推荐接口,然后动态渲染 HTML 卡片。这部分没有复杂的组件库依赖,原生 JavaScript 或 jQuery 都行。
5.3 环境配置与本地部署注意要点
本地部署常见的问题集中在环境版本和静态资源上。Django 版本建议选 4.x 或 5.x 稳定版,Python 用 3.9 以上,数据库用 SQLite 就够了,如果老师要求 MySQL,再迁移也很方便,只需要改 settings 里的 DATABASES 配置和装好驱动。
一个常见的坑是:前端页面能打开,但图片加载不出来。原因往往是静态资源路径配置不对。检查 settings.py 里的 STATIC_URL、STATICFILES_DIRS,确保 static 目录路径正确:
STATIC_URL = "/static/" STATICFILES_DIRS = [BASE_DIR / "static"]另一个坑是跨域问题。如果你开发时前后端分离,前端跑在 8000 端口,后端接口跑在 8080 端口,浏览器会拦截跨域请求。解决方式很简单:开发时用 Django 的模板直接渲染页面,把请求发到同源的 /api 路径下,不分离部署;如果必须分离,安装 django-cors-headers 并在 settings 里配置白名单。对毕设来说,我更推荐模板渲染的“伪分离”方式,省去跨域和打包的麻烦,演示时也更稳。
启动方面,本地演示用python manage.py runserver就完全够用。如果你想把系统放到服务器上展示,可以用 gunicorn + nginx 的方式部署,但这属于加分项,不是必选项。
6. 常见问题与排查技巧实录
6.1 高频故障与排查方案表格
开发过程中我遇到了不少问题,有些问题几乎每个来问我的人都会遇到。我整理成了表格,方便你直接对照排查:
| 问题现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| 大屏页面打开后图表空白 | ECharts 资源未加载/容器高度为0 | 打开浏览器控制台看报错 | 给图表容器设置固定高度(如 400px),确认 echarts.min.js 已加载 |
| 接口返回中文乱码 | Django 未配置 UTF-8 响应 | 检查响应头 Content-Type | 用 JsonResponse,它默认使用 UTF-8;手动 HttpResponse 时设置content_type="application/json;charset=utf-8" |
| 推荐结果为 null 或空列表 | 用户无行为数据 | 检查用户行为表是否有该用户记录 | 走热门榜兜底逻辑,并打印日志区分“冷启动”和“算法异常” |
| 爬虫请求被拒绝 | 请求频率过高/Cookie 失效 | 打印响应状态码和响应体 | 降低频率、更新 Cookie、增加随机延迟 |
| 图表数据刷新时整个页面闪动 | 每次都重绘了整张大屏 | 检查 setInterval 中是否重新初始化图表 | 图表实例化一次,之后只调用 setOption 更新数据 |
| 数据库查询慢/页面卡顿 | 没有索引且数据量增大 | 查看 Django Debug 工具栏 | 在 user_id、item_id、behavior_type 上建联合索引 |
6.2 答辩现场的高频提问与应对
答辩环节是毕设真正的“决赛圈”,老师的问题往往集中在项目真实性和技术深度两个维度。提前想清楚答案,现场就不容易慌。
“你的推荐算法是实时计算的吗?”——这个问题要小心答。如果回答“是”,老师可能追问实时计算的时机和性能问题;如果回答“不是”,老师可能觉得你实现得太简单。比较稳的说法是:“相似度矩阵是离线计算的,因为物品之间的相似关系是相对稳定的,每天更新一次即可;在线推荐只是查矩阵做加权排序,响应在毫秒级。”这样既承认了离线计算的成熟做法,又体现你对性能和时效性有清楚的认识。
“数据和训练集从哪里来的?数据量多少?”——如实回答:数据来源于公开平台的学习研究型爬取,规模在几百条商品、几千条模拟行为数据。重点是强调“算法流程是完整跑通的”,而不是“数据量有多大”。如果老师质疑数据量小,你可以说“这只是一个原型验证项目,生产环境的数据量虽然更大,但算法原理一致,且可以通过缩放横向扩展”,这个回答逻辑上是自洽的。
“为什么不用深度学习做推荐?”——这个问题不要慌。答法很清晰:深度学习模型需要大规模数据集才能发挥优势,在毕设的数据量级下容易过拟合;协同过滤算法简单、可控、可解释性强,作为教学演示和系统原型是更合适的选择。如果你还做了 UserCF 与 ItemCF 的对比实验,甚至可以主动展示两个算法的指标对比表格,把问答变成你的加分表演时间。
6.3 开发周期与避坑建议
最后分享一下我自己的实操心得。如果从零开始做这个项目,我建议按“6 周”来分配:第 1 周做需求分析、数据库建模和爬虫,第 2 周做数据清洗和 Django 后端基础接口,第 3 周做 ECharts 大屏,第 4 周做协同过滤算法和推荐接口,第 5 周做联动联调和页面美化,第 6 周写论文和准备答辩材料。
不少学生会犯一个错误:一开始就花大量时间研究爬虫,想把得物的数据爬得非常全,结果被反爬卡了两周,后面算法和大屏的时间被严重压缩。我个人的经验是:数据先保证“有”,再保证“多”。先用 50 条真实数据加 500 条模拟数据把整条流水线跑通,之后再逐步加数据量。系统能跑起来、逻辑自洽,永远是第一位的,数据量可以后面补。
还有一点要提醒:代码里尽量多写注释,尤其是算法的相似度计算、推荐打分公式、数据清洗规则这三处。因为论文里需要贴核心代码和解释逻辑,如果你写代码时没注释,到写论文时你可能已经忘了当初为什么这么写。我现在回头看我以前的项目,最感激的就是当时写的一行行注释。
值得一提的还有“大数据”“大模型”“agent”这些热词。这个项目的主体是数据可视化与协同过滤推荐,但在论文里可以适度结合这些概念,比如讨论未来可以引入大模型做商品描述的语义向量,用更丰富的文本特征增强推荐效果,或者引入 agent 做自动化的数据分析和报表生成。写“展望”的时候把这些概念结合进去,会显得你关注前沿,同时不会影响核心内容的完成度。
另外我在实际部署中还发现一个小技巧:大屏演示时最好准备一个静态数据的降级方案。万一现场网络不好导致接口请求失败,至少保证页面框架和图表能静态展示出来。具体做法是前端 JavaScript 里写一个 fallback 数据对象,当 fetch 接口抛异常时自动用内置的示例数据渲染。这个细节看起来不起眼,但在现场演示时能救你一次。
这个项目做完之后,你手里就有了四样实际可用的资产:一套能跑的后端系统、一套完整的数据可视化大屏、一套可解释的推荐算法、一份可以写进简历的项目经历。我建议你在此基础上再扩展一个方向,比如把推荐结果的点击反馈收集起来,做成一个简单的 A/B 测试对比页面,这会让整个项目从“毕业设计”直接跃升为“一个有闭环的推荐系统原型”。