ARTICLE DETAIL

资讯详情

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

Django+MySQL旅游推荐系统实战:从数据库设计到协同过滤

Django+MySQL旅游推荐系统实战:从数据库设计到协同过滤 简介这份论文资源以Python旅游推荐系统为选题完整覆盖毕业设计论文从绪论到结论的全部章节。面向需要撰写系统开发类论文的高校本科生及毕业设计开发者内容围绕Django框架、MySQL数据库及协同过滤与混合推荐算法展开系统阐述B/S三层架构、数据库ER建模、用户管理、旅游信息展示、推荐算法和用户反馈模块的设计实现并给出功能测试、单元测试与性能测试思路。文档包含封面、中英文摘要、目录、章节正文、参考文献等完整内容章节中进一步展开选题背景、开发现状分析、课题目的与意义、技术介绍及系统测试结论可直接作为论文写作框架和系统设计参考。资源为单个docx格式文档压缩包共1个文件、大小约1.6MB排版清晰便于查看和二次编辑。目前已有332人学习适合正在开展旅游推荐系统课题或需要快速搭建论文框架的学习者。1. 推荐系统项目的日常数据表比算法模型更先决定成败做旅游推荐系统很多人第一反应是上协同过滤、深度学习模型但真到一个用 Django MySQL 搭起来的毕设系统里最耗时的不是算法而是数据怎么组织、页面怎么取数、推荐结果怎么解释。这套系统不是一个花哨的算法 demo而是一套完整的 B/S 架构应用景点信息管理、酒店预定、行程分享、交流论坛都在里面推荐只是其中一环。对正在选毕设方向的学生、准备用 Python 做业务站点的开发者来说这套设计比单抠算法更接近真实的业务系统。我把它当一套可以照着改的工程样例来拆从建库、建模到推荐、验证每一步都落到能跑的代码上。2. Django MySQL为什么这套选型撑得起旅游推荐系统2.1 框架选型Django 的大而全在推荐系统里意味着什么旅游推荐系统的核心不是推荐算法本身而是围绕景点、酒店、用户行为的数据管理。项目里选 Python 没有悬念但 Web 框架有得挑Flask 轻量灵活适合做接口服务Tornado 擅长异步高并发适合实时推送场景Django 则把 ORM、模板引擎、Admin 后台、认证权限、Session 全部内置遵循 MVC实际是 MTV设计模式Model 控制数据层Template 管表现层View 处理业务逻辑。框架数据建模后台管理推荐系统适配度Flask需自行集成 SQLAlchemy无自由需拼装Tornado需自行集成无异步优先非刚需Django自带 ORM Migrations自带 Admin高内置用户行为关联Flask 写推荐算法原型可以但一个系统要同时管用户、景点、酒店、收藏、留言模型关联关系要手写一堆开发成本反而上升。Tornado 的优势在 IO 密集旅游推荐系统没有实时推送需求异步框架的调试成本对毕设并不友好。Django 把常用组件集成好开发速度最快也有自己的 Admin 数据管理后台这正是毕设和中小型业务站点选它的直接原因。2.2 三层架构在 Django 工程里的映射B/S 模式的三层架构——表现层、业务逻辑层、数据访问层落到 Django 里分别是 templates 目录、views.py 和 models.py。表现层的模板渲染出标准 HTML对跨浏览器兼容性友好Chrome、Edge、Firefox 打开效果一致这也解决了同类 Web 项目常见的浏览器差异问题。数据访问层的入口在 settings.py配置 MySQL 连接的代码是典型的 Django 写法# settings.py DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: travel_db, USER: root, PASSWORD: 123, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }ENGINE 指定 MySQL 驱动django.db.backends.mysql 会让 Django 通过数据库适配层连接 MySQLNAME 是库名USER 和 PASSWORD 对应 MySQL 账号HOST 写 127.0.0.1 表示代码和数据库在同一台机器charset 用 utf8mb4是因为景点名称、酒店介绍里可能有生僻字和特殊符号用 utf8 会在写入时触发编码错误。光配好 settings 还不算完Python 3.x 下还需要在项目__init__.py里声明使用 pymysql 接管 MySQLdb 接口# travel_project/__init__.py import pymysql pymysql.install_as_MySQLdb()install_as_MySQLdb()是一个兼容层把 pymysql 伪装成 MySQLdb否则 Django 在连接 MySQL 时会报No module named MySQLdb。项目原文里提到通过 winMysqladmin 安装和启动 MySQL 服务走的是免安装解压版这类环境下最容易踩的坑是服务没启动就执行迁移命令后面第三节会专门说排查顺序。2.3 项目骨架与依赖清单推荐系统的工程目录建议按业务拆 app而不是把全部逻辑塞进一个模块。常见划分如下路径职责travel_project/settings.py全局配置含数据库、静态文件travel_project/urls.py根路由apps/scenic景点信息与推荐模块apps/hotel酒店管理与预定apps/users用户认证与会员管理templatesDjango 模板对应 B/S 表现层依赖清单 requirements.txt 只需要两行核心内容Django pymysql版本不写死建议按本地环境安装 LTS 版本Django 的 LTS 版本维护周期长社区资料多遇到报错更容易搜到解决方案。pymysql 是纯 Python 驱动pip 直接装就能用避免在 Windows 上编译 mysqlclient 的麻烦。数据库连接就绪后下一步就是设计核心数据表这才是推荐系统的地基。3. 数据库设计从 E-R 图到 Django 模型的完整过程3.1 核心数据表用户、景点、收藏、留言从系统需求分析出发这套系统的表结构覆盖了用户、景点内容、行为反馈、公告内容四类数据。项目原文给出的数据库表设计是理解整个系统的关键其中 storeup 收藏表尤其重要它是推荐算法最直接的数据来源。表名核心字段在整个系统里的作用usersusername, password, role用户认证与角色区分jingdianfenxijingdianmingcheng, gonglvetidaoshuliang, dianpingshuliang, xingji景点信息与热度统计storeupuserid, refid, tablename, type收藏与用户行为记录hotel酒店名称、地址、价格酒店管理与预定messagesuserid, content用户留言反馈newstitle, content系统公告景点分析表里攻略提到数量和点评数量是热度推荐最重要的两个计数星级用于展示口径storeup 表的 type 字段区分收藏、赞、踩、关注为推荐算法提供细粒度的正负反馈信号。酒店表在原论文里只有字段描述实现时通常补上酒店名称、地址、价格、房型等列满足前台展示和预定的需要。3.2 用 Django ORM 表达外键与多表关联数据库表设计成 ER 图之后转换成 Django 模型是顺理成章的事。模型定义如下# apps/scenic/models.py from django.db import models class User(models.Model): username models.CharField(max_length100, uniqueTrue) password models.CharField(max_length100) role models.CharField(max_length50, default用户) class Scenic(models.Model): name models.CharField(景点名称, max_length200) description models.TextField(攻略, blankTrue) mention_count models.IntegerField(攻略提到数量, default0) comment_count models.IntegerField(点评数量, default0) rating models.FloatField(星级, default4.5) class Storeup(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name用户) scenic models.ForeignKey(Scenic, on_deletemodels.CASCADE, verbose_name景点, nullTrue, blankTrue) table_name models.CharField(max_length200, blankTrue) type models.IntegerField(收藏类型, default1)ForeignKey 的 on_deletemodels.CASCADE 表示用户或景点删除后对应的收藏记录一并删除避免出现孤儿数据nullTrue 和 blankTrue 允许某条收藏记录暂时不关联景点对象兼容原表用 refid 和 tablename 标记关联对象的通用收藏设计。verbose_name 用于 Admin 后台和表单页面显示中文名称在 Django 的 MTV 模式里这直接决定了管理后台的界面语言。原设计里 storeup 表用 refid 加 tablename 的方式同时收藏景点、酒店、攻略扩展性强但 ORM 查询时无法直接用外键关联需要自己判断 table_name 再拼接条件。常见做法是保留这两个字段作为兼容入口同时显式声明 user 和 scenic 外键推荐查询走外键路径省去在代码里拼字符串表名的开销。3.3 迁移命令与 MySQL 连接排错模型定义完成后执行迁移的步骤是固定的python manage.py makemigrations python manage.py migratemakemigrations 会扫描 app 下的 models.py生成迁移文件migrate 把迁移文件应用到数据库。如果数据库里已经存在同名表直接 migrate 会报Table already exists这时用python manage.py migrate --fake把已有表标记为已迁移后续新增字段再正常迁移。提示连接 MySQL 报错 2003 时先确认 MySQL 服务是否启动再检查 3306 端口是否可通最后核对 settings.py 里账号密码。项目原文的 root 密码改成了 123开发环境可以这样生产环境必须换成强密码并限制远程访问。迁移完成后数据库层就绪下一步进入整个系统的核心推荐模块的实现。4. 推荐模块实现权重排序、Jaccard 协同过滤与查询优化4.1 第一版推荐热度权重排序冷启动阶段新用户没有任何收藏和浏览记录协同过滤没有输入数据只能用全局热度兜底。常见策略是把景点按攻略提到数量和点评数量加权排序取前 N 条展示。# apps/recommend/views.py from django.db.models import F from django.shortcuts import render from apps.scenic.models import Scenic def hot_scenic(request): scenic_list ( Scenic.objects .annotate(weightF(mention_count) F(comment_count)) .order_by(-weight)[:10] ) return render(request, recommend/hot.html, {list: scenic_list})annotate 在数据库层计算 weight 字段避免把全部景点数据拉到 Python 内存再排序。F(mention_count) 直接引用数据库列不会产生 Python 对象的复制开销order_by(-weight) 按权重倒序负号表示降序切片 [:10] 在数据库层翻译成 LIMIT 10只取前十条。推荐策略数据要求冷启动表现实现成本热度排序无需用户行为好低Jaccard 协同过滤需要收藏或评分差中热度 协同过滤混合行为数据积累后切换一般较高这个表是选型时的判断依据。热度排序适合系统上线初期行为数据积累到一定量级后协同过滤才有意义混合策略在真实产品里最常用但毕设系统里先做协同过滤已经能说明问题。4.2 基于收藏行为的 Jaccard 协同过滤storeup 表积累了一段时间的收藏数据后可以做基于用户的协同过滤。核心思路是用户 A 收藏了景点 x、y用户 B 收藏了 y、z公共项是 y于是把 z 推荐给 A把 x 推荐给 B。用集合相似度衡量用户偏好的接近程度Jaccard 是简洁且解释性强的选择。# apps/recommend/collaborative.py from apps.scenic.models import Storeup def jaccard(set_a, set_b): if not set_a or not set_b: return 0.0 inter len(set_a set_b) union len(set_a | set_b) return inter / union def recommend_by_user(user_id, top_n5): my_scenic_ids set( Storeup.objects.filter(user_iduser_id) .values_list(scenic_id, flatTrue) ) if not my_scenic_ids: return [] others ( Storeup.objects .exclude(user_iduser_id) .values(user_id) .annotate(cntCount(id)) .filter(cnt__gte2) .order_by(-cnt)[:50] ) scores {} for row in others: other_id row[user_id] other_scenic_ids set( Storeup.objects.filter(user_idother_id) .values_list(scenic_id, flatTrue) ) common my_scenic_ids other_scenic_ids union my_scenic_ids | other_scenic_ids sim jaccard(common, union) if sim 0: continue for sid in other_scenic_ids - my_scenic_ids: scores[sid] scores.get(sid, 0) sim ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return [sid for sid, _ in ranked[:top_n]]values_list(scenic_id, flatTrue) 返回扁平的 id 列表比循环 ORM 对象快一个量级exclude 把当前用户的行为记录排除避免自己和自己比较annotate(cntCount(id)) 统计每个用户的收藏次数filter(cnt__gte2) 过滤掉只有一条收藏的用户因为样本太少时相似度没有参考价值。这段实现里最需要注意的参数是 sim 的计算。Jaccard 的分母是并集大小这个选择和余弦相似度相比有一个明显优势高频用户不会因为收藏数量多而获得虚高的相似度。一个收藏 200 个景点和一个收藏 20 个景点的用户即使共同收藏 10 个景点相似度也只有 10 / 210 左右不会被放大。在毕设数据量几百个用户的情况下接口响应基本在毫秒级。4.3 推荐结果的查询优化协同过滤算出来的是景点 id 列表回查景点详情时要注意查询效率。直接用 for 循环一个个查会触发 N1 查询正确做法是一次 in 查询取回所有对象# apps/recommend/views.py def recommend_view(request): user request.user if request.user.is_authenticated else None if user is None: scenic_list Scenic.objects.order_by(-comment_count)[:10] else: ids recommend_by_user(user.id) scenic_list Scenic.objects.filter(id__inids) return render(request, recommend/list.html, {list: scenic_list})未登录用户直接走热门推荐已登录用户先算协同过滤再回表查详情。filter(id__inids) 生成 where id in (...) 的 SQLids 长度一般不超过 10比逐条查询少去 10 次数据库往返。request.user.is_authenticated 是 Django 内置的认证状态判断不需要额外查询数据库。注意如果推荐结果里要展示酒店和帖子协同过滤的输入集合需要按 table_name 拆开不要把不同实体混进同一个 Jaccard 集合否则相似度会被实体类型干扰。5. 管理后台、系统测试与推荐效果验证5.1 Django Admin 把管理员功能变成配置项目原文里的管理员功能模块——景点信息管理、景点分类管理、会员管理、酒店预定管理在 Django 里统一由 Admin 后台承接。注册模型并配置展示字段# apps/scenic/admin.py from django.contrib import admin from .models import Scenic, Storeup admin.register(Scenic) class ScenicAdmin(admin.ModelAdmin): list_display (name, rating, comment_count) search_fields (name,) list_per_page 20 admin.register(Storeup) class StoreupAdmin(admin.ModelAdmin): list_display (user, scenic, type)list_display 决定列表页展示哪些列search_fields 支持对景点名称的模糊搜索list_per_page 控制后台分页。在 /admin 路径下登录管理员账号就能完成景点、用户、收藏记录的增删改查不需要单独写管理页面。5.2 单元测试与性能测试推荐逻辑是纯 Python 函数适合写单元测试# apps/recommend/tests.py from django.test import TestCase from .collaborative import jaccard class RecommendTestCase(TestCase): def test_jaccard(self): self.assertEqual(jaccard({1, 2}, {2, 3}), 1 / 3)执行python manage.py test recommend验证相似度计算。性能测试在积累 5000 条收藏数据后用 apache bench 压一下推荐接口ab -n 1000 -c 20 http://localhost:8000/recommend/观察请求失败率和平均响应时间。5.3 留一法验证推荐命中率推荐效果可以离线验证不需要用户在线参与。留一法把每个用户的收藏列表随机抽出一条用剩余数据计算推荐结果看被抽走的那条是否出现在推荐列表里import random from apps.scenic.models import Storeup from .collaborative import recommend_by_user def leave_one_out(user_id, top_n10): ids list( Storeup.objects.filter(user_iduser_id) .values_list(scenic_id, flatTrue) ) if len(ids) 2: return None random.shuffle(ids) test_item ids[0] # 用剩余数据重新计算推荐列表 recommended recommend_by_user(user_id, top_ntop_n) return test_item in recommended验证的判定标准是命中率命中率稳定在 0.3 以上说明行为数据能支撑推荐低于 0.2问题通常出在相似度计算上先检查 Jaccard 集合里是否混入了非景点数据再确认 storeup 表里收藏和踩的 type 值是否区分清楚。本文还有配套的精品资源点击获取
返回列表