news 2026/9/17 12:43:57

Django构建个性化餐饮推荐系统:协同过滤与ItemCF详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Django构建个性化餐饮推荐系统:协同过滤与ItemCF详解

简介:基于Python和Django的个性化餐饮场所推荐毕业设计项目资源包,面向正在完成课程设计或毕业设计的学生,以及希望学习Django+Vue前后端分离开发的开发者。项目以餐饮场所智能推荐为核心业务,涉及用户管理、餐厅信息维护、推荐逻辑等模块,完整呈现一个Web系统的前后端实现。后端采用Python3.7+与Django框架,前端使用Vue.js和ElementUI,数据库为MySQL 5.7,压缩包内附有SQL脚本和配置说明,导入数据库并调整配置后即可在本机运行体验。压缩包约17.74MB,包含项目源码、数据库文件及说明文档等基础内容,可从中参考Django项目分层结构、ORM模型设计、API接口编写、前端组件调用以及推荐算法的落地方式。目前已有约143人浏览学习,适合作毕业设计起步模板,也可在现有功能上增加协同过滤、实时榜单等进阶模块,提升项目的完整度与创新性。

1. 个性化餐饮场所推荐:为什么是 Django 而不是其他框架

打开外卖 App,同样的餐饮场所,有人看到麻辣烫,有人收到日料店推荐,背后其实是一套基于用户行为的推荐系统在影响排序。把这个逻辑做成“基于 Python 的个性化餐饮场所推荐”的 Django 毕业设计,核心就三件事:用户行为数据建模、餐饮场所的相似度计算、推荐结果的可视化展示。相比 Flask 或 FastAPI,Django 自带 ORM、Admin 后台和模板引擎,省去自己拼 SQL 和写前端页面的时间,非常适合课程设计这类需要快速跑通完整闭环的项目。本文从 PyCharm 打开项目开始,把推荐算法、数据库迁移、页面联调到参数调优这条线捋一遍。

2. Django 项目结构与推荐算法选型:从相似度计算到用户画像

2.1 个性化推荐的常用路径:基于内容过滤与协同过滤

做餐饮场所推荐,先别急着写代码,得想清楚推荐依据是什么。最常用的两类思路:基于内容过滤(Content-Based)和协同过滤(Collaborative Filtering)。

基于内容过滤的思路是“找相似的店”:根据菜品类别、人均价格、地理位置、评分等标签,计算目标用户喜欢过的店与候选店的相似度。优点是冷启动容易,新店只要有标签就能被推荐,不依赖用户历史行为;缺点是不会跨品类推荐,用户天天吃麻辣烫就永远看不到日料。

协同过滤的思路是“找相似的人”:基于用户的浏览、收藏、下单行为构造矩阵,通过用户行为向量的余弦相似度或皮尔逊相关系数找到相似用户,再推荐相似用户喜欢的店。这类方法更贴近“个性化”,但冷启动阶段如果某个用户一条行为都没有,推荐结果会退化成热门榜。

本项目标题既然强调“个性化”,通常走基于物品的协同过滤(ItemCF)更合理,因为它对用户行为量要求不高,且有明确的相似度参数可以调整,适合毕业设计展示算法过程。下面我会用 Django 的 app 结构把这条链路实现出来。

2.2 Django 项目如何组织推荐模块:app 划分与数据流

一个餐饮推荐项目,我一般会拆三个 app:users处理登录注册与用户画像,restaurant维护餐饮场所的基础数据,recommend负责相似度计算和推荐输出。标题里提到的“含数据库”,指的就是restaurantrecommend里建好的 ORM 模型以及初始化的行为数据。

数据流按这个顺序理解:用户访问餐饮场所详情页 → 前端记录一次行为(浏览/收藏/下单) → 写入Behavior表 → 推荐视图读取该表,构建用户-餐饮场所行为矩阵 → 计算候选餐饮场所与已行为场所的相似度 → 返回 Top-N 列表 → 模板渲染。整个过程里,Behavior表是整个推荐系统的燃料,它的字段设计直接影响后续算法效果。

一个可落地的模型设计如下:

# restaurant/models.py from django.db import models from django.contrib.auth.models import User class Restaurant(models.Model): # 店铺编号,项目配套数据里类似 99sdi 开头的样例 code = models.CharField(max_length=20, unique=True, verbose_name="店铺编号") name = models.CharField(max_length=100, verbose_name="店铺名称") category = models.CharField(max_length=50, db_index=True, verbose_name="餐饮类别") avg_price = models.DecimalField(max_digits=6, decimal_places=1, verbose_name="人均价格") rating = models.FloatField(default=4.0, verbose_name="综合评分") opening_hours = models.CharField(max_length=100, blank=True, verbose_name="营业时间") class Meta: db_table = "restaurant_restaurant" verbose_name = "餐饮场所" class Behavior(models.Model): ACTION_CHOICES = [ ("view", "浏览"), ("favorite", "收藏"), ("order", "下单"), ] user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="behaviors") restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE, related_name="behaviors") action = models.CharField(max_length=10, choices=ACTION_CHOICES, db_index=True) action_weight = models.IntegerField(default=1, verbose_name="行为权重") created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = "restaurant_behavior" verbose_name = "用户行为"

逻辑说明:action_weight直接参与相似度计算,浏览记 1、收藏记 2、下单记 3,这样不同动作对用户偏好的贡献就有了主次。db_index=True加在categoryaction上,因为后续查询要么按类别筛选,要么按行为类型过滤,没有索引的话数据量大了会明显变慢。code字段用于对应项目附件里那种带99sdi编号的初始化数据,导入时用update_or_create防止重复。

2.3 基于物品的协同过滤参数详解

ItemCF 的核心是:对每个用户已产生行为的餐饮场所,计算它与所有未行为场所的余弦相似度,然后按相似度加权求和,取 Top-N 作为推荐结果。下面这个工具函数是整条链路的关键:

# recommend/utils.py from math import sqrt from restaurant.models import Restaurant, Behavior def build_user_item_matrix(): """构建用户-餐饮场所行为权重矩阵,返回 {user_id: {restaurant_id: weight}}""" behaviors = Behavior.objects.all().values("user_id", "restaurant_id", "action_weight") matrix = {} for b in behaviors: matrix.setdefault(b["user_id"], {}) matrix[b["user_id"]][b["restaurant_id"]] = ( matrix[b["user_id"]].get(b["restaurant_id"], 0) + b["action_weight"] ) return matrix def cosine_similarity(vec_a, vec_b): """余弦相似度,vec_a 和 vec_b 是 {restaurant_id: weight} 形式的字典""" if not vec_a or not vec_b: return 0.0 common = set(vec_a) & set(vec_b) if not common: return 0.0 dot = sum(vec_a.get(k, 0) * vec_b.get(k, 0) for k in common) norm_a = sqrt(sum(v * v for v in vec_a.values())) norm_b = sqrt(sum(v * v for v in vec_b.values())) if norm_a == 0 or norm_b == 0: return 0.0 return round(dot / (norm_a * norm_b), 4) def recommend_for_user(user, top_n=10, min_similarity=0.15): """为用户返回推荐餐饮场所的主键列表,无行为时退回热门榜""" matrix = build_user_item_matrix() user_vec = matrix.get(user.id) if not user_vec: # 冷启动兜底:评分降序 return list(Restaurant.objects.order_by("-rating").values_list("id", flat=True)[:top_n]) sim_scores = {} user_items = set(user_vec.keys()) all_items = Restaurant.objects.exclude(id__in=user_items).values_list("id", flat=True) for candidate_id in all_items: candidate_vec = {} for uid, vec in matrix.items(): if candidate_id in vec: candidate_vec[uid] = vec[candidate_id] # 候选场所的列向量与用户行为向量计算余弦相似度 sim = cosine_similarity(user_vec, candidate_vec) if sim >= min_similarity: sim_scores[candidate_id] = sim sorted_ids = sorted(sim_scores.keys(), key=lambda x: sim_scores[x], reverse=True) return sorted_ids[:top_n]

这段代码里有几个参数值得细说:

build_user_item_matrix()每次推荐都会把Behavior表全量读进内存,在几千条行为数据下没问题,但如果想提升性能,可以在返回时用django.core.paginator分批处理,或利用cache缓存矩阵结果。min_similarity是相似度阈值,设得太低会把大量无关餐饮场所拉进候选集,设得太高又容易导致推荐结果过少。我在本地测试时,500 家店、1 万条行为数据下,0.15是一个平衡点,你可以用下面这段脚本快速验证不同阈值对结果数量的影响:

# manage.py shell 中执行 from django.contrib.auth.models import User from recommend.utils import recommend_for_user from restaurant.models import Restaurant u = User.objects.filter(behavior__isnull=False).distinct().first() for threshold in [0.05, 0.15, 0.3]: ids = recommend_for_user(u, min_similarity=threshold) print(threshold, len(ids))

top_n则直接决定推荐页展示条数,通常 10 到 20 条。这个参数在后续视图中也可以通过 HTTP 查询参数暴露出来,方便演示评分、覆盖率等推荐指标。

下面给出相似度函数的对比,方便你在报告中解释选型理由:

相似度函数适用场景本项目适用性
余弦相似度向量长度差异不敏感时推荐,行为权重是整数,计算简单,最常用
皮尔逊相关系数需要中心化用户评分差异不推荐,餐饮行为不是显式评分,中心化无意义
杰卡德相似度只看是否共同评分,不看权重可选,作为用户行为覆盖率的补充指标

3. 用 PyCharm 跑通含数据库的 Django 项目:环境、迁移与初始化数据

3.1 环境准备:Python 安装、Django 安装与 PyCharm 配置

拿到项目先在本地把环境铺好。Python 版本建议 3.9 到 3.11,Django 用 4.2 LTS,这两个版本搭配最稳。系统里没有 Python 的先下载安装包,安装时勾选Add python.exe to PATH,避免后面在命令行找不到python命令。

项目导入 PyCharm 时注意选择虚拟环境,不要直接使用全局解释器。PyCharm 打开项目后,在File -> Settings -> Project -> Python Interpreter里点击Add Interpreter,选择Virtualenv Environment -> New,基解释器选系统 Python。这一步如果漏了,后面执行pip install装错环境是常见问题,典型表现是命令行里runserver能用但 PyCharm 里报ModuleNotFoundError: No module named 'django'

在 PyCharm 的 Terminal 面板里安装依赖:

python -m venv venv venv\Scripts\activate # Windows # 或 source venv/bin/activate # Linux / macOS pip install django==4.2 pip install mysqlclient # 如果数据库使用 MySQL pip install pandas # 用于读取初始化 CSV 数据

参数说明:venv是虚拟环境目录名,可以随意改成.venvmysqlclient是针对 Python 3 的 MySQL 驱动,如果你使用的是 SQLite 可以不装;pandas只在导入初始化数据阶段需要,如果项目里提供了.sql数据文件,可以省略这一步。

3.2 配置 settings.py 连接数据库并完成数据增删改查

项目根目录的settings.py是数据库配置的核心文件。用 SQLite 起步最简单,也就是标题项目中“含数据库”的最小实现:

# settings.py DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3", } } INSTALLED_APPS = [ # ... "restaurant.apps.RestaurantConfig", "recommend.apps.RecommendConfig", ]

如果项目提供的是northwind_django.sql这种结构比较复杂的数据脚本,一般里面会有创建表和数据插入的语句。你需要先确认它是 MySQL 方言还是 SQLite 方言,再决定用哪种库接入。

接入 MySQL 时,settings.py改成这样:

DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "restaurant_db", "USER": "root", "PASSWORD": "你的密码", "HOST": "127.0.0.1", "PORT": "3306", "OPTIONS": { "charset": "utf8mb4", "init_command": "SET sql_mode='STRICT_TRANS_TABLES'", }, } }

参数说明:utf8mb4是必须的,否则餐饮场所名称里出现 Emoji 表情时写入会报Incorrect string value错误;init_command里的sql_mode配置会在连接时生效,避免 MySQL 5.7 在某些 SQL 写法下静默截断数据。HOSTPORT按实际 MySQL 服务地址改,localhost127.0.0.1的差异在于:前者走 socket 连接,后者走 TCP,如果你的 MySQL 只监听了其中一种方式,换着试试。

接着执行数据库迁移和初始化命令:

python manage.py makemigrations restaurant recommend python manage.py migrate python manage.py createsuperuser python manage.py runserver 8000

逻辑说明:makemigrations会扫描models.py里定义的模型,生成迁移文件;migrate把迁移文件应用到数据库,创建实际的数据表。如果项目附带了.sql数据文件,建议先执行migrate重建空表,再用数据库管理工具导入。如果直接在已有数据的库上执行migrate,表结构冲突时会出现table already exists之类的报错,这属于数据库课程设计里最常见的坑之一。

导入初始化数据时,推荐用 Django 的loaddata配合 Fixture JSON 文件,而不是直接灌 SQL,因为 Fixture 会走 ORM 校验,字段类型不对时能直接抛异常,方便排查。

3.3 关联 PyCharm 调试工具与 Django 命令行

PyCharm 里不能直接双击main.py启动 Django 项目,正确做法是配置 Django 运行配置。在Run -> Edit Configurations -> + -> Django Server,Name 填runserver,Host 填127.0.0.1,Port 填8000,注意把Python interpreter指向刚才创建的虚拟环境。

如果你在 Linux 环境或国产化系统上部署,比如麒麟系统,命令行方式更通用:

python manage.py runserver 0.0.0.0:8000

0.0.0.0表示监听所有网卡,这样局域网内其他机器可以通过本机 IP 访问到推荐页面,方便在手机上模拟不同用户视角。

4. 推荐接口与页面联调:Django 视图、模板与前后端交互

4.1 输出推荐结果的视图层设计与代码

推荐视图是整个 Django 项目是否好改的关键。我习惯把所有查询逻辑放utils.py,视图保持薄薄的:只取参数、调用函数、返回响应。这样后期想在recommend_for_user里加缓存或改用实时训练结果时,不会牵连视图。

# recommend/views.py from django.shortcuts import render from django.views.decorators.http import require_GET from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator from restaurant.models import Restaurant from recommend.utils import recommend_for_user @require_GET @login_required def recommend_list(request): top_n = int(request.GET.get("top_n", 10)) if top_n > 20: top_n = 20 ids = recommend_for_user(request.user, top_n=top_n) restaurants = Restaurant.objects.filter(id__in=ids) # 保持推荐顺序,避免 order_by 打乱相似度排序 order_map = {rid: pos for pos, rid in enumerate(ids)} items = sorted(restaurants, key=lambda x: order_map.get(x.id, 999)) paginator = Paginator(items, 5) page_obj = paginator.get_page(request.GET.get("page")) return render( request, "recommend/list.html", {"page_obj": page_obj, "top_n": top_n}, )

逻辑说明:top_n从查询字符串读取,但限制最大 20,防止别人用?top_n=100000拖垮数据库。按相似度排序后的ids列表已经确定了推荐顺序,但filter(id__in=ids)返回的结果集是乱序的,必须用order_map重新排序,否则每次刷新页面看到的顺序都不一样,这是推荐系统页面里容易忽略的细节。

如果你不想强制登录,把@login_required换成自定义判断:

from django.shortcuts import get_object_or_404 user = request.user if request.user.is_authenticated else None

这样匿名用户进入页面时会走recommend_for_user里的冷启动分支,返回热门餐饮场所榜单,也不难看。

4.2 模板引擎渲染推荐列表

Django 模板语言自动对变量做 HTML 转义,所以直接输出餐饮场所名称是不需要担心 XSS 的,但如果在模板里写了|safe或者autoescape off,就要确保内容来自受控字段。

<!-- recommend/templates/recommend/list.html --> <ul class="restaurant-list"> {% for r in page_obj %} <li class="restaurant-item"> <h3>{{ r.name }}</h3> <p>类别:{{ r.category }} | 人均:{{ r.avg_price }}</p> <p>评分:{{ r.rating }} | 营业时间:{{ r.opening_hours }}</p> </li> {% empty %} <li>暂无可推荐的餐饮场所。</li> {% endfor %} </ul> <div class="pagination"> {% if page_obj.has_previous %} <a href="?page={{ page_obj.previous_page_number }}&top_n={{ top_n }}">上一页</a> {% endif %} {% if page_obj.has_next %} <a href="?page={{ page_obj.next_page_number }}&top_n={{ top_n }}">下一页</a> {% endif %} </div>

分页参数要通过?page=拼接,同时把top_n也带过去,否则用户从第 2 页回到第 1 页时推荐条数会变回默认值。这个细节在答辩演示时容易暴露出来。

4.3 调试与排错:推荐接口联调中的常见报错

推荐功能联调阶段最常见的报错集中在 ORM 查询和数据库连接上,下面这张表基本覆盖了 PyCharm 里跑 Django 项目的高频问题:

报错信息原因处理方式
No module named 'django'PyCharm 解释器没指向虚拟环境在 Settings 里重新选择venv下的 Python 解释器
Table '...' doesn't exist忘记执行migrate检查Show all tables或直接用数据库工具确认
django.db.utils.OperationalError: no such table: restaurant_behavior迁移目标没有覆盖相关 app执行python manage.py migrate restaurant指定 app 名
'NoneType' object has no attribute 'restaurant_id'用户对象不存在request.user为空时先走登录页或兜底热门榜
Method Not Allowed (GET)视图无法处理 GET 请求@require_POST改成接受 GET,或者在urls.py里配两个路由

以上大部分错误在 PyCharm 的 Run 窗口里就能看到完整堆栈,点击蓝色文件链接可以直接跳到出错行,这点比命令行调试方便很多。如果要在浏览器里看实时 SQL 日志,可以在settings.py里额外加:

LOGGING = { "version": 1, "handlers": {"console": {"class": "logging.StreamHandler"}}, "loggers": {"django.db.backends": {"handlers": ["console"], "level": "DEBUG"}}, }

这样每次推荐查询都会在控制台打印对应的 SQL 语句,方便核对filter是否生成了预期的JOIN。如果项目要用宝塔面板部署到 Linux,这一步查到的 SQL 能直接帮助判断要不要给Behavior表加联合索引。

5. 推荐效果验证与优化技巧

想要让毕业设计在答辩时不只停留在“能跑”,可以从冷启动、查询性能、结果可解释性三个方向做验证和优化。

冷启动是餐饮场景里最现实的问题:新用户没有行为数据时,recommend_for_user已经通过order_by("-rating")降级成了热门榜。你可以进一步做一个混合策略:对行为数少于 3 条的用户,推荐同样 category 下评分最高的餐饮场所,而对老用户才启用相似度计算。实现上只需在recommend_for_user开头判断行为矩阵长度,切到分类热门查询即可。

性能优化方面,build_user_item_matrix每次请求都会全表扫描,最有效的改造是引入 Django cache。推荐结果在 10 分钟内的变化其实很小,用cache.set把结果缓存起来,用户量上来后能少打 90% 的查询:

from django.core.cache import cache def recommend_for_user_cached(user, top_n=10, timeout=600): cache_key = f"reco:{user.id}:{top_n}" ids = cache.get(cache_key) if ids: return ids ids = recommend_for_user(user, top_n=top_n) cache.set(cache_key, ids, timeout) return ids

缓存键必须包含user.idtop_n,否则不同用户会拿到同一份推荐。加缓存后要手动验证一下用户对某家店下单后,推荐结果是否还在 10 分钟内保持不变,这正好符合推荐系统“实时行为触发而非全量重算”的常见设计。

结果可解释性也是加分项。在推荐列表模板里给每个餐饮场所旁边增加一个“相似来源”字段,显示“根据你最近收藏的某家店推荐”。这个数据可以在recommend_for_user返回候选列表时,同时记录相似度最高的来源餐饮场所 ID,并存成字典返回。答辩时评委看到这种细节,基本上就说明你对推荐链路是真吃透了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 12:40:21

OpenClaw 报网络错误?TaoToken 的接口 Base URL 这样填

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 12:39:58

PTV3:序列化点云Transformer如何实现更简单、更快、更强

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 12:39:26

心理咨询师水平评价考试时间安排与备考节奏-中国心理学会心理咨询师水平评价-心理咨询师培训机构-长春心理咨询师培训机构-意心技能课堂

心理咨询师水平评价考试时间安排与备考节奏了解考试时间安排&#xff0c;制定合理的备考节奏&#xff0c;是顺利通过心理咨询师水平评价的重要前提。很多考生因为对考试时间和备考节奏把握不当&#xff0c;导致准备不充分或临时抱佛脚。本文将为你详细梳理考试时间安排和科学的…

作者头像 李华
网站建设 2026/9/17 12:38:59

如何 30 秒看懂 SQLFluff:SQL 代码检查与格式化入门指南

如何 30 秒看懂 SQLFluff&#xff1a;SQL 代码检查与格式化入门指南 【免费下载链接】sqlfluff A modular SQL linter and auto-formatter with support for multiple dialects and templated code. 项目地址: https://gitcode.com/GitHub_Trending/sq/sqlfluff 写 SQL …

作者头像 李华
网站建设 2026/9/17 12:38:32

如何通过心理咨询师证书实现斜杠人生-中国心理学会心理咨询师水平评价-心理咨询师培训机构-长春心理咨询师培训机构-意心技能课堂

如何通过心理咨询师证书实现斜杠人生-中国心理学会心理咨询师水平评价-心理咨询师培训机构-长春心理咨询师培训机构-意心技能课堂 "斜杠青年"已经成为当下年轻人追求的理想生活方式。在众多斜杠技能中&#xff0c;心理咨询师因为其专业性强、灵活度高、社会价值大等特…

作者头像 李华
网站建设 2026/9/17 12:38:01

Multisim 14.3安装失败根因与Windows兼容性解决方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华