十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Django与协同过滤的新疆特产推荐系统设计与可视化实践

基于Django与协同过滤的新疆特产推荐系统设计与可视化实践 每年到了毕业设计季总能在各种群里看到“求推荐系统毕设”的消息。其实推荐系统这个方向本身很适合做本科或硕士的毕设课题它既有算法层面的东西可以写、能讲出深度又有数据和界面层面的东西可以展示、能给出直观的演示效果再加上Django这种成熟的Web框架工作量可控、答辩演示也好看。今天我就来把这个“基于Django数据可视化的Python新疆特产推荐系统”完整地拆开聊一聊从整体设计到核心技术点再到落地实操时的各种坑和心得一次性说清。这个项目能做什么简单讲就是一套完整的电商导购系统游客可以看到新疆特产的商品列表和可视化统计面板注册登录后可以浏览商品详情、对商品打分、收藏、加入购物车并模拟下单系统会根据你的历史行为生成个性化商品推荐。管理员端则负责商品管理、用户管理、订单处理以及查看全站的数据统计图表。它适合以下几种人参考一是正在选毕业设计题目、想找“有算法、有可视化、有完整业务闭环”项目的同学二是想系统学习Django并想做一个完整项目来练手的Python学习者三是想研究推荐系统基础原理但不想只跑冰冷的离线实验、希望看到真实Web落地的朋友。从技术栈来看项目核心是Django框架前端使用BootstrapjQueryECharts做页面和数据可视化数据库采用MySQL或SQLite推荐模块用Python实现基于物品的协同过滤算法和基于内容的推荐策略。整个架构不复杂但麻雀虽小五脏俱全下面我按模块来拆。1. 项目整体设计与技术选型思路1.1 为什么选Django而不是Flask或前后端分离架构先聊框架选型。现在很多同学一上来就追求前后端分离VueSpringBoot或VueDjango REST Framework觉得这样才“高级”。但做毕设或者中小型系统最怕的就是架构过度设计把大量时间耗费在联调、跨域、权限认证这些和业务无关的事情上。Django的最大优势就是“全家桶”ORM自带Admin后台自带表单和验证自带Session认证自带模板引擎也自带。这意味着你不需要像Flask那样去拼装各种第三方库也不用自己在Vue项目里封装axios请求、配置路由守卫。用Django的MTV模式视图函数直接渲染模板数据通过上下文传给前端前后端不分离整个请求链路由Django一站式处理开发调试都省心很多。有人说那可视化图表怎么办前后端不分离就不能用ECharts了吗完全不影响。前端模板里正常引入ECharts的JS文件视图函数提供返回JSON数据的接口前端通过Ajax异步请求这些接口拿到数据再渲染图表。换句话说“数据可视化”和“前后端分离”本来就是两件事Django做后端数据接口ECharts负责前端渲染两者通过JSON对接配合得非常顺。另外Django自带的Admin后台对毕设来说是一把利器。你可以很快速地把商品、分类、订单等都注册到Admin里哪怕你还没来得及写管理页面的前端老师演示时也能直接登录Admin后台查看数据尤其是加上 django-import-export 这种插件还能直接导入导出Excel数据。对时间紧张的同学来说能省下好几天的开发量。1.2 推荐系统的算法选型协同过滤为主但不是全部推荐算法是这类系统的核心亮点也是答辩时老师最关注的部分。很多同学听到“推荐系统”就直接想上深度学习模型、Graph Neural Network之类。但说实话在一个没有海量用户行为数据的毕设项目里复杂的深度模型根本不是最优选择首先是数据量撑不起训练其次模型不可解释性很强答辩时你很难讲清楚“为什么给这个用户推荐了这几款商品”。当前项目采用的方案是两类算法的组合第一类是基于物品的协同过滤Item-based Collaborative FilteringItemCF。核心思想很简单如果用户A喜欢商品X同时用户B也喜欢商品X并且用户B还喜欢商品Y那么系统就可以把商品Y推荐给用户A。具体实现时不需要管商品自身的属性只看用户行为评分、收藏、购买之间的关联。它的优点在于不需要商品的内容信息只要用户行为数据够多就能挖掘出“喜欢A的人也喜欢B”这种隐性的关系。第二类是基于内容的推荐Content-based Recommendation。这个需要给商品打上属性标签然后通过计算用户历史喜欢商品的标签特征与候选商品标签特征的相似度来生成推荐。比如用户经常浏览“和田玉枣”和“若羌灰枣”那这些商品的标签里有“红枣”“若羌”“和田”等关键词系统就会优先推荐其他标签相似的干果类特产。它的好处是能解决协同过滤的“冷启动”问题——新用户没有行为数据时至少可以根据用户主动选择的偏好标签先给出初版推荐。我的建议是以基于物品的协同过滤为“主力”同时加入基于内容的标签相似度作为“替补”。这样组合起来你既能讲清楚协同过滤的原理也能说明白冷启动问题的解决思路整个推荐模块在算法逻辑上有层次、有取舍在答辩时非常有讲述空间。1.3 数据可视化模块的设计思路提到“数据可视化”很多毕设只是简单画两三个图表就交了差。但如果想让项目有“大数据”的感觉可视化一定要成体系。这个项目里我设计了一整套可视化面板首页数据看板展示商品总数、用户总数、订单总数、评论总数等核心指标用统计卡片展示。商品分类统计图用饼图展示不同特产分类的商品占比。销量Top10图用柱状图展示销量最高的10款商品。价格区间分布图用直方图展示商品价格区间分布。用户行为热力图用热力图展示一周内不同时间段用户访问量的分布。产地词云图用词云展示新疆各地州吐鲁番、哈密、和田、阿克苏等的商品分布情况。这些图表不只是装饰而是要从不同维度回答“这个平台上的商品结构是怎样的”“用户访问行为有什么规律”这些问题。答辩的时候老师指着一张图问你“这个图想说明什么”你能从数据意义层面给出解读比单纯说“这叫柱状图”要加分得多。技术实现方面图表库用的是ECharts数据接口用Django写JSON API返回前端用Ajax异步获取再通过init方法渲染。关于这套流程的具体代码和踩坑点后面我会详细讲。2. 数据库模型设计与推荐引擎核心逻辑2.1 数据表结构设计做推荐系统最重要的是用户行为数据。系统设计的表结构直接决定了后续算法能拿到什么数据。这个项目里我设计了以下几张核心表User用户表继承Django自带的AbstractUser扩展了手机号、头像、收货地址等字段。Category商品分类表分类名称、父级分类ID。新疆特产大体可以分为干果类、水果类、肉类、乳制品类、工艺品等。Product商品表标题、描述、原价、现价、库存、销量、封面图、详情图、标签用逗号分隔的多值字段、所属分类、产地、上架时间。Rating评分表用户ID、商品ID、评分值1-5分、创建时间。用UniqueConstraint确保一个用户对一个商品只能有一条评分记录。Favorite收藏表用户ID、商品ID、收藏时间。CartItem购物车表用户ID、商品ID、数量、加入时间。Order订单表和OrderItem订单明细表订单编号、用户ID、总价、状态、下单时间以及订单里每个商品的数量和单价。UserBehavior行为日志表用户ID、商品ID、行为类型浏览/收藏/加购/购买、时间戳。这张表是协同过滤最重要的数据来源。行为日志表非常关键。很多同学做毕设推荐系统时只用了评分数据但实际场景下大部分用户根本不会打分浏览、收藏、加购才是更普遍的行为。把行为类型加权融合进推荐算法效果会好很多。比如浏览算1分、收藏算2分、加购算3分、下单算4分这样能更精准地反映用户对商品的喜好程度。2.2 基于物品的协同过滤算法实现这一节是算法的核心部分我会给出比较完整的Python实现思路。ItemCF的核心步骤分三步第一步构建“用户-商品”行为矩阵。从UserBehavior表里读出所有行为数据按用户分组得到每个用户产生过行为的商品ID列表。from django.db.models import F from .models import UserBehavior def build_user_item_dict(): 构建用户-商品行为字典: {user_id: {product_id: score}} score由行为类型加权得到 behavior_weight {view: 1, favorite: 2, cart: 3, purchase: 4} records UserBehavior.objects.values(user_id, product_id, behavior_type) user_items {} for record in records: user_id record[user_id] product_id record[product_id] weight behavior_weight.get(record[behavior_type], 1) user_items.setdefault(user_id, {}) user_items[user_id][product_id] user_items[user_id].get(product_id, 0) weight return user_items第二步计算商品之间的相似度矩阵。ItemCF的标准做法是先建立“商品-用户”的倒排表也就是每个商品都被哪些用户行为过然后遍历任意两个商品用余弦相似度或杰卡德相似系数计算它们之间的相似度。余弦相似度的公式是 similarity(i, j) |N(i) ∩ N(j)| / sqrt(|N(i)| × |N(j)|)其中N(i)表示对商品i产生过行为的用户集合。分子是两个商品共同被行为过的用户数分母是各自用户数的几何平均值。import math from collections import defaultdict def calc_item_similarity(user_items): 基于余弦相似度计算商品相似度矩阵 user_items: {user_id: {product_id: score}} # 1. 建立商品 - 用户集合的倒排表 item_users defaultdict(set) for user_id, items in user_items.items(): for product_id in items.keys(): item_users[product_id].add(user_id) # 2. 计算共现矩阵 C[i][j] 和商品流行度 N[i] C defaultdict(dict) N defaultdict(int) for product_id, users in item_users.items(): N[product_id] len(users) for u in users: for other_product in user_items[u]: if other_product ! product_id: C[product_id][other_product] C[product_id].get(other_product, 0) 1 # 3. 计算相似度矩阵 W W defaultdict(dict) for product_id, related_items in C.items(): for other_product, co_count in related_items.items(): W[product_id][other_product] co_count / math.sqrt(N[product_id] * N[other_product]) return W这里有一个关键优化点在计算共现矩阵的时候需要对用户的行为商品列表进行“去重”。如果某用户一天内反复浏览同一个商品几十次你不去重的话这个商品和同用户其他商品的共现值会被无限拉高直接污染最终相似度。所以在读取数据构建user_items时同一个用户对同一个商品哪怕行为类型不同也只保留最高的权重分数就行或者合并成一条行为。第三步为目标用户生成推荐列表。遍历用户已经行为过的商品找出这些商品最相似的TopN个商品排除掉用户已经行为过的项目计算推荐得分并排序。def recommend_for_user(user_id, W, user_items, top_n10): 为目标用户生成推荐列表 W: 商品相似度矩阵 user_items: 完整用户行为字典 top_n: 返回前N个推荐商品 if user_id not in user_items: return [] interacted_items user_items[user_id] scores defaultdict(float) # 遍历用户行为过的每个商品 for product_id, pref_score in interacted_items.items(): # 找出与该商品最相似的K个商品 similar_items W.get(product_id, {}) for other_product, sim_score in similar_items.items(): # 排除用户已经产生过行为的商品 if other_product in interacted_items: continue # 推荐得分 用户对该商品的偏好度 * 商品间相似度 scores[other_product] pref_score * sim_score # 排序取TopN sorted_scores sorted(scores.items(), keylambda x: x[1], reverseTrue) return [(pid, round(score, 4)) for pid, score in sorted_scores[:top_n]]这段代码在面试或答辩时可以好好讲一讲。公式本身很简单但你要能解释清楚“为什么推荐得分要用用户偏好度乘以商品相似度”——因为用户对历史商品有多喜欢会直接影响“相似商品值不值得推荐”的置信度。偏好度高的商品它找到的相似商品在推荐列表里的权重要更大。2.3 冷启动问题与基于内容的推荐补充协同过滤算法有一个天然缺陷新用户没有任何行为数据或者只产生了一两次行为算法基本失效。这个时候就需要基于内容的推荐来兜底。具体做法是给商品表增加一个tags字段存“若羌灰枣、特级、新疆、红枣、健康零食”这样的标签串。推荐时先让用户在新用户引导页选择自己感兴趣的品类或标签然后计算候选商品与用户兴趣标签的匹配度def content_based_recommend(user_tags, top_n10): 基于标签匹配的内容推荐 user_tags: 用户选择的兴趣标签列表 products Product.objects.filter(is_activeTrue) scored [] for product in products: product_tags product.tags.split(,) if product.tags else [] # 计算Jaccard相似度交集大小 / 并集大小 inter len(set(user_tags) set(product_tags)) union len(set(user_tags) | set(product_tags)) if union 0: score 0 else: score inter / union # 叠加销量和评分权重 final_score score * 0.6 (product.sales / 1000) * 0.2 (product.avg_rating / 5) * 0.2 scored.append((product, final_score)) scored.sort(keylambda x: x[1], reverseTrue) return [p for p, s in scored[:top_n]]这里叠加销量和平均评分的权重是为了避免纯标签匹配时结果过于生硬。比如用户选了“健康”标签匹配到的商品可能全是文字描述里带健康两个字的小众商品把销量和评分考虑进去才能推荐出既有名又对味的商品。2.4 实时推荐还是离线计算很多刚接触推荐系统的同学会纠结一个问题用户每次点开推荐页面都要把所有用户行为数据拿出来重新算一遍相似度吗答案是不用而且千万别这么做。如果数据量稍微大一点每次请求都现场跑一遍ItemCF数据库会被打爆页面加载速度会慢到无法接受。这里要区分清楚“离线计算”和“实时召回”两个环节离线计算每天定时比如凌晨2点跑一次相似度计算任务把商品相似度矩阵写入缓存Redis或数据库表。平时推荐请求直接读取这份预计算结果。实时召回用户点开推荐页面时只根据该用户最近的行为记录从预计算好的相似度矩阵里取相关商品做一次排序后返回。在Django里最简单的离线圈任务可以用django-crontab或Celery beat来实现。不过对于毕设这个量级的数据量其实不需要上Redis。你可以写一个management command就是Django的自定义命令在服务器上通过crontab每天定时执行一次就行# products/management/commands/update_similarity.py from django.core.management.base import BaseCommand from recommend.utils import calc_item_similarity, build_user_item_dict class Command(BaseCommand): help 更新商品相似度矩阵 def handle(self, *args, **options): user_items build_user_item_dict() similarity_matrix calc_item_similarity(user_items) # 将相似度矩阵保存到缓存或数据库表 save_similarity_to_cache(similarity_matrix) self.stdout.write(self.style.SUCCESS(f相似度矩阵更新完成共{len(similarity_matrix)}个商品))然后Linux服务器上配置crontab0 2 * * * cd /path/to/project /usr/bin/python3 manage.py update_similarity /tmp/update_similarity.log 21这样推荐模块的性能问题和使用逻辑都理顺了。答辩的时候你可以理直气壮地说系统采用“离线计算 在线推荐”的架构兼顾了准确性和实时性要求这在工业界也是通用做法。3. 数据可视化模块的实现细节3.1 ECharts与Django的数据对接方式数据可视化这块前端我选择用ECharts原因很简单文档全、图表类型丰富、社区例程多而且完全免费。相比Highcharts和Chart.jsECharts对中文场景、地图、热力图、词云这些特殊图表的支持都要好一些。实现思路是Django视图函数返回JSON数据前端通过jQuery的Ajax请求获取数据再用ECharts初始化并渲染图表。下面以“商品分类统计饼图”为例演示完整的对接流程。先写一个Django视图接口返回商品分类的数量统计import json from django.http import JsonResponse from django.db.models import Count from .models import Product, Category def category_stats_api(request): 商品分类统计接口: 返回每个分类下的商品数量 stats ( Category.objects.filter(parent__isnullTrue) .annotate(product_countCount(product)) .values(name, product_count) ) data { categories: [item[name] for item in stats], counts: [item[product_count] for item in stats] } return JsonResponse(data, json_dumps_params{ensure_ascii: False})然后在前端模板中引入ECharts并写好Ajax请求逻辑!DOCTYPE html html langzh-CN head meta charsetUTF-8 title数据可视化看板/title !-- 引入 ECharts -- script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script !-- 引入 jQuery -- script srchttps://cdn.jsdelivr.net/npm/jquery3.7.1/dist/jquery.min.js/script /head body div idcategoryChart stylewidth: 100%; height: 400px;/div script $(document).ready(function () { // 初始化图表实例 var chart echarts.init(document.getElementById(categoryChart)); // 请求后端接口 $.ajax({ url: /api/category-stats/, type: GET, dataType: json, success: function (data) { // 配置图表选项 chart.setOption({ title: { text: 新疆特产商品分类统计, left: center }, tooltip: { trigger: item }, legend: { orient: vertical, left: left }, series: [{ name: 商品数量, type: pie, radius: 60%, data: data.categories.map(function (name, i) { return { name: name, value: data.counts[i] }; }), emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: rgba(0, 0, 0, 0.5) } } }] }); }, error: function (xhr, status, error) { console.error(获取分类统计数据失败:, error); } }); }); /script /body /html注意一个容易踩的坑如果后端返回的JSON里包含中文必须设置ensure_asciiFalse否则ECharts里中文标题和分类名会显示成\uXXXX形式的Unicode转义序列。这个在Django的JsonResponse里默认就是False但如果你自己用json.dumps构造JSON字符串一定记得加上这个参数。3.2 销量Top10与价格区间分布图销量Top10用柱状图非常直观。接口大概长这样def top_sales_api(request): 销量Top10商品接口 top_products Product.objects.filter(is_activeTrue).order_by(-sales)[:10] data { names: [p.title for p in top_products], sales: [p.sales for p in top_products] } return JsonResponse(data, json_dumps_params{ensure_ascii: False})前端配置柱状图时注意在series里加上label显示这样每个柱子顶上就能直接看到具体销量数字。另外建议把柱状图的渐变色加上视觉上会专业很多series: [{ type: bar, data: data.sales, barWidth: 40, itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: #83bff6 }, { offset: 1, color: #2f7ed8 } ]) }, label: { show: true, position: top, formatter: function (params) { return params.value 件; } } }]价格区间分布图可以用直方图本质上是柱状图但需要先把价格区间分组统计。Django里可以做分箱逻辑def price_distribution_api(request): 商品价格区间分布接口 products Product.objects.filter(is_activeTrue) bins [(0, 50), (50, 100), (100, 200), (200, 500), (500, 1000), (1000, None)] labels [0-50, 50-100, 100-200, 200-500, 500-1000, 1000以上] counts [] for low, high in bins: if high is None: count products.filter(price__gtelow).count() else: count products.filter(price__gtelow, price__lthigh).count() counts.append(count) return JsonResponse({labels: labels, counts: counts})这个功能的展示价值很高因为它能从图上直接看出商品定位是偏高端还是偏大众。比如新疆特产里的驼奶粉、雪菊、黑枸杞这些品类价格带分布可能和普通零食完全不一样放在图上就有故事可讲。3.3 词云和地图类可视化提到“新疆特产”如果不做一个新疆地图或产地词云总觉得少了点地域特色。ECharts本身支持地图但新疆的地图GeoJSON数据需要单独加载。一种最省事的做法是使用词云图展示产地分布比如“吐鲁番葡萄干”“和田大枣”“哈密瓜”“库尔勒香梨”等产地词按商品数量或销量显示不同大小视觉冲击力非常强。词云图不是ECharts内置的核心图表需要额外引入echarts-wordcloud插件script srchttps://cdn.jsdelivr.net/npm/echarts-wordcloud2.1.0/dist/echarts-wordcloud.min.js/script后端接口返回产地和数量from django.db.models import Count def origin_wordcloud_api(request): 产地词云数据接口: 统计各产地的商品数量 origin_stats ( Product.objects.filter(is_activeTrue) .values(origin) .annotate(totalCount(id)) .order_by(-total) ) data [{name: item[origin], value: item[total]} for item in origin_stats] return JsonResponse(data, safeFalse)前端渲染词云的配置项chart.setOption({ tooltip: {}, series: [{ type: wordCloud, shape: circle, left: center, top: center, width: 80%, height: 80%, sizeRange: [12, 60], rotationRange: [-45, 45], textStyle: { fontFamily: sans-serif, fontWeight: bold, color: function () { // 随机颜色让词云更生动 return rgb( [ Math.round(Math.random() * 160), Math.round(Math.random() * 160), Math.round(Math.random() * 160) ].join(,) ); } }, data: data }] });3.4 用户行为热力图让可视化“动起来”如果只有静态统计图视觉冲击力还是不够。我建议加一个基于用户行为日志的热力图展示一周七天不同时段的用户访问量。这类图表常被称为“周热度图”用ECharts的热力图heatmap来实现。后端接口需要把UserBehavior表按星期几和小时做聚合统计from django.db.models import Count from django.db.models.functions import ExtractWeekDay, ExtractHour def behavior_heatmap_api(request): 用户行为热力图接口: 统计一周内24小时的访问行为分布 records ( UserBehavior.objects .values(behavior_type) .annotate( weekdayExtractWeekDay(created_at), hourExtractHour(created_at), totalCount(id) ) .values(weekday, hour, total) ) # 初始化 7x24 的矩阵 heatmap_data [[0] * 24 for _ in range(7)] for record in records: weekday record[weekday] - 1 # Django的ExtractWeekDay返回1周日7周六 hour record[hour] heatmap_data[weekday][hour] record[total] # 转换为ECharts需要的 [x, y, value] 格式 data [] for w in range(7): for h in range(24): data.append([h, w, heatmap_data[w][h]]) return JsonResponse({data: data})前端用热力图配置chart.setOption({ title: { text: 用户行为周热度分布, left: center }, tooltip: { position: top, formatter: function (params) { return 星期 [日,一,二,三,四,五,六][params.value[1]] params.value[0] :00br访问量: params.value[2]; } }, grid: { top: 50, left: 80, right: 30, bottom: 40 }, xAxis: { type: category, data: Array.from({ length: 24 }, (_, i) i :00), splitArea: { show: true } }, yAxis: { type: category, data: [周日, 周一, 周二, 周三, 周四, 周五, 周六], splitArea: { show: true } }, visualMap: { min: 0, max: 100, calculable: true, orient: horizontal, left: center, bottom: 0, inRange: { color: [#e0f3f8, #abd9e9, #74add1, #4575b4, #313695] } }, series: [{ type: heatmap, data: data.data, label: { show: false }, emphasis: { itemStyle: { shadowBlur: 10, shadowColor: rgba(0, 0, 0, 0.5) } } }] });为什么要做这个热力图因为它是“行为数据可视化”的代表直接体现了推荐系统所依赖的用户行为日志的价值。答辩时你可以顺势说系统采集了用户的浏览、收藏、加购、下单全链路行为数据这些数据既用于推荐算法计算也支持平台运营决策分析。这样“大数据”的概念就有了实在的支撑而不是只停留在PPT上。4. 系统核心功能实战开发4.1 用户端购物全流程实现用户端的核心业务闭环是注册/登录 - 浏览商品 - 查看详情 - 加入购物车 - 提交订单 - 模拟支付 - 查看订单状态。注册登录这一块Django自带的认证系统基本够用。需要注意扩展邮箱或手机号字段时要用AUTH_USER_MODEL指定自定义用户模型尽量在项目创建初期就配置好否则后期改起来非常麻烦。# settings.py AUTH_USER_MODEL users.User商品列表页可以做一个筛选和排序功能按分类筛选、按价格排序、按销量排序。这里用Django ORM的链式查询一个视图函数就能搞定def product_list(request): products Product.objects.filter(is_activeTrue) category_id request.GET.get(category) sort_by request.GET.get(sort, default) if category_id: products products.filter(category_idcategory_id) if sort_by price_asc: products products.order_by(price) elif sort_by price_desc: products products.order_by(-price) elif sort_by sales: products products.order_by(-sales) return render(request, product_list.html, {products: products})购物车这块我建议用Django的Session来管理未登录用户的购物车登录后可以合并到数据库中的CartItem表。不过为了简单起见很多毕设项目直接要求登录后才能加购物车这样数据模型更简洁不会出现“Session购物车和数据库购物车两边同步”的麻烦问题。省下的时间拿去做推荐算法和可视化性价比更高。提交订单时需要注意修改商品库存和销量的原子性。用Django的F表达式可以避免并发下的超卖问题from django.db.models import F def create_order(request): cart_items CartItem.objects.filter(userrequest.user) if not cart_items.exists(): return JsonResponse({code: 1, msg: 购物车为空}) total_price 0 order_items [] for item in cart_items: product item.product # 用F表达式原子更新库存和销量避免并发覆盖 if product.stock item.quantity: return JsonResponse({code: 1, msg: f{product.title} 库存不足}) product.stock F(stock) - item.quantity product.sales F(sales) item.quantity product.save() # 创建订单主表 order Order.objects.create( userrequest.user, total_pricetotal_price, statuspending ) # 创建订单明细 # ...省略 return JsonResponse({code: 0, msg: 下单成功})深入一点讲用F(stock)写法Django生成的SQL是在数据库层面执行“stock stock - quantity”而不是先SELECT出来算好再UPDATE回去这样能避免在高并发场景下两个请求同时读到相同库存、各自减一导致的数据错乱。虽然毕设数据量也许遇不到并发问题但这是面试和答辩都很爱问的细节。4.2 管理员后台的商品与订单管理Django Admin后台可以直接管理所有模型但建议对Admin做一点定制让它更好演示。比如商品列表页展示缩略图、现价、库存并支持按分类筛选。用django的list_display、list_filter、search_fields这些属性几十行代码就能配置出一个像模像样的管理后台from django.contrib import admin from .models import Product, Category admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display [title, category, price, stock, sales, is_active, created_at] list_filter [category, is_active, origin] search_fields [title, tags, description] readonly_fields [sales] list_per_page 20为了演示效果更好还可以自定义一个数据看板页面写到Django Admin的index里登录管理员账号后一进去就能看到各项核心数据的统计卡片和最近订单。这个看板可以直接复用前面做的可视化图表把商品分类饼图和销量Top10放进去。4.3 数据的来源与预处理做推荐系统的毕设另一个常见卡点是“数据从哪来”。自己手工录入几十条商品数据当然可以但要让推荐算法有效果、可视化面板有说服力至少需要几百条商品数据和几千条用户行为数据。我推荐几种数据获取方式第一种是爬虫抓取。可以在保证合规的前提下爬取主流电商平台上新疆特产商品的价格、标题、销量、产地等信息。注意控制请求频率做好User-Agent伪装和代理轮换。爬下来的数据存成CSV再用Django的manage.py shell或数据导入脚本写入数据库。这一部分做好了还能在毕设里多写一章“数据采集与预处理”内容就更充实了。第二种是数据增强。在基础商品数据之上写脚本自动生成模拟用户行为。比如随机生成500个用户、1000条评分、2000条浏览记录等。这个要注意生成的数据分布要尽量贴近真实少数热门商品被大多数人浏览和评分长尾商品只有偶尔的访问用户的行为时间集中在19点到23点等。第三种是最稳妥的直接用现成的电商公开数据集把它清洗转换成项目所需格式。不过公开数据集通常不是新疆特产垂直领域特征字段可能对不上改动工作量也不小。我自己的建议是“爬虫抓取 脚本生成”双管齐下爬几十个真实商品作为种子数据然后用脚本在真实商品基础上补充生成用户行为数据。这样既能保证商品信息的真实性又能让推荐算法有足够的数据跑起来。实际操作时数据导入写成一个management command最方便# products/management/commands/import_products.py import csv from django.core.management.base import BaseCommand from products.models import Product, Category class Command(BaseCommand): help 从CSV文件导入商品数据 def add_arguments(self, parser): parser.add_argument(csv_file, typestr, helpCSV文件路径) def handle(self, *args, **options): filepath options[csv_file] with open(filepath, moder, encodingutf-8-sig) as f: reader csv.DictReader(f) for row in reader: category, _ Category.objects.get_or_create(namerow[category]) Product.objects.update_or_create( titlerow[title], defaults{ category: category, description: row.get(description, ), price: decimal.Decimal(row[price]), stock: int(row.get(stock, 999)), sales: int(row.get(sales, 0)), origin: row.get(origin, ), tags: row.get(tags, ), } ) self.stdout.write(self.style.SUCCESS(商品数据导入完成))4.4 项目文档与代码讲解的组织方式毕设除了系统本身文档也是大头。这个项目名里带了“程序文档代码讲解一条龙定制”说明这类项目在市场上通常是成套交付的文档是重要的组成部分。毕设论文一般要包含这几个章节绪论、相关技术介绍、系统需求分析、系统设计架构设计、数据库设计、功能模块设计、系统实现、系统测试、总结与展望。写文档最核心的原则是“图和表优先”。系统架构图、功能模块图、数据库ER图、核心流程图每一样都比大段的文字描述加分。推荐系统的部分建议把算法流程图画出来从行为数据采集、数据预处理、相似度计算、推荐列表生成到前端展示画一条完整的链路。代码讲解在答辩时很重要。很多同学代码写得出来但讲不明白。建议准备10-15分钟的项目演示和讲解核心逻辑要拿算法模块和可视化模块举例不要从头到尾念代码。我见过最好的答辩演示节奏是先花3分钟展示系统页面效果再花5分钟讲推荐算法原理和核心代码最后花2分钟讲可视化模块的数据链路剩下时间留给老师提问。5. 常见问题与排查技巧实录5.1 Django静态文件和图片无法显示这是Django项目最常见的坑没有之一。页面样式加载不出来、商品图片不显示基本都是静态文件配置问题。开发环境的配置很简单# settings.py DEBUG True STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR / static] MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media然后在项目的urls.py里加上媒体文件的处理from django.conf import settings from django.conf.urls.static import static urlpatterns [ # ...你的url ] static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)模板中使用静态文件的正确姿势是{% load static %} img src{% static images/products/dates.jpg %} alt和田玉枣图片上传后如果是通过ImageField存储的在模板里用{{ product.image.url }}来引用这个的底层路径就是MEDIA_URL加上文件相对路径。如果你在VSCode里写img标签、在Django的static文件里显示不了十有八九是忘了在模板里{% load static %}或者用了绝对路径而不是模板标签方式引用。5.2 时间字段的时区问题Django默认开启USE_TZ True存储到数据库的是UTC时间。统计今天的数据或绘制行为热力图时如果直接按本地时间过滤可能差8个小时导致图表数据不对。解决办法有两个要么全局使用本地时间在settings.py里设置TIME_ZONE Asia/Shanghai USE_TZ False要么保持USE_TZ True在查询时用timezone.localtime做转换from django.utils import timezone now_local timezone.localtime(timezone.now()) today_start now_local.replace(hour0, minute0, second0, microsecond0) today_records UserBehavior.objects.filter(created_at__gtetoday_start)对于毕设项目我建议直接用USE_TZ False省掉一切时区烦恼。只有当你将来要部署到多时区的服务器上时才需要认真处理时区问题。5.3 推荐结果全是热门商品的伪个性化如果你跑推荐算法发现给每个用户推荐的商品都差不多都是销量最高的那几款那说明相似度计算可能出了问题。最常见的原因是用户行为数据太稀疏每个用户只对一两个商品产生过行为导致共现矩阵非常稀疏相似度计算不稳定最终热门商品因为被行为次数多而统治了推荐结果。这和ItemCF的“热门物品惩罚”机制有关——标准ItemCF在计算相似度时还会对热门物品做惩罚也就是分子不变、分母中热门物品的流行度指数加一个系数这样热门商品对相似度计算的贡献会被削弱。解决思路有几种增加行为数据的密度用脚本补充模拟行为保证每个用户至少有10-20条行为记录。对相似度公式加“热门惩罚”。在分母中把N(i)替换为N(i)的alpha次方alpha通常取0.5左右目的是减弱热门商品在相似度计算中的主导作用。alpha 0.5 W[product_id][other_product] co_count / math.pow(N[product_id], alpha) * math.pow(N[other_product], 1 - alpha)在推荐列表生成阶段引入“多样性”约束比如对同分类的商品做去重或限定单个分类的推荐数量最多不超过N个。5.4 Ajax请求403 CSRF验证失败前端用Ajax向Django提交POST请求比如提交评分、加购物车会报CSRF验证失败错。Django默认开启了CSRF防护这是安全机制。解决方法是在发送Ajax请求前从cookie中获取csrftoken加到请求头里。一个通用的办法是给jQuery的Ajax统一加上headers$.ajaxSetup({ beforeSend: function (xhr, settings) { if (!(/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type))) { xhr.setRequestHeader(X-CSRFToken, getCookie(csrftoken)); } } }); function getCookie(name) { var cookieValue null; if (document.cookie document.cookie ! ) { var cookies document.cookie.split(;); for (var i 0; i cookies.length; i) { var cookie $.trim(cookies[i]); if (cookie.substring(0, name.length 1) (name )) { cookieValue decodeURIComponent(cookie.substring(name.length 1)); break; } } } return cookieValue; }如果你觉得这个写法麻烦也可以在视图函数上用csrf_exempt装饰器临时屏蔽CSRF校验。但注意这只适合学习阶段的简单调试正式项目不应该这么做。5.5 部署环境的Python和依赖版本坑这个项目的部署通常会用Windows服务器学生买Windows轻量服务器比较常见生产环境用waitress替代runserver再用nginx做反向代理和静态文件服务。这里的坑主要在两个地方一个是Windows上waitress启动Django应用时如果项目中用到了sqlite数据库且路径是相对路径可能出现数据库打不开的情况。建议在生产环境用绝对路径配置数据库。在settings.py里可以通过Path(file).resolve().parent.parent来定位项目根目录避免路径漂移。另一个是Python版本。Django 4.2以上版本只支持Python 3.8如果你服务器上装的是Python 3.6只能装Django 3.2 LTS。建议项目requirements.txt里锁定版本避免部署时pull到不兼容的依赖版本。Django4.2.7 mysqlclient2.2.0 djangorestframework3.14.0 django-cors-headers4.3.1 celery5.3.4 redis5.0.1 Pillow10.1.0 requests2.31.0 beautifulsoup44.12.25.6 文档和代码版本管理建议代码版本管理务必用Git。从项目第一天就git init每完成一个功能模块就提交一次。这样不光是防止代码丢失更重要的是你在写论文时可以回头看每次提交记录准确回忆起每个功能的开发时间和思路。这个细节在写“系统实现”章节时能帮你省很多事。文档方面维护一个README.md记录项目启动步骤、管理员账号和密码、测试账号和密码。我见过太多同学答辩前晚会忘记自己项目的启动命令和超级管理员密码到现场手忙脚乱。把这类信息在README里写清楚你真会感谢当时的自己。6. 一条龙定制与资源交付经验项目标题里提到了“一条龙定制”这其实反映了目前毕设市场的一种常见需求有些同学时间紧、基础弱需要的不只是一份代码而是从选题、实现、论文写作到答辩PPT的全流程支持。我不评价这种模式的好坏只说从技术角度一套完整可交付的毕设项目应该包含哪些东西部署环境一台云服务器Windows或Linux都行、域名或IP地址、Python 3.8环境、MySQL或SQLite数据库。项目源代码完整的Django项目包含requirements.txt、README.md、数据库初始化脚本。演示视频建议录一段5分钟左右的系统演示视频把用户端、管理端、推荐效果和可视化面板都过一遍。这个在紧急的时候可以直接作为答辩演示备用。论文文档不少于15000字的毕设论文包含绪论、相关技术、需求分析、系统设计、系统实现、系统测试、总结展望。答辩PPT10-15页风格简洁核心内容在推荐算法和数据可视化两个模块。如果是自己学习复现这个项目我建议不要直接去下载“成品”而是按章节自己实现一遍。Django的项目结构、ORM外键关系、模板继承语法、Ajax交互、算法实现这些知识点在“照着写”的过程中才能真正转化为自己的技能。哪怕时间很紧至少把推荐算法模块和可视化模块的核心代码自己敲一遍。答辩时被问到“这块代码是什么意思”现场能答出来的同学基本都不会挂。7. 项目的可扩展方向这个项目做完之后如果你想让它在功能或学术深度上更上一个台阶有几个后续扩展方向可以参考第一个方向是推荐算法的升级。现在的ItemCF是一种比较基础的推荐算法你可以把它替换成或组合上基于矩阵分解的SVD算法甚至用LightGBM做排序模型。论文里可以做一个算法效果对比实验ItemCF的召回率、覆盖率与SVD算法的对比用离线评测指标PrecisionN、RecallN来量化说明。这样论文的学术深度一下子就上去了。第二个方向是引入用户画像。目前系统里的用户特征主要是行为数据你可以增加用户注册时填写的年龄、性别、地域、偏好品类等信息做基于用户画像的推荐。结合Django的信号机制用户完善资料时自动更新画像标签推荐模块再叠加这些标签权重。第三个方向是爬虫系统的完善。目前的数据采集可能是一次性的脚本可以把它改造成定时增量爬取的爬虫服务配合Celery异步任务队列和Redis去重缓存做成一套可持续更新的数据管道。这样系统就真的有了“大数据处理”的雏形不只是静态商品数据。第四个方向是前后端分离改造。如果学有余力把后端从Django模板渲染改成DRFDjango REST Framework Vue3的前后端分离架构管理端用Vue Element Plus重写。工作量大约增加一倍但对求职时的项目展示加分不少。你可以在简历上写“熟悉RESTful API设计与前后端分离开发模式”确实有真实项目支撑。最后再分享一个小技巧无论你做哪个方向的扩展都要保证主流程是通顺的也就是“用户浏览商品 - 行为被记录 - 推荐算法计算 - 推荐列表展示 - 用户完成购买”这个核心闭环必须能串起来。扩展功能只是加分项核心闭环才是项目的灵魂。答辩时只要能把这条链路讲清楚、演示顺畅再配合一两个有深度的技术亮点分数基本不会低。这个项目我前前后后带过不少学生复现过大家最容易卡住的点其实不是技术本身而是刚开始时面对“又要做算法、又要做页面、又要写论文、又要做PPT”产生的畏难情绪。我的建议是给项目搭一个明确的任务清单第一周搭好Django环境、跑通数据库和登录注册第二周完成商品模块和购物车订单第三周实现推荐算法和可视化第四周集中写论文和做PPT。把大项目拆成可以每天交付的小任务一步步走完你会发现它远没有想象中那么吓人。
返回列表