本文还有配套的精品资源点击获取简介基于Python和Django开发的可直接运行的电商商品推荐系统内置SQLite数据库db.sqlite3支持用户注册登录、商品浏览、标签筛选、收藏夹管理、星级评分及实时个性化推荐展示。核心推荐逻辑封装在recommend_shopping.py中兼容协同过滤与基于内容的推荐策略前端采用HTMLCSSJS模板login.html、shopping.html等集成自定义templatetags、media资源目录和静态文件static配套shopping_cover封面图与项目演示视频项目演示.m4v。项目结构规范包含完整Django配置settings.py、urls.py、缓存键管理cache_keys.py及依赖清单requirements.txt开箱即用无需额外环境配置。附带详细说明文档说明文件.txt与设计文档压缩包171265889347208773632.zip覆盖需求分析、数据库ER图、算法流程、部署步骤与调试方法。适用于本科毕业设计、课程大作业或推荐系统入门实践也适合教学演示与快速原型验证。1. 项目概述这不是一个“玩具系统”而是一套能跑通真实业务闭环的推荐实践模板你有没有遇到过这样的情况在学完协同过滤、矩阵分解、Embedding这些概念后打开Jupyter Notebook写完一个评分预测函数心里挺美——结果发现它孤零零地躺在一个.ipynb文件里既没有用户登录也没有商品封面图更没法点“收藏”后立刻看到推荐列表刷新它像一张精准但没装进车里的发动机图纸。而今天要讲的这个Django电商推荐系统就是把那台发动机完整装进了车架、接上了油门、配好了仪表盘甚至给你调好了后视镜角度——它不是教你怎么造发动机而是带你亲手开上路感受油门响应、转弯侧倾和刹车点头的真实反馈。我带过三届毕业设计每年都有至少5个学生卡在“算法跑通了但不知道怎么塞进Web系统里”。他们用Flask搭个登录页再硬塞一个recommend()函数返回JSON前端用jQuery拼DOM结果调试时发现用户A刚打完分推荐列表还是显示用户B的历史偏好收藏夹数据存进SQLite却没加事务刷新页面就丢了一条更别说缓存失效、模板变量命名冲突、静态资源404这些“看不见的坑”。这套源码之所以值得细读正因为它绕开了所有“理论上可行”的陷阱全部采用Django原生机制落地用户认证走django.contrib.auth评分走GenericRelation关联收藏用ManyToManyField加中间表推荐结果缓存用cache.set()cache.get()配合自定义键生成逻辑连封面图路径都通过MEDIA_URL和MEDIA_ROOT规范处理——它不炫技但每一步都踩在Django最佳实践的鼓点上。关键词里提到的“Django推荐系统”“电商商品推荐”“Python毕业设计”其实指向三个不同层次的需求对初学者它是可运行、可调试、可截图交作业的“安全网”对课程设计者它是模块清晰、文档完备、能拆解成“用户模块”“商品模块”“推荐引擎模块”三块讲授的教学载体对想深入推荐工程的同学它更是理解“算法如何与业务系统耦合”的活体标本——比如recommend_shopping.py里那个看似简单的get_user_recommendations(user_id, top_k10)函数背后藏着用户行为数据清洗、冷启动兜底策略、实时性与准确性的权衡取舍。它不假装自己是工业级系统没上Redis集群、没做AB测试分流但它诚实展示了从pip install django到用户点击“推荐商品”按钮之间每一个必须亲手拧紧的螺丝。我试过把它部署在校内服务器上给大三学生做两周实训从环境搭建到功能演示全程无阻。最让我意外的是有位非计算机专业的同学在读懂shopping/models.py里Product和UserRating的外键关系后主动重构了评分逻辑把原来的整数星级改成了带权重的时间衰减评分——这恰恰说明当系统足够“透明”算法就不再是黑箱而成了可触摸、可修改、可生长的有机体。所以如果你正在找一个既能满足毕设答辩要求、又能真正帮你建立工程直觉的项目别再翻那些只有核心算法、没有登录框的GitHub仓库了。接下来我们就从它的骨架开始一层层剥开这套系统是如何把“推荐”这件事稳稳地落在Django的地基上的。2. 系统整体架构与设计思路拆解为什么选Django而不是Flask或FastAPI2.1 架构选型背后的现实考量毕业设计场景下的“够用且健壮”很多同学第一反应是“推荐系统不是该用FastAPI吗异步快啊”或者“Flask轻量代码少好改”。但当你真把它放进毕业设计场景里就会发现这些“理论优势”在实操中反而成了负担。FastAPI的异步模型对新手极其不友好——一个await忘加整个请求就卡死数据库操作得全换成asyncpg或aiomysql而SQLite根本没官方异步驱动更别说JWT鉴权、文件上传、模板渲染这些毕业设计刚需功能FastAPI得靠第三方库拼凑文档碎片化严重。Flask则走向另一个极端它太自由自由到你需要自己决定“用户密码怎么加密”“登录状态怎么保持”“静态文件放哪”“错误页面长什么样”。我见过太多学生花三天时间纠结用werkzeug.security.generate_password_hash还是bcrypt最后答辩PPT里连登录页截图都没有。而Django在这里展现出惊人的“毕业设计适配性”。它的“约定优于配置”哲学直接把90%的基建问题打包解决了python manage.py startproject生成的目录结构天然支持模块拆分django.contrib.auth提供的User模型、登录视图、密码重置流程开箱即用django.contrib.sessions自动管理会话不用你手写cookie签名逻辑django.contrib.staticfiles统一处理CSS/JS/图片连collectstatic命令都给你写好了。更重要的是Django的ORM不是简单的SQL封装而是把数据库设计思维深度融入开发流程——当你在models.py里写下class UserRating(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE)Django不仅帮你建表还自动生成反向查询user.userrating_set.all()这种“数据关系即代码”的表达方式让本科生能直观理解“用户-评分-商品”三者间的业务约束比对着ER图硬背外键规则有效得多。这套系统的目录结构shopping/应用、templates/、media/、static/正是Django标准范式的体现。它没用微服务拆分因为毕业设计不需要也没上Docker因为校内服务器通常只允许python manage.py runserver但它严格遵循Django的App组织原则shopping应用里封装所有电商相关逻辑recommend_shopping.py作为独立模块被views.py调用而非混在视图里——这种隔离让代码可测试、可替换。比如你想把协同过滤换成LightFM只需重写recommend_shopping.py里的get_recommendations()函数其他部分完全不动。这才是工程思维不是追求技术栈最新而是选择能让核心逻辑稳定生长的土壤。2.2 推荐引擎的定位嵌入式模块而非独立服务观察recommend_shopping.py的导入方式from shopping.models import Product, UserRating, UserFavorite。这透露出关键信息——推荐逻辑不是跑在单独进程里的微服务而是作为Django应用内部的一个计算模块。这种设计在毕业设计中极为务实。想象一下如果推荐服务独立部署你需要额外维护一个requirements.txt、一套日志配置、一个健康检查端点调试时得同时开两个终端一个跑Django一个跑推荐服务更麻烦的是跨进程通信——用户打分后如何通知推荐服务更新缓存用HTTP请求延迟高用消息队列又引入新复杂度。而嵌入式设计让一切变得简单用户提交评分表单 → Django视图接收请求 → 调用UserRating.objects.create()存入数据库 → 立即调用recommend_shopping.get_user_recommendations(request.user.id)重新计算 → 结果存入cache.set()→ 模板直接渲染。整个链路在一次HTTP请求内完成毫秒级响应。recommend_shopping.py里没有app.run()没有app.route只有纯函数输入user_id和top_k输出商品ID列表。它甚至不关心HTTP协议只专注解决“给这个用户推荐什么”这个本质问题。这种“算法归算法框架归框架”的分离正是工业界推荐系统的基础架构思想——只不过这里用函数调用代替了RPC调用。更值得玩味的是cache_keys.py的存在。它没用frecommend_{user_id}这种简单拼接而是定义了get_recommendation_cache_key(user_id)函数内部做了str(user_id)类型转换和hashlib.md5哈希防key过长。这说明作者清楚意识到缓存键不是随便起的字符串而是需要保证唯一性、可预测性、长度可控的工程实体。当你在views.py里看到cache_key cache_keys.get_recommendation_cache_key(user.id)再看到cache.get(cache_key)你就明白这不是临时写的缓存而是为后续扩展比如按标签缓存、按热度缓存预留了接口。这种细节往往比算法本身更能体现一个项目的成熟度。2.3 数据流闭环从用户行为到推荐结果的七步落地这套系统最精妙的设计是构建了一个完整的、可验证的数据流闭环。我们以“用户A给商品X打5星”为例追踪数据如何流动行为触发用户在shopping.html点击星级评分组件前端通过AJAX发送POST请求到/rating/路由分发urls.py将请求映射到shopping.views.rate_product视图业务校验视图检查用户是否登录、商品是否存在、是否已评分避免重复打分数据落库调用UserRating.objects.update_or_create()原子性地创建或更新评分记录缓存失效执行cache.delete(cache_keys.get_recommendation_cache_key(user.id))确保下次请求获取最新推荐实时计算调用recommend_shopping.get_user_recommendations(user.id)基于最新评分数据生成Top10商品结果呈现将推荐商品列表传入模板recommend_shopping.html用{% for product in recommendations %}循环渲染。这七步里每一步都有明确的技术选型支撑AJAX用原生Fetch而非jQuery减少依赖评分校验用Django ORM的get_or_404和exists()方法避免N1查询缓存失效用delete()而非set(None)语义清晰模板渲染用Django内置的for标签安全防XSS。没有一步是“能跑就行”的权宜之计全部指向同一个目标让用户的行为能在3秒内转化为可见的推荐结果变化。这种闭环感是学生作品与工业级系统最直观的分水岭。3. 核心模块解析与实操要点从models到templates的逐层穿透3.1 数据模型设计用ORM表达业务语义而非仅仅建表打开shopping/models.py你会看到三个核心模型Product商品、UserRating用户评分、UserFavorite用户收藏。它们的定义远不止于字段声明而是业务规则的代码化表达。先看Productclass Product(models.Model): name models.CharField(max_length200) description models.TextField() price models.DecimalField(max_digits10, decimal_places2) tags models.CharField(max_length200, blankTrue) # 逗号分隔的标签如手机,5G,旗舰 cover_image models.ImageField(upload_tocovers/, blankTrue) created_at models.DateTimeField(auto_now_addTrue)注意tags字段用CharField而非ManyToManyField。这看似违背范式实则是针对毕业设计场景的务实选择标签数量有限通常20个且极少变动用逗号分隔可直接在模板里用{{ product.tags.split(,) }}渲染无需额外JOIN查询搜索时用__icontains也能满足基础需求。如果强行上多对多就得建Tag模型、ProductTag中间表还要处理标签创建、去重、统计——对毕设而言这是典型的“过度设计”。再看UserRatingclass UserRating(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameratings) product models.ForeignKey(Product, on_deletemodels.CASCADE, related_nameratings) rating models.PositiveSmallIntegerField(choices[(i, str(i)) for i in range(1, 6)]) created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (user, product) # 关键防止同一用户对同一商品重复评分unique_together是灵魂所在。它不只是数据库约束更是业务逻辑的强制声明一个用户对一个商品只能有一个评分。Django ORM会在save()时自动检查视图层调用update_or_create()时也依赖此约束实现“存在则更新不存在则创建”。如果没有它用户狂点五星数据库里就堆满重复记录推荐算法算出来的结果全是噪声。UserFavorite更巧妙class UserFavorite(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_namefavorites) product models.ForeignKey(Product, on_deletemodels.CASCADE, related_namefavorited_by) added_at models.DateTimeField(auto_now_addTrue)这里related_name的命名极具教学价值user.favorites.all()读作“用户的所有收藏”product.favorited_by.all()读作“收藏了该商品的所有用户”。这种命名让代码自解释学生看一眼就知道怎么查数据。更绝的是shopping/views.py里收藏/取消收藏的逻辑只用两行# 收藏 UserFavorite.objects.get_or_create(userrequest.user, productproduct) # 取消收藏 UserFavorite.objects.filter(userrequest.user, productproduct).delete()没有if-else判断没有状态机纯粹依靠数据库约束和ORM的原子操作。这就是Django ORM的魅力把复杂的业务状态变更压缩成一行可读、可测、可复现的代码。提示cover_image字段依赖Pillow库处理图片缩略图。settings.py里已配置MEDIA_URL/media/和MEDIA_ROOTos.path.join(BASE_DIR, media)但本地开发时需在urls.py中添加static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)否则shopping_cover.jpg无法显示。这是新手最容易忽略的配置点。3.2 推荐算法实现协同过滤与内容推荐的双轨并行recommend_shopping.py是整个系统的“大脑”但它没有用任何机器学习库而是用纯PythonDjango ORM实现了两种推荐策略。这种“不用AI框架也能做推荐”的思路恰恰是理解推荐本质的关键。先看协同过滤Collaborative Filtering的核心函数get_collaborative_recommendations(user_id, top_k10)def get_collaborative_recommendations(user_id, top_k10): # 步骤1获取该用户评过分的商品列表 user_ratings UserRating.objects.filter(user_iduser_id).values_list(product_id, flatTrue) # 步骤2找出与该用户评分相似的其他用户基于Jaccard相似度 # 计算用户A和用户B共同评分的商品数 / 用户A或用户B评分的商品总数 similar_users [] target_user_products set(user_ratings) for other_user in User.objects.exclude(iduser_id): other_ratings UserRating.objects.filter(userother_user).values_list(product_id, flatTrue) other_products set(other_ratings) if not other_products: continue intersection len(target_user_products other_products) union len(target_user_products | other_products) jaccard intersection / union if union else 0 if jaccard 0.3: # 相似度阈值 similar_users.append((other_user.id, jaccard)) # 步骤3聚合相似用户喜欢的商品排除目标用户已评分的 candidate_products defaultdict(float) for similar_user_id, similarity in similar_users: rated_products UserRating.objects.filter( user_idsimilar_user_id ).exclude(product_id__inuser_ratings).values_list(product_id, rating) for pid, rating in rated_products: candidate_products[pid] similarity * rating # 步骤4按加权得分排序取TopK sorted_products sorted(candidate_products.items(), keylambda x: x[1], reverseTrue) return [pid for pid, score in sorted_products[:top_k]]这段代码的价值不在性能它没用矩阵运算而在清晰揭示了协同过滤的四个步骤找邻居、算相似、聚合偏好、排序筛选。jaccard 0.3这个阈值不是拍脑袋定的——我实测过低于0.2时推荐结果过于随机高于0.4则邻居太少导致冷启动问题加剧。similarity * rating的加权方式也比简单求和更能反映“相似用户给出的高分更有参考价值”。再看基于内容的推荐Content-Based函数get_content_recommendations(user_id, top_k10)def get_content_recommendations(user_id, top_k10): # 步骤1获取用户历史高分商品的标签取评分4的商品 high_rated_products UserRating.objects.filter( user_iduser_id, rating__gte4 ).select_related(product).values_list(product__tags, flatTrue) # 步骤2统计标签词频TF tag_counter Counter() for tags_str in high_rated_products: if tags_str: for tag in tags_str.split(,): tag_counter[tag.strip()] 1 # 步骤3计算商品标签向量与用户标签向量的余弦相似度 # 这里简化直接匹配标签重合度 all_products Product.objects.exclude( id__inUserRating.objects.filter(user_iduser_id).values_list(product_id, flatTrue) ) scored_products [] for product in all_products: if not product.tags: continue product_tags set(tag.strip() for tag in product.tags.split(,)) overlap len(tag_counter.keys() product_tags) scored_products.append((product.id, overlap)) # 步骤4按重合度排序 scored_products.sort(keylambda x: x[1], reverseTrue) return [pid for pid, score in scored_products[:top_k]]这里用“标签重合度”替代复杂的TF-IDF和余弦相似度是教学场景的神来之笔。学生一眼就能懂用户喜欢“手机,5G”那么标签含“手机”或“5G”的商品优先推荐。select_related(product)的使用避免了N1查询否则每查一个评分都要JOIN一次Product表exclude(...)确保不推荐用户已评分的商品这是推荐系统的基本礼仪。注意get_user_recommendations()函数内部会先尝试协同过滤若结果不足top_k个则用内容推荐补足。这种“主备切换”策略完美解决了冷启动问题——新用户没评分历史协同过滤失效但内容推荐仍能基于注册时填写的兴趣标签虽未在源码中实现但预留了扩展接口提供基础推荐。3.3 前端模板与交互用最少的JS实现最大化的用户体验templates/目录下的login.html、shopping.html、recommend_shopping.html展现了Django模板系统的强大。它们没用Vue或React却通过Django模板标签和少量原生JS实现了流畅交互。以评分组件为例shopping.html中!-- 商品卡片内的评分区域 -- div classrating-widget>document.querySelectorAll(.star).forEach(star { star.addEventListener(click, function() { const productId this.closest(.rating-widget).dataset.productId; const rating this.dataset.value; fetch(/rating/, { method: POST, headers: {X-CSRFToken: getCookie(csrftoken)}, body: new URLSearchParams({product_id: productId, rating: rating}) }) .then(response response.json()) .then(data { if (data.success) { // 更新当前商品的平均分显示 const avgSpan this.closest(.rating-widget).querySelector(.avg-rating); avgSpan.textContent data.new_avg; // 高亮已选星星 this.closest(.stars).querySelectorAll(.star).forEach(s { s.classList.toggle(filled, parseInt(s.dataset.value) rating); }); } }); }); });关键点在于它只负责“触发请求”和“局部更新”不处理业务逻辑。评分计算、缓存刷新、数据库写入全部交给Django视图完成。这种前后端分工让学生能清晰区分“界面表现”和“业务规则”避免陷入“JS里写一堆if-else判断用户权限”的混乱。templatetags/目录下的自定义标签更是点睛之笔。比如product_extras.py中的get_user_ratingregister.simple_tag def get_user_rating(user, product): 获取指定用户对指定商品的评分用于模板中显示已评星级 if user.is_authenticated: try: rating UserRating.objects.get(useruser, productproduct) return rating.rating except UserRating.DoesNotExist: return 0 return 0在模板里直接调用{% get_user_rating request.user product as user_rating %}然后{% if user_rating %}已评{{ user_rating }}星{% endif %}。这种封装把数据库查询逻辑从模板中剥离既保持了模板的简洁性又避免了视图层传递过多冗余数据。4. 实操过程与核心环节实现从零部署到功能验证的完整流水线4.1 环境准备与依赖安装避开Windows下SQLite的编码雷区虽然requirements.txt只有一行Django4.2.7但实际部署时有两个隐藏坑点必须提前处理坑点一Windows下SQLite中文路径问题db.sqlite3文件若放在中文路径如C:\用户\张三\电商推荐系统Django启动时会报错sqlite3.OperationalError: unable to open database file。解决方案1. 将项目解压到纯英文路径如D:\django-recom2. 在settings.py中确认BASE_DIR定义正确BASE_DIR Path(__file__).resolve().parent.parent.parent # 确保指向项目根目录运行前先手动创建db.sqlite3空文件右键新建文本文档→重命名为db.sqlite3避免首次迁移时因路径问题失败。坑点二Pillow图像处理库缺失Product.cover_image字段依赖Pillow处理封面图。若未安装上传图片时会报ImportError: No module named PIL。安装命令pip install Pillow提示安装Pillow前建议先升级pippython -m pip install --upgrade pip避免因旧版pip导致编译失败。完成环境准备后四步启动# 1. 进入项目根目录包含manage.py的位置 cd D:\django-recom # 2. 安装依赖即使只有一个Django也要执行 pip install -r requirements.txt # 3. 执行数据库迁移首次运行必做 python manage.py migrate # 4. 创建超级用户用于登录后台管理 python manage.py createsuperuser # 按提示输入用户名、邮箱、密码密码不会显示输完直接回车 # 5. 启动开发服务器 python manage.py runserver此时访问http://127.0.0.1:8000/admin/用刚创建的超级用户登录即可在Django Admin后台看到Products、User Ratings等模型证明数据库已就绪。4.2 数据初始化用fixtures快速填充测试数据db.sqlite3是空库直接访问/shopping/会显示“暂无商品”。项目提供了fixtures/目录需从压缩包中解压里面包含products.json和ratings.json测试数据。加载命令python manage.py loaddata fixtures/products.json python manage.py loaddata fixtures/ratings.jsonproducts.json文件结构示例[ { model: shopping.product, pk: 1, fields: { name: iPhone 15 Pro, description: A17芯片钛金属机身USB-C接口, price: 7999.00, tags: 手机,苹果,旗舰, cover_image: covers/iphone15.jpg, created_at: 2023-09-22T10:00:00Z } } ]关键点cover_image路径必须与MEDIA_ROOT配置一致。fixtures/目录需放在shopping/应用内或在settings.py中配置FIXTURE_DIRS [os.path.join(BASE_DIR, fixtures)]。加载后访问http://127.0.0.1:8000/shopping/商品列表即刻出现。此时用普通用户非超级用户注册登录即可开始全流程测试浏览商品→点击收藏→打分→查看推荐结果。4.3 推荐功能验证三步法确认算法生效验证推荐是否真正工作不能只看页面渲染要穿透到数据层。我总结了三步验证法第一步确认用户行为已入库登录Django Admin后台进入User Ratings列表筛选你的用户检查是否有刚提交的评分记录。重点看created_at时间戳是否为当前时间rating值是否正确。若无记录说明视图层的rate_product函数未执行检查urls.py路由是否正确映射以及AJAX请求的CSRF Token是否携带。第二步手动触发推荐计算在Python Shell中模拟推荐调用python manage.py shell from recommend_shopping import get_user_recommendations from django.contrib.auth.models import User user User.objects.get(usernametestuser) # 替换为你注册的用户名 rec_ids get_user_recommendations(user.id, top_k5) print(rec_ids) [3, 7, 12, 5, 9] # 输出商品ID列表 # 验证这些商品确实存在且未被该用户评分 from shopping.models import Product, UserRating for pid in rec_ids: ... p Product.objects.get(idpid) ... rated UserRating.objects.filter(useruser, productp).exists() ... print(f商品{p.name}已评分{rated}) ...若输出显示所有商品已评分False说明算法正确过滤了历史行为若出现True则get_user_recommendations()中的排除逻辑有bug。第三步检查缓存键与内容Django默认使用本地内存缓存可通过Shell查看 from django.core.cache import cache from cache_keys import get_recommendation_cache_key key get_recommendation_cache_key(user.id) print(key) recommend_5 # user.id5 result cache.get(key) print(result) [3, 7, 12, 5, 9] # 应与第二步结果一致若result为None说明缓存未命中检查views.py中cache.set()调用位置是否在评分保存之后若result存在但与第二步不一致说明缓存未及时失效检查cache.delete()是否被执行。4.4 自定义功能扩展为毕业设计加分的三个实用改造这套系统预留了清晰的扩展接口以下三个改造方向能让你的毕设脱颖而出改造一增加“猜你喜欢”标签页基于用户画像在shopping/views.py中新增视图def user_profile_recommend(request): if not request.user.is_authenticated: return redirect(login) # 获取用户收藏的商品标签取Top3 favorite_tags UserFavorite.objects.filter(userrequest.user).select_related(product) tag_counter Counter() for fav in favorite_tags: if fav.product.tags: for tag in fav.product.tags.split(,): tag_counter[tag.strip()] 1 # 推荐标签匹配度最高的商品 top_tags [tag for tag, count in tag_counter.most_common(3)] products Product.objects.filter( tags__icontainstop_tags[0] if top_tags else ).distinct()[:10] return render(request, user_profile_recommend.html, {products: products})对应在urls.py中添加path(profile-recommend/, views.user_profile_recommend, nameprofile_recommend)再在导航栏加入链接。这个改造仅需20行代码却体现了“用户画像”概念答辩时可强调“从行为数据提炼兴趣特征”。改造二评分图表可视化用Chart.js在templates/shopping.html底部添加script srchttps://cdn.jsdelivr.net/npm/chart.js/script canvas idratingChart width400 height200/canvas script const ctx document.getElementById(ratingChart).getContext(2d); new Chart(ctx, { type: bar, data: { labels: [1星, 2星, 3星, 4星, 5星], datasets: [{ label: 评分分布, data: {{ rating_distribution|safe }}, // 由视图传入[5, 3, 12, 25, 48] backgroundColor: [#ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #feca57] }] } }); /script视图中计算分布def shopping_view(request): # ... 其他逻辑 # 统计当前商品评分分布 ratings UserRating.objects.filter(productproduct).values_list(rating, flatTrue) dist [0, 0, 0, 0, 0] for r in ratings: dist[r-1] 1 context[rating_distribution] dist return render(request, shopping.html, context)图表让数据一目了然展示你对用户体验的关注。改造三收藏夹持久化支持未登录用户利用Django Session存储临时收藏def add_to_favorites(request): if request.method POST: product_id request.POST.get(product_id) if not request.user.is_authenticated: # 未登录用户存入session favorites request.session.get(temp_favorites, []) if product_id not in favorites: favorites.append(product_id) request.session[temp_favorites] favorites request.session.modified True else: # 已登录用户存入数据库 UserFavorite.objects.get_or_create( userrequest.user, product_idproduct_id ) return redirect(shopping)这样即使用户未注册也能体验收藏功能提升交互友好度。5. 常见问题与排查技巧实录那些文档里不会写的“血泪经验”5.1 典型问题速查表问题现象可能原因排查命令/方法解决方案访问/shopping/报Page not found (404)urls.py未正确包含shopping.urls检查mysite/urls.py中是否有path(shopping/, include(shopping.urls))在mysite/urls.py的urlpatterns中添加该行登录后跳转到/accounts/profile/但该页面不存在Django默认登录成功跳转URL未配置查看settings.py中是否有LOGIN_REDIRECT_URL /shopping/添加LOGIN_REDIRECT_URL /shopping/封面图shopping_cover.jpg显示为破损图标MEDIA_URL与MEDIA_ROOT配置不匹配或未启用媒体文件服务运行python manage.py showmigrations确认迁移完成检查settings.py中MEDIA_URL是否为/media/在mysite/urls.py末尾添加 static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)评分后推荐列表不更新缓存未失效或推荐函数未被调用在views.py的rate_product函数中print(Cache deleted)重启服务器后点击评分看控制台输出确认cache.delete()在UserRating.objects.create()之后执行检查cache_keys.get_recommendation_cache_key()参数是否为user.id而非request.user对象python manage.py migrate报错no such table: auth_user数据库迁移未按顺序执行运行python manage.py showmigrations查看哪些迁移未应用删除db.sqlite3重新执行python manage.py migrate5.2 独家避坑技巧来自三届毕设指导的真实教训技巧一用django-extensions的runserver_plus替代原生runserver原生runserver在模板语法错误时只报TemplateSyntaxError不显示具体哪一行。安装django-extensions后pip install django-extensions在settings.py的INSTALLED_APPS中添加django_extensions然后用python manage.py runserver_plus它会提供交互式调试器点击错误行即可进入Python Shell直接检查变量值。我曾帮一个学生发现他的get_user_recommendations()函数里user_id传进来是字符串而非整数导致UserRating.objects.filter(user_iduser_id)查不到数据——runserver_plus的调试器让他两分钟就定位到问题。技巧二为UserRating模型添加__str__方法大幅提升Admin调试效率在shopping/models.py中UserRating类里添加def __str__(self): return f{self.user.username} - {self.product.name} ({self.rating}星)这样在Django Admin的User Ratings列表中每条记录直接显示“张三 - iPhone 15 Pro (5星)”而不是枯燥的UserRating object (1)。答辩时老师问“这个评分是谁给的”你点开就能看到无需额外查询。技巧三用django-debug-toolbar实时监控SQL查询安装后在settings.py中配置INSTALLED_APPS [debug_toolbar] MIDDLEWARE [debug_toolbar.middleware.DebugToolbarMiddleware] INTERNAL_IPS [127.0.0.1]启动后页面右下角会出现调试工具栏点击“SQL”标签能看到本次请求执行的所有SQL语句及耗时。当推荐页面加载慢时你可能发现SELECT * FROM shopping_userrating WHERE user_id 5执行了5次——这就是N1查询。解决方案在视图中用select_related(product)预加载商品信息把5次查询合并为1次。技巧四recommend_shopping.py的算法性能瓶颈自查清单协同过滤在用户量1000时会明显变慢。自查以下三点1.索引缺失运行python manage.py dbshell进入SQLite执行CREATE INDEX IF NOT EXISTS idx_userrating_user ON shopping_userrating(user_id);2.未分页get_collaborative_recommendations()中User.objects.exclude(iduser_id)会加载所有用户应改为User.objects.exclude(iduser_id)[:100]限制邻居数量3.重复计算get_user_recommendations()被频繁调用应确保cache.set()的timeout参数合理如300秒避免每秒都重算。最后分享一个小技巧在recommend_shopping.py顶部添加import logging; logger logging.getLogger(__name__)在关键步骤插入logger.info(fStep 2: Found {len(similar_users)} similar users)。然后在settings.py中配置日志输出到控制台。这样每次推荐计算时终端都会打印详细步骤比断点调试更直观——毕竟真正的工程师永远相信日志而不是自己的记忆。这套Django电商推荐系统表面看是一份毕业设计源码深层看它是一套“软件工程最小可行实践”用最克制的技术选型解决最真实的业务问题用最透明的代码结构暴露最本质的工程权衡用最详尽的文档记录最琐碎的踩坑经验。它不承诺成为下一个淘宝推荐引擎但它郑重告诉你从第一行django-admin startproject到用户点击“推荐商品”那一刻的喜悦每一步都踏实可循。本文还有配套的精品资源点击获取简介基于Python和Django开发的可直接运行的电商商品推荐系统内置SQLite数据库db.sqlite3支持用户注册登录、商品浏览、标签筛选、收藏夹管理、星级评分及实时个性化推荐展示。核心推荐逻辑封装在recommend_shopping.py中兼容协同过滤与基于内容的推荐策略前端采用HTMLCSSJS模板login.html、shopping.html等集成自定义templatetags、media资源目录和静态文件static配套shopping_cover封面图与项目演示视频项目演示.m4v。项目结构规范包含完整Django配置settings.py、urls.py、缓存键管理cache_keys.py及依赖清单requirements.txt开箱即用无需额外环境配置。附带详细说明文档说明文件.txt与设计文档压缩包171265889347208773632.zip覆盖需求分析、数据库ER图、算法流程、部署步骤与调试方法。适用于本科毕业设计、课程大作业或推荐系统入门实践也适合教学演示与快速原型验证。本文还有配套的精品资源点击获取
Django电商推荐系统实战源码:含登录、评分、收藏与个性化推荐功能
本文还有配套的精品资源点击获取简介基于Python和Django开发的可直接运行的电商商品推荐系统内置SQLite数据库db.sqlite3支持用户注册登录、商品浏览、标签筛选、收藏夹管理、星级评分及实时个性化推荐展示。核心推荐逻辑封装在recommend_shopping.py中兼容协同过滤与基于内容的推荐策略前端采用HTMLCSSJS模板login.html、shopping.html等集成自定义templatetags、media资源目录和静态文件static配套shopping_cover封面图与项目演示视频项目演示.m4v。项目结构规范包含完整Django配置settings.py、urls.py、缓存键管理cache_keys.py及依赖清单requirements.txt开箱即用无需额外环境配置。附带详细说明文档说明文件.txt与设计文档压缩包171265889347208773632.zip覆盖需求分析、数据库ER图、算法流程、部署步骤与调试方法。适用于本科毕业设计、课程大作业或推荐系统入门实践也适合教学演示与快速原型验证。1. 项目概述这不是一个“玩具系统”而是一套能跑通真实业务闭环的推荐实践模板你有没有遇到过这样的情况在学完协同过滤、矩阵分解、Embedding这些概念后打开Jupyter Notebook写完一个评分预测函数心里挺美——结果发现它孤零零地躺在一个.ipynb文件里既没有用户登录也没有商品封面图更没法点“收藏”后立刻看到推荐列表刷新它像一张精准但没装进车里的发动机图纸。而今天要讲的这个Django电商推荐系统就是把那台发动机完整装进了车架、接上了油门、配好了仪表盘甚至给你调好了后视镜角度——它不是教你怎么造发动机而是带你亲手开上路感受油门响应、转弯侧倾和刹车点头的真实反馈。我带过三届毕业设计每年都有至少5个学生卡在“算法跑通了但不知道怎么塞进Web系统里”。他们用Flask搭个登录页再硬塞一个recommend()函数返回JSON前端用jQuery拼DOM结果调试时发现用户A刚打完分推荐列表还是显示用户B的历史偏好收藏夹数据存进SQLite却没加事务刷新页面就丢了一条更别说缓存失效、模板变量命名冲突、静态资源404这些“看不见的坑”。这套源码之所以值得细读正因为它绕开了所有“理论上可行”的陷阱全部采用Django原生机制落地用户认证走django.contrib.auth评分走GenericRelation关联收藏用ManyToManyField加中间表推荐结果缓存用cache.set()cache.get()配合自定义键生成逻辑连封面图路径都通过MEDIA_URL和MEDIA_ROOT规范处理——它不炫技但每一步都踩在Django最佳实践的鼓点上。关键词里提到的“Django推荐系统”“电商商品推荐”“Python毕业设计”其实指向三个不同层次的需求对初学者它是可运行、可调试、可截图交作业的“安全网”对课程设计者它是模块清晰、文档完备、能拆解成“用户模块”“商品模块”“推荐引擎模块”三块讲授的教学载体对想深入推荐工程的同学它更是理解“算法如何与业务系统耦合”的活体标本——比如recommend_shopping.py里那个看似简单的get_user_recommendations(user_id, top_k10)函数背后藏着用户行为数据清洗、冷启动兜底策略、实时性与准确性的权衡取舍。它不假装自己是工业级系统没上Redis集群、没做AB测试分流但它诚实展示了从pip install django到用户点击“推荐商品”按钮之间每一个必须亲手拧紧的螺丝。我试过把它部署在校内服务器上给大三学生做两周实训从环境搭建到功能演示全程无阻。最让我意外的是有位非计算机专业的同学在读懂shopping/models.py里Product和UserRating的外键关系后主动重构了评分逻辑把原来的整数星级改成了带权重的时间衰减评分——这恰恰说明当系统足够“透明”算法就不再是黑箱而成了可触摸、可修改、可生长的有机体。所以如果你正在找一个既能满足毕设答辩要求、又能真正帮你建立工程直觉的项目别再翻那些只有核心算法、没有登录框的GitHub仓库了。接下来我们就从它的骨架开始一层层剥开这套系统是如何把“推荐”这件事稳稳地落在Django的地基上的。2. 系统整体架构与设计思路拆解为什么选Django而不是Flask或FastAPI2.1 架构选型背后的现实考量毕业设计场景下的“够用且健壮”很多同学第一反应是“推荐系统不是该用FastAPI吗异步快啊”或者“Flask轻量代码少好改”。但当你真把它放进毕业设计场景里就会发现这些“理论优势”在实操中反而成了负担。FastAPI的异步模型对新手极其不友好——一个await忘加整个请求就卡死数据库操作得全换成asyncpg或aiomysql而SQLite根本没官方异步驱动更别说JWT鉴权、文件上传、模板渲染这些毕业设计刚需功能FastAPI得靠第三方库拼凑文档碎片化严重。Flask则走向另一个极端它太自由自由到你需要自己决定“用户密码怎么加密”“登录状态怎么保持”“静态文件放哪”“错误页面长什么样”。我见过太多学生花三天时间纠结用werkzeug.security.generate_password_hash还是bcrypt最后答辩PPT里连登录页截图都没有。而Django在这里展现出惊人的“毕业设计适配性”。它的“约定优于配置”哲学直接把90%的基建问题打包解决了python manage.py startproject生成的目录结构天然支持模块拆分django.contrib.auth提供的User模型、登录视图、密码重置流程开箱即用django.contrib.sessions自动管理会话不用你手写cookie签名逻辑django.contrib.staticfiles统一处理CSS/JS/图片连collectstatic命令都给你写好了。更重要的是Django的ORM不是简单的SQL封装而是把数据库设计思维深度融入开发流程——当你在models.py里写下class UserRating(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE)Django不仅帮你建表还自动生成反向查询user.userrating_set.all()这种“数据关系即代码”的表达方式让本科生能直观理解“用户-评分-商品”三者间的业务约束比对着ER图硬背外键规则有效得多。这套系统的目录结构shopping/应用、templates/、media/、static/正是Django标准范式的体现。它没用微服务拆分因为毕业设计不需要也没上Docker因为校内服务器通常只允许python manage.py runserver但它严格遵循Django的App组织原则shopping应用里封装所有电商相关逻辑recommend_shopping.py作为独立模块被views.py调用而非混在视图里——这种隔离让代码可测试、可替换。比如你想把协同过滤换成LightFM只需重写recommend_shopping.py里的get_recommendations()函数其他部分完全不动。这才是工程思维不是追求技术栈最新而是选择能让核心逻辑稳定生长的土壤。2.2 推荐引擎的定位嵌入式模块而非独立服务观察recommend_shopping.py的导入方式from shopping.models import Product, UserRating, UserFavorite。这透露出关键信息——推荐逻辑不是跑在单独进程里的微服务而是作为Django应用内部的一个计算模块。这种设计在毕业设计中极为务实。想象一下如果推荐服务独立部署你需要额外维护一个requirements.txt、一套日志配置、一个健康检查端点调试时得同时开两个终端一个跑Django一个跑推荐服务更麻烦的是跨进程通信——用户打分后如何通知推荐服务更新缓存用HTTP请求延迟高用消息队列又引入新复杂度。而嵌入式设计让一切变得简单用户提交评分表单 → Django视图接收请求 → 调用UserRating.objects.create()存入数据库 → 立即调用recommend_shopping.get_user_recommendations(request.user.id)重新计算 → 结果存入cache.set()→ 模板直接渲染。整个链路在一次HTTP请求内完成毫秒级响应。recommend_shopping.py里没有app.run()没有app.route只有纯函数输入user_id和top_k输出商品ID列表。它甚至不关心HTTP协议只专注解决“给这个用户推荐什么”这个本质问题。这种“算法归算法框架归框架”的分离正是工业界推荐系统的基础架构思想——只不过这里用函数调用代替了RPC调用。更值得玩味的是cache_keys.py的存在。它没用frecommend_{user_id}这种简单拼接而是定义了get_recommendation_cache_key(user_id)函数内部做了str(user_id)类型转换和hashlib.md5哈希防key过长。这说明作者清楚意识到缓存键不是随便起的字符串而是需要保证唯一性、可预测性、长度可控的工程实体。当你在views.py里看到cache_key cache_keys.get_recommendation_cache_key(user.id)再看到cache.get(cache_key)你就明白这不是临时写的缓存而是为后续扩展比如按标签缓存、按热度缓存预留了接口。这种细节往往比算法本身更能体现一个项目的成熟度。2.3 数据流闭环从用户行为到推荐结果的七步落地这套系统最精妙的设计是构建了一个完整的、可验证的数据流闭环。我们以“用户A给商品X打5星”为例追踪数据如何流动行为触发用户在shopping.html点击星级评分组件前端通过AJAX发送POST请求到/rating/路由分发urls.py将请求映射到shopping.views.rate_product视图业务校验视图检查用户是否登录、商品是否存在、是否已评分避免重复打分数据落库调用UserRating.objects.update_or_create()原子性地创建或更新评分记录缓存失效执行cache.delete(cache_keys.get_recommendation_cache_key(user.id))确保下次请求获取最新推荐实时计算调用recommend_shopping.get_user_recommendations(user.id)基于最新评分数据生成Top10商品结果呈现将推荐商品列表传入模板recommend_shopping.html用{% for product in recommendations %}循环渲染。这七步里每一步都有明确的技术选型支撑AJAX用原生Fetch而非jQuery减少依赖评分校验用Django ORM的get_or_404和exists()方法避免N1查询缓存失效用delete()而非set(None)语义清晰模板渲染用Django内置的for标签安全防XSS。没有一步是“能跑就行”的权宜之计全部指向同一个目标让用户的行为能在3秒内转化为可见的推荐结果变化。这种闭环感是学生作品与工业级系统最直观的分水岭。3. 核心模块解析与实操要点从models到templates的逐层穿透3.1 数据模型设计用ORM表达业务语义而非仅仅建表打开shopping/models.py你会看到三个核心模型Product商品、UserRating用户评分、UserFavorite用户收藏。它们的定义远不止于字段声明而是业务规则的代码化表达。先看Productclass Product(models.Model): name models.CharField(max_length200) description models.TextField() price models.DecimalField(max_digits10, decimal_places2) tags models.CharField(max_length200, blankTrue) # 逗号分隔的标签如手机,5G,旗舰 cover_image models.ImageField(upload_tocovers/, blankTrue) created_at models.DateTimeField(auto_now_addTrue)注意tags字段用CharField而非ManyToManyField。这看似违背范式实则是针对毕业设计场景的务实选择标签数量有限通常20个且极少变动用逗号分隔可直接在模板里用{{ product.tags.split(,) }}渲染无需额外JOIN查询搜索时用__icontains也能满足基础需求。如果强行上多对多就得建Tag模型、ProductTag中间表还要处理标签创建、去重、统计——对毕设而言这是典型的“过度设计”。再看UserRatingclass UserRating(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameratings) product models.ForeignKey(Product, on_deletemodels.CASCADE, related_nameratings) rating models.PositiveSmallIntegerField(choices[(i, str(i)) for i in range(1, 6)]) created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (user, product) # 关键防止同一用户对同一商品重复评分unique_together是灵魂所在。它不只是数据库约束更是业务逻辑的强制声明一个用户对一个商品只能有一个评分。Django ORM会在save()时自动检查视图层调用update_or_create()时也依赖此约束实现“存在则更新不存在则创建”。如果没有它用户狂点五星数据库里就堆满重复记录推荐算法算出来的结果全是噪声。UserFavorite更巧妙class UserFavorite(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_namefavorites) product models.ForeignKey(Product, on_deletemodels.CASCADE, related_namefavorited_by) added_at models.DateTimeField(auto_now_addTrue)这里related_name的命名极具教学价值user.favorites.all()读作“用户的所有收藏”product.favorited_by.all()读作“收藏了该商品的所有用户”。这种命名让代码自解释学生看一眼就知道怎么查数据。更绝的是shopping/views.py里收藏/取消收藏的逻辑只用两行# 收藏 UserFavorite.objects.get_or_create(userrequest.user, productproduct) # 取消收藏 UserFavorite.objects.filter(userrequest.user, productproduct).delete()没有if-else判断没有状态机纯粹依靠数据库约束和ORM的原子操作。这就是Django ORM的魅力把复杂的业务状态变更压缩成一行可读、可测、可复现的代码。提示cover_image字段依赖Pillow库处理图片缩略图。settings.py里已配置MEDIA_URL/media/和MEDIA_ROOTos.path.join(BASE_DIR, media)但本地开发时需在urls.py中添加static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)否则shopping_cover.jpg无法显示。这是新手最容易忽略的配置点。3.2 推荐算法实现协同过滤与内容推荐的双轨并行recommend_shopping.py是整个系统的“大脑”但它没有用任何机器学习库而是用纯PythonDjango ORM实现了两种推荐策略。这种“不用AI框架也能做推荐”的思路恰恰是理解推荐本质的关键。先看协同过滤Collaborative Filtering的核心函数get_collaborative_recommendations(user_id, top_k10)def get_collaborative_recommendations(user_id, top_k10): # 步骤1获取该用户评过分的商品列表 user_ratings UserRating.objects.filter(user_iduser_id).values_list(product_id, flatTrue) # 步骤2找出与该用户评分相似的其他用户基于Jaccard相似度 # 计算用户A和用户B共同评分的商品数 / 用户A或用户B评分的商品总数 similar_users [] target_user_products set(user_ratings) for other_user in User.objects.exclude(iduser_id): other_ratings UserRating.objects.filter(userother_user).values_list(product_id, flatTrue) other_products set(other_ratings) if not other_products: continue intersection len(target_user_products other_products) union len(target_user_products | other_products) jaccard intersection / union if union else 0 if jaccard 0.3: # 相似度阈值 similar_users.append((other_user.id, jaccard)) # 步骤3聚合相似用户喜欢的商品排除目标用户已评分的 candidate_products defaultdict(float) for similar_user_id, similarity in similar_users: rated_products UserRating.objects.filter( user_idsimilar_user_id ).exclude(product_id__inuser_ratings).values_list(product_id, rating) for pid, rating in rated_products: candidate_products[pid] similarity * rating # 步骤4按加权得分排序取TopK sorted_products sorted(candidate_products.items(), keylambda x: x[1], reverseTrue) return [pid for pid, score in sorted_products[:top_k]]这段代码的价值不在性能它没用矩阵运算而在清晰揭示了协同过滤的四个步骤找邻居、算相似、聚合偏好、排序筛选。jaccard 0.3这个阈值不是拍脑袋定的——我实测过低于0.2时推荐结果过于随机高于0.4则邻居太少导致冷启动问题加剧。similarity * rating的加权方式也比简单求和更能反映“相似用户给出的高分更有参考价值”。再看基于内容的推荐Content-Based函数get_content_recommendations(user_id, top_k10)def get_content_recommendations(user_id, top_k10): # 步骤1获取用户历史高分商品的标签取评分4的商品 high_rated_products UserRating.objects.filter( user_iduser_id, rating__gte4 ).select_related(product).values_list(product__tags, flatTrue) # 步骤2统计标签词频TF tag_counter Counter() for tags_str in high_rated_products: if tags_str: for tag in tags_str.split(,): tag_counter[tag.strip()] 1 # 步骤3计算商品标签向量与用户标签向量的余弦相似度 # 这里简化直接匹配标签重合度 all_products Product.objects.exclude( id__inUserRating.objects.filter(user_iduser_id).values_list(product_id, flatTrue) ) scored_products [] for product in all_products: if not product.tags: continue product_tags set(tag.strip() for tag in product.tags.split(,)) overlap len(tag_counter.keys() product_tags) scored_products.append((product.id, overlap)) # 步骤4按重合度排序 scored_products.sort(keylambda x: x[1], reverseTrue) return [pid for pid, score in scored_products[:top_k]]这里用“标签重合度”替代复杂的TF-IDF和余弦相似度是教学场景的神来之笔。学生一眼就能懂用户喜欢“手机,5G”那么标签含“手机”或“5G”的商品优先推荐。select_related(product)的使用避免了N1查询否则每查一个评分都要JOIN一次Product表exclude(...)确保不推荐用户已评分的商品这是推荐系统的基本礼仪。注意get_user_recommendations()函数内部会先尝试协同过滤若结果不足top_k个则用内容推荐补足。这种“主备切换”策略完美解决了冷启动问题——新用户没评分历史协同过滤失效但内容推荐仍能基于注册时填写的兴趣标签虽未在源码中实现但预留了扩展接口提供基础推荐。3.3 前端模板与交互用最少的JS实现最大化的用户体验templates/目录下的login.html、shopping.html、recommend_shopping.html展现了Django模板系统的强大。它们没用Vue或React却通过Django模板标签和少量原生JS实现了流畅交互。以评分组件为例shopping.html中!-- 商品卡片内的评分区域 -- div classrating-widget>document.querySelectorAll(.star).forEach(star { star.addEventListener(click, function() { const productId this.closest(.rating-widget).dataset.productId; const rating this.dataset.value; fetch(/rating/, { method: POST, headers: {X-CSRFToken: getCookie(csrftoken)}, body: new URLSearchParams({product_id: productId, rating: rating}) }) .then(response response.json()) .then(data { if (data.success) { // 更新当前商品的平均分显示 const avgSpan this.closest(.rating-widget).querySelector(.avg-rating); avgSpan.textContent data.new_avg; // 高亮已选星星 this.closest(.stars).querySelectorAll(.star).forEach(s { s.classList.toggle(filled, parseInt(s.dataset.value) rating); }); } }); }); });关键点在于它只负责“触发请求”和“局部更新”不处理业务逻辑。评分计算、缓存刷新、数据库写入全部交给Django视图完成。这种前后端分工让学生能清晰区分“界面表现”和“业务规则”避免陷入“JS里写一堆if-else判断用户权限”的混乱。templatetags/目录下的自定义标签更是点睛之笔。比如product_extras.py中的get_user_ratingregister.simple_tag def get_user_rating(user, product): 获取指定用户对指定商品的评分用于模板中显示已评星级 if user.is_authenticated: try: rating UserRating.objects.get(useruser, productproduct) return rating.rating except UserRating.DoesNotExist: return 0 return 0在模板里直接调用{% get_user_rating request.user product as user_rating %}然后{% if user_rating %}已评{{ user_rating }}星{% endif %}。这种封装把数据库查询逻辑从模板中剥离既保持了模板的简洁性又避免了视图层传递过多冗余数据。4. 实操过程与核心环节实现从零部署到功能验证的完整流水线4.1 环境准备与依赖安装避开Windows下SQLite的编码雷区虽然requirements.txt只有一行Django4.2.7但实际部署时有两个隐藏坑点必须提前处理坑点一Windows下SQLite中文路径问题db.sqlite3文件若放在中文路径如C:\用户\张三\电商推荐系统Django启动时会报错sqlite3.OperationalError: unable to open database file。解决方案1. 将项目解压到纯英文路径如D:\django-recom2. 在settings.py中确认BASE_DIR定义正确BASE_DIR Path(__file__).resolve().parent.parent.parent # 确保指向项目根目录运行前先手动创建db.sqlite3空文件右键新建文本文档→重命名为db.sqlite3避免首次迁移时因路径问题失败。坑点二Pillow图像处理库缺失Product.cover_image字段依赖Pillow处理封面图。若未安装上传图片时会报ImportError: No module named PIL。安装命令pip install Pillow提示安装Pillow前建议先升级pippython -m pip install --upgrade pip避免因旧版pip导致编译失败。完成环境准备后四步启动# 1. 进入项目根目录包含manage.py的位置 cd D:\django-recom # 2. 安装依赖即使只有一个Django也要执行 pip install -r requirements.txt # 3. 执行数据库迁移首次运行必做 python manage.py migrate # 4. 创建超级用户用于登录后台管理 python manage.py createsuperuser # 按提示输入用户名、邮箱、密码密码不会显示输完直接回车 # 5. 启动开发服务器 python manage.py runserver此时访问http://127.0.0.1:8000/admin/用刚创建的超级用户登录即可在Django Admin后台看到Products、User Ratings等模型证明数据库已就绪。4.2 数据初始化用fixtures快速填充测试数据db.sqlite3是空库直接访问/shopping/会显示“暂无商品”。项目提供了fixtures/目录需从压缩包中解压里面包含products.json和ratings.json测试数据。加载命令python manage.py loaddata fixtures/products.json python manage.py loaddata fixtures/ratings.jsonproducts.json文件结构示例[ { model: shopping.product, pk: 1, fields: { name: iPhone 15 Pro, description: A17芯片钛金属机身USB-C接口, price: 7999.00, tags: 手机,苹果,旗舰, cover_image: covers/iphone15.jpg, created_at: 2023-09-22T10:00:00Z } } ]关键点cover_image路径必须与MEDIA_ROOT配置一致。fixtures/目录需放在shopping/应用内或在settings.py中配置FIXTURE_DIRS [os.path.join(BASE_DIR, fixtures)]。加载后访问http://127.0.0.1:8000/shopping/商品列表即刻出现。此时用普通用户非超级用户注册登录即可开始全流程测试浏览商品→点击收藏→打分→查看推荐结果。4.3 推荐功能验证三步法确认算法生效验证推荐是否真正工作不能只看页面渲染要穿透到数据层。我总结了三步验证法第一步确认用户行为已入库登录Django Admin后台进入User Ratings列表筛选你的用户检查是否有刚提交的评分记录。重点看created_at时间戳是否为当前时间rating值是否正确。若无记录说明视图层的rate_product函数未执行检查urls.py路由是否正确映射以及AJAX请求的CSRF Token是否携带。第二步手动触发推荐计算在Python Shell中模拟推荐调用python manage.py shell from recommend_shopping import get_user_recommendations from django.contrib.auth.models import User user User.objects.get(usernametestuser) # 替换为你注册的用户名 rec_ids get_user_recommendations(user.id, top_k5) print(rec_ids) [3, 7, 12, 5, 9] # 输出商品ID列表 # 验证这些商品确实存在且未被该用户评分 from shopping.models import Product, UserRating for pid in rec_ids: ... p Product.objects.get(idpid) ... rated UserRating.objects.filter(useruser, productp).exists() ... print(f商品{p.name}已评分{rated}) ...若输出显示所有商品已评分False说明算法正确过滤了历史行为若出现True则get_user_recommendations()中的排除逻辑有bug。第三步检查缓存键与内容Django默认使用本地内存缓存可通过Shell查看 from django.core.cache import cache from cache_keys import get_recommendation_cache_key key get_recommendation_cache_key(user.id) print(key) recommend_5 # user.id5 result cache.get(key) print(result) [3, 7, 12, 5, 9] # 应与第二步结果一致若result为None说明缓存未命中检查views.py中cache.set()调用位置是否在评分保存之后若result存在但与第二步不一致说明缓存未及时失效检查cache.delete()是否被执行。4.4 自定义功能扩展为毕业设计加分的三个实用改造这套系统预留了清晰的扩展接口以下三个改造方向能让你的毕设脱颖而出改造一增加“猜你喜欢”标签页基于用户画像在shopping/views.py中新增视图def user_profile_recommend(request): if not request.user.is_authenticated: return redirect(login) # 获取用户收藏的商品标签取Top3 favorite_tags UserFavorite.objects.filter(userrequest.user).select_related(product) tag_counter Counter() for fav in favorite_tags: if fav.product.tags: for tag in fav.product.tags.split(,): tag_counter[tag.strip()] 1 # 推荐标签匹配度最高的商品 top_tags [tag for tag, count in tag_counter.most_common(3)] products Product.objects.filter( tags__icontainstop_tags[0] if top_tags else ).distinct()[:10] return render(request, user_profile_recommend.html, {products: products})对应在urls.py中添加path(profile-recommend/, views.user_profile_recommend, nameprofile_recommend)再在导航栏加入链接。这个改造仅需20行代码却体现了“用户画像”概念答辩时可强调“从行为数据提炼兴趣特征”。改造二评分图表可视化用Chart.js在templates/shopping.html底部添加script srchttps://cdn.jsdelivr.net/npm/chart.js/script canvas idratingChart width400 height200/canvas script const ctx document.getElementById(ratingChart).getContext(2d); new Chart(ctx, { type: bar, data: { labels: [1星, 2星, 3星, 4星, 5星], datasets: [{ label: 评分分布, data: {{ rating_distribution|safe }}, // 由视图传入[5, 3, 12, 25, 48] backgroundColor: [#ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #feca57] }] } }); /script视图中计算分布def shopping_view(request): # ... 其他逻辑 # 统计当前商品评分分布 ratings UserRating.objects.filter(productproduct).values_list(rating, flatTrue) dist [0, 0, 0, 0, 0] for r in ratings: dist[r-1] 1 context[rating_distribution] dist return render(request, shopping.html, context)图表让数据一目了然展示你对用户体验的关注。改造三收藏夹持久化支持未登录用户利用Django Session存储临时收藏def add_to_favorites(request): if request.method POST: product_id request.POST.get(product_id) if not request.user.is_authenticated: # 未登录用户存入session favorites request.session.get(temp_favorites, []) if product_id not in favorites: favorites.append(product_id) request.session[temp_favorites] favorites request.session.modified True else: # 已登录用户存入数据库 UserFavorite.objects.get_or_create( userrequest.user, product_idproduct_id ) return redirect(shopping)这样即使用户未注册也能体验收藏功能提升交互友好度。5. 常见问题与排查技巧实录那些文档里不会写的“血泪经验”5.1 典型问题速查表问题现象可能原因排查命令/方法解决方案访问/shopping/报Page not found (404)urls.py未正确包含shopping.urls检查mysite/urls.py中是否有path(shopping/, include(shopping.urls))在mysite/urls.py的urlpatterns中添加该行登录后跳转到/accounts/profile/但该页面不存在Django默认登录成功跳转URL未配置查看settings.py中是否有LOGIN_REDIRECT_URL /shopping/添加LOGIN_REDIRECT_URL /shopping/封面图shopping_cover.jpg显示为破损图标MEDIA_URL与MEDIA_ROOT配置不匹配或未启用媒体文件服务运行python manage.py showmigrations确认迁移完成检查settings.py中MEDIA_URL是否为/media/在mysite/urls.py末尾添加 static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)评分后推荐列表不更新缓存未失效或推荐函数未被调用在views.py的rate_product函数中print(Cache deleted)重启服务器后点击评分看控制台输出确认cache.delete()在UserRating.objects.create()之后执行检查cache_keys.get_recommendation_cache_key()参数是否为user.id而非request.user对象python manage.py migrate报错no such table: auth_user数据库迁移未按顺序执行运行python manage.py showmigrations查看哪些迁移未应用删除db.sqlite3重新执行python manage.py migrate5.2 独家避坑技巧来自三届毕设指导的真实教训技巧一用django-extensions的runserver_plus替代原生runserver原生runserver在模板语法错误时只报TemplateSyntaxError不显示具体哪一行。安装django-extensions后pip install django-extensions在settings.py的INSTALLED_APPS中添加django_extensions然后用python manage.py runserver_plus它会提供交互式调试器点击错误行即可进入Python Shell直接检查变量值。我曾帮一个学生发现他的get_user_recommendations()函数里user_id传进来是字符串而非整数导致UserRating.objects.filter(user_iduser_id)查不到数据——runserver_plus的调试器让他两分钟就定位到问题。技巧二为UserRating模型添加__str__方法大幅提升Admin调试效率在shopping/models.py中UserRating类里添加def __str__(self): return f{self.user.username} - {self.product.name} ({self.rating}星)这样在Django Admin的User Ratings列表中每条记录直接显示“张三 - iPhone 15 Pro (5星)”而不是枯燥的UserRating object (1)。答辩时老师问“这个评分是谁给的”你点开就能看到无需额外查询。技巧三用django-debug-toolbar实时监控SQL查询安装后在settings.py中配置INSTALLED_APPS [debug_toolbar] MIDDLEWARE [debug_toolbar.middleware.DebugToolbarMiddleware] INTERNAL_IPS [127.0.0.1]启动后页面右下角会出现调试工具栏点击“SQL”标签能看到本次请求执行的所有SQL语句及耗时。当推荐页面加载慢时你可能发现SELECT * FROM shopping_userrating WHERE user_id 5执行了5次——这就是N1查询。解决方案在视图中用select_related(product)预加载商品信息把5次查询合并为1次。技巧四recommend_shopping.py的算法性能瓶颈自查清单协同过滤在用户量1000时会明显变慢。自查以下三点1.索引缺失运行python manage.py dbshell进入SQLite执行CREATE INDEX IF NOT EXISTS idx_userrating_user ON shopping_userrating(user_id);2.未分页get_collaborative_recommendations()中User.objects.exclude(iduser_id)会加载所有用户应改为User.objects.exclude(iduser_id)[:100]限制邻居数量3.重复计算get_user_recommendations()被频繁调用应确保cache.set()的timeout参数合理如300秒避免每秒都重算。最后分享一个小技巧在recommend_shopping.py顶部添加import logging; logger logging.getLogger(__name__)在关键步骤插入logger.info(fStep 2: Found {len(similar_users)} similar users)。然后在settings.py中配置日志输出到控制台。这样每次推荐计算时终端都会打印详细步骤比断点调试更直观——毕竟真正的工程师永远相信日志而不是自己的记忆。这套Django电商推荐系统表面看是一份毕业设计源码深层看它是一套“软件工程最小可行实践”用最克制的技术选型解决最真实的业务问题用最透明的代码结构暴露最本质的工程权衡用最详尽的文档记录最琐碎的踩坑经验。它不承诺成为下一个淘宝推荐引擎但它郑重告诉你从第一行django-admin startproject到用户点击“推荐商品”那一刻的喜悦每一步都踏实可循。本文还有配套的精品资源点击获取简介基于Python和Django开发的可直接运行的电商商品推荐系统内置SQLite数据库db.sqlite3支持用户注册登录、商品浏览、标签筛选、收藏夹管理、星级评分及实时个性化推荐展示。核心推荐逻辑封装在recommend_shopping.py中兼容协同过滤与基于内容的推荐策略前端采用HTMLCSSJS模板login.html、shopping.html等集成自定义templatetags、media资源目录和静态文件static配套shopping_cover封面图与项目演示视频项目演示.m4v。项目结构规范包含完整Django配置settings.py、urls.py、缓存键管理cache_keys.py及依赖清单requirements.txt开箱即用无需额外环境配置。附带详细说明文档说明文件.txt与设计文档压缩包171265889347208773632.zip覆盖需求分析、数据库ER图、算法流程、部署步骤与调试方法。适用于本科毕业设计、课程大作业或推荐系统入门实践也适合教学演示与快速原型验证。本文还有配套的精品资源点击获取