Django智能旅游推荐系统全栈毕设项目深度拆解
发布时间:2026/10/25 10:06:06 锦皓数字建站

简介这是一份基于Python的智能旅游推荐系统毕业设计完整项目面向计算机相关专业学生在毕业设计、课程设计或期末大作业中快速落地实践。项目基于Django框架构建前端以HTML、CSS、Vue、JavaScript为主后端由Python实现开发环境为PyCharm数据库可视化使用Navicat并附有SQL脚本与安装运行批处理真正做到开箱即用。压缩包内共797个文件涵盖45个Python源码文件、164个JavaScript脚本、53个Vue组件、53个HTML页面、53个CSS样式表以及图片、字体、SVG图标等静态资源整体约20.19MB目录结构清晰便于二次开发与系统学习。项目功能完善、界面美观经过严格调试确保可运行目前已有227人学习下载是毕业设计答辩和课程实践的高性价比参考方案。1. 智能旅游推荐系统能直接跑起来的 Django 全栈毕设长什么样一个 python 智能旅游推荐系统放在毕业设计里到底能做到什么程度这份资源给我的答案是Django 做后端Vue 写法的前端页面配好数据库脚本和三四个一键启动的 .bat 文件解压之后按顺序点两下就能在本地跑起来。它不是那种只给你一段算法代码的玩具而是完整的、能演示、能答辩的全栈项目。对正在找 python 毕业设计、课程设计或期末大作业的人来说这意味着你不需要从零搭框架也不用到处拼代码拿到手先跑通再按自己的思路改。全文我会把项目结构、数据表设计、推荐逻辑、运行方式和常见报错都拆开讲清楚。2. Django 数据模型与数据库初始化先把景点和用户表建对2.1 项目结构与前后端两层代码怎么摆这个项目的目录和普通 Django 教程项目不太一样。常见教学项目是纯 Django templates 模板渲染而这份资源里出现了 index.html.bak、IndexMain.vue.bak、IndexAsideStatic.vue.bak 这类文件。.vue后缀说明前端原本是用 Vue 写的.bak是备份文件对应的是 index.html、IndexMain.vue、IndexAsideStatic.vue、BreadCrumbs.vue、IndexHeader.vue 等原始组件。典型做法是前端用 Vue 开发后台管理界面build 打包后把静态文件交给 Django 托管所以最终你看到的 index.html 其实是打包产物。这种结构的直接好处是前后端职责清晰Django 只管接口和数据页面交互全交给 Vue。你改后端逻辑不用动页面改页面样式也不用碰 Python 代码。对毕业设计来说答辩时老师问你前后端怎么交互的你直接说Vue 通过 axios 调 Django 的 REST 接口数据以 JSON 格式返回这句话本身就值不少分。2.2 核心数据表设计与 Navicat 导入旅游推荐系统的核心表按我一贯拆项目的经验至少会包含这几张用户表、景点表、收藏表、评论表。用户表可以直接用 Django 自带的 auth_user也可以自己建。景点表是重头戏字段一般包含景点名称、所在城市、景点类别、评分、热度、门票价格、介绍、封面图。收藏表和评论表用来支撑推荐逻辑和用户行为分析。用 Navicat 导入数据库脚本时我习惯先看一下 SQL 文件里的建表语句确认表名和字段再导入。下面是一份简化版景点表的建表参考和这个项目的设计思路基本一致CREATE TABLE scenic_spot ( id int NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 景点名称, city varchar(50) DEFAULT NULL COMMENT 所在城市, category varchar(50) DEFAULT NULL COMMENT 景点类别, score float DEFAULT 0 COMMENT 评分, heat int DEFAULT 0 COMMENT 热度值, price decimal(8,2) DEFAULT 0.00 COMMENT 门票价格, description text COMMENT 景点介绍, image varchar(200) DEFAULT NULL COMMENT 封面图路径, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表语句里utf8mb4很重要。很多人在 Navicat 里导入 SQL 报错就是因为脚本里写的是utf8而数据库默认字符集不匹配。utf8mb4能完整存下 Emoji 和生僻字景点介绍里偶尔会出现特殊符号用这个字符集最稳。另外heat字段我建议用int而不是varchar因为后面排序推荐要靠它。2.3 从 .bak 备份文件看前端组件结构文件列表里那串.bak不是没用的垃圾文件它们暴露了前端页面的组织方式。update-password.vue 是修改密码页IndexMain.vue 是主内容区IndexAsideStatic.vue 是左侧静态菜单栏BreadCrumbs.vue 是面包屑导航IndexHeader.vue 是顶部头部。这是一个标准后台管理布局左边菜单、右边内容、顶部导航、底部面包屑。为什么会有 .bak我推测是作者改版时保留了旧版本方便回退。你拿到项目后如果改前端改坏了把 .bak 复制一份改名回去就能恢复相当于自带后悔药。这提醒我一件事动手改任何文件之前先复制一份 .bak 出来哪怕项目里没有自己也该养成这个习惯。数据库层面除了景点表还要看收藏表和评论表是否导入了。推荐功能依赖收藏关系如果没有收藏数据推荐逻辑跑起来就是空的。我一般会在 Navicat 里手动插入几条测试收藏数据比如让用户 A 收藏故宫和颐和园用户 B 收藏故宫这样调试推荐接口时能立刻看到效果。3. 核心业务与推荐逻辑登录、景点管理和推荐结果怎么串起来3.1 登录鉴权与会话控制后台管理系统的第一道门是登录。这个项目前端有 update-password.vue说明它至少包含登录和修改密码两个基础功能。Django 生态里做登录鉴权主流方案有两种一是用 Django 自带的django.contrib.auth配合 session二是用django-rest-framework的 JWT 认证。毕业设计项目用前者居多因为代码量少、依赖少、调通容易。典型登录接口的写法大致是这个思路from django.contrib.auth import authenticate, login from django.http import JsonResponse def user_login(request): if request.method POST: username request.POST.get(username) password request.POST.get(password) user authenticate(request, usernameusername, passwordpassword) if user is not None: login(request, user) return JsonResponse({code: 0, msg: 登录成功, data: {username: user.username}}) return JsonResponse({code: 1, msg: 用户名或密码错误})authenticate负责校验用户名和密码是否匹配login负责把登录状态写进 session。这里有个细节Django 的authenticate默认只对is_active为 True 的用户返回有效对象所以如果你在数据库里直接插用户记录而没设置is_active密码对也登不进去。我遇到不少人在这一步翻车。修改密码逻辑也很直接拿到当前用户对象调用set_password再save即可。注意set_password之后必须调用save()否则密码只存在内存里没写进数据库刷新页面又变回老密码。这个坑我帮别人排查过好几回现象就是密码改成功了但退出登录后用新密码登不上。3.2 景点信息的增删改查与检索后台管理的核心价值是把景点数据管起来。所谓数据库增删改查在 Django 里就是 ORM 的create、filter、update、delete。景点管理页面对应前端 IndexMain.vue 里的内容区域通常会有一个表格支持按名称、城市、类别搜索支持分页支持编辑和删除。一个完整的景点列表接口考虑分页和搜索写法大致如下from django.core.paginator import Paginator from django.http import JsonResponse from .models import ScenicSpot def spot_list(request): keyword request.GET.get(keyword, ) city request.GET.get(city, ) spots ScenicSpot.objects.all() if keyword: spots spots.filter(name__icontainskeyword) if city: spots spots.filter(citycity) paginator Paginator(spots, 10) page request.GET.get(page, 1) page_obj paginator.get_page(page) data [{ id: s.id, name: s.name, city: s.city, score: s.score, heat: s.heat, price: str(s.price), } for s in page_obj] return JsonResponse({code: 0, data: data, total: paginator.count})name__icontains是 Django ORM 里的模糊查询i表示忽略大小写。这里字段名直接取列表里的字段接口返回给前端前端在表格里渲染。分页参数page由前端传入Paginator每页 10 条。你拿到源码后想改每页条数直接改Paginator(spots, 10)里的 10 就行。新增和更新景点可以用 Django 的ModelForm或者直接objects.update_or_create视具体实现而定。删除我更建议用软删除即给表加一个is_delete字段删除时把状态置为 1列表查询时过滤掉。这是血泪经验管理员手滑删错景点如果是物理删除数据直接没了想恢复只能靠数据库备份软删除至少还能捞回来。3.3 推荐逻辑基于热度和用户行为排序智能推荐是这份资源的卖点。真正的推荐系统会用协同过滤、内容推荐、甚至深度学习模型但毕业设计在演示层面跑得通、讲得清才是第一位的。我看了这个项目的文件结构没有额外的算法库依赖所以最可能的实现是基于收藏行为和景点热度做排序。一种常见且合理的简化方案是用户收藏过某些类别的景点就给用户推荐同类别里热度高、评分高的其它景点。这样既利用了用户行为数据又不需要复杂的矩阵计算答辩时也容易解释。from .models import ScenicSpot, Favorite def recommend_for_user(user): # 找出用户收藏过的景点类别 fav_categories ( Favorite.objects.filter(useruser) .values_list(spot__category, flatTrue) .distinct() ) if not fav_categories: # 没有收藏记录就按全站热度推 return ScenicSpot.objects.order_by(-heat, -score)[:10] # 排除已收藏的按热度和评分排序 recommended ( ScenicSpot.objects .exclude(favorite__useruser) .filter(category__infav_categories) .order_by(-heat, -score)[:10] ) return recommended这段逻辑的关键是exclude(favorite__useruser)它把用户已经收藏过的景点过滤掉避免推荐了用户早就收藏过的地方。.distinct()防止用户收藏同一个类别的多个景点导致重复推荐。order_by(-heat, -score)先按热度排热度相同再按评分排负号表示倒序。如果你想升级推荐效果一个低成本的改法是把只看类别改成类别 城市双维度加权。比如用户收藏了北京的自然风光推荐时就优先推北京其他自然风光其次才推其他城市的。这个改动不需要动数据表只是在 filter 条件上多加一个 OR 条件。前端拿到推荐接口返回的列表后渲染成卡片或者列表图片和介绍从对应字段取值。推荐接口调用时机一般是用户登录后进入首页时触发所以前端会有一次初始请求后端根据当前 session 里的用户计算推荐结果。4. 环境搭建与一键运行PyCharm 里把项目拉起来4.1 Python 环境与依赖安装拿到项目第一步是确认本机 Python 版本。Django 项目对 Python 版本有要求Django 2.x 支持 Python 3.5 到 3.8Django 3.x 要求 Python 3.6 以上。这份资源自带安装.bat点开会执行依赖安装我建议不要直接双击而是先右键编辑看看里面的命令是什么。常见的安装脚本内容是echo off pip install -r requirements.txt echo 依赖安装完成 pauserequirements.txt里一般会列 Django、mysqlclient 或 pymysql、djangorestframework 等。如果你用的是 Python 3.10 以上版本注意mysqlclient可能装不上常见做法是改装pymysql然后在项目__init__.py里加上一句import pymysql pymysql.install_as_MySQLdb()这句的作用是把 PyMySQL 伪装成 MySQLdbDjango 才能正常连 MySQL。不装或者不导入这行运行迁移命令时会直接报ModuleNotFoundError: No module named MySQLdb。我自己习惯用 PyMySQL因为它纯 Python 实现不需要编译 C 扩展Windows 上省心很多。4.2 数据库连接配置与迁移打开项目的 settings.py找到 DATABASES 配置块确认连接参数和本地一致。最常见的配置是DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: travel_db, USER: root, PASSWORD: 123456, HOST: 127.0.0.1, PORT: 3306, } }NAME是数据库名必须在 Navicat 里先创建好Django 不会自动建库。USER和PASSWORD按你本机的 MySQL 账号改。这里有一个高频报错如果 MySQL 密码里有特殊字符比如或#直接在配置里写字符串容易解析出错解决办法是在 PyCharm 里检查字符串有没有被正确转义。配置改好后打开终端执行python manage.py makemigrations python manage.py migratemakemigrations会根据 models.py 生成迁移文件migrate把表建到数据库里。如果项目自带了数据库脚本你会发现在脚本导入后表结构和 models.py 是对得上的migrate 只会生成少量系统表。如果对不上migrate 可能报错需要根据报错信息判断是缺字段还是多字段。4.3 一键脚本各自干什么资源里有两个运行相关的脚本2-run.bat和运行.bat还有一个3-build.bat。它们的分工我按一般项目习惯推断一下3-build.bat是前端构建作用是执行npm install和npm run build把 Vue 源码打包成静态文件2-run.bat和运行.bat很可能一个是启动 Django 后端、一个是启动前端开发服务器的。运行脚本最常见的形态是echo off python manage.py runserver 0.0.0.0:8000如果你不想用脚本直接在 PyCharm 终端里手动执行这行命令也行。0.0.0.0表示监听所有网卡这样同一局域网下其他设备也能访问演示时方便用手机连接看效果。如果只想本机访问用python manage.py runserver 8000就够了。我这里要强调一个点运行.bat 里如果写的是npm run dev说明前端是开发模式启动和 Django 是两个端口比如 Django 占 8000Node 占 8080。开发模式下前端通过代理转发请求到后端这种配置在 Vue 的vue.config.js里会有一段 devServer.proxy。如果你直接打开 index.html 而不是走前端服务器接口请求会 404。所以拿到项目后先确认你要用哪种方式演示不要混着来。5. 避坑指南毕设跑不通时先查这五处5.1 数据库连接失败Navicat 却明明能连上现象双击运行.bat 启动后浏览器访问页面报OperationalError: (1045, Access denied for user rootlocalhost)但用 Navicat 用同一个账号密码却能连上。原因settings.py 里的密码和本机 MySQL 实际密码不一致或者密码包含特殊字符被 Python 转义问题干扰。另一个可能是项目里用的数据库名不存在报错会变成Unknown database xxx。解决先在 Navicat 里用配置的账号密码测试连接确认能通。然后在 PyCharm 里单独跑一行命令验证 Django 侧连接python -c import django; import os; os.environ.setdefault(DJANGO_SETTINGS_MODULE, 你的项目名.settings); django.setup(); from django.db import connection; connection.ensure_connection(); print(连接成功)提示这段命令里的你的项目名要换成实际的 settings 模块名。如果能输出连接成功说明配置没问题。5.2 页面能打开但样式全丢图片全是裂图现象页面结构在但 CSS、JS、图片都加载不出来控制台一堆 404。原因Django 没有正确配置静态文件路径。前端 build 出来的静态文件没有放到 Django 能识别的位置或者 settings.py 里STATIC_URL、STATICFILES_DIRS没配对。解决打开 settings.py 检查静态文件配置STATIC_URL /static/ STATICFILES_DIRS [ BASE_DIR / static, ]如果前端文件已经 build 到了 static 目录再执行一次收集python manage.py collectstatic --noinput注意开发环境这个命令不是必需的Django 会自动找STATICFILES_DIRS里的文件。5.3 端口被占用导致启动失败现象运行.bat 执行后立刻退出终端提示Error: That port is already in use。原因上次启动的服务没有关干净端口被残留进程占住。解决Windows 下用命令查占用netstat -ano | findstr :8000看到 LISTENING 行后面对应的 PID到任务管理器里结束对应进程或者用命令强杀taskkill /PID 那个PID /F我建议每次演示完都正常按 Ctrl C 关闭服务避免残留进程。5.4 导入数据库脚本时中途报错现象Navicat 导入 .sql 文件执行到一半报Invalid utf8 character string或Unknown collation。原因SQL 文件里的字符集或排序规则和当前 MySQL 版本不兼容。常见是脚本里写了utf8mb4_unicode_ci而本地 MySQL 版本较老不支持或者脚本文件本身编码不是 UTF-8。解决用 Notepad 或 VS Code 打开 .sql 文件确认右下角编码是 UTF-8如果不是先转码保存。把文件开头的SET NAMES utf8改成SET NAMES utf8mb4再去掉或替换掉不支持的排序规则。改完再重新导入。5.5 前端 build 后接口请求 404现象执行 3-build.bat 打包后打开 index.html 页面登录接口请求地址是http://localhost:8080/api/login但后端跑在 8000 端口请求打到前端服务器上直接 404。原因前端代码里接口地址写的是相对路径本地开发依赖 devServer 代理转发打包后没有正确配置接口域名。解决打开前端源码里的请求配置文件常见是src/utils/request.js或.env.production把baseURL改成后端地址import axios from axios const service axios.create({ baseURL: http://127.0.0.1:8000, // 改成 Django 实际地址 timeout: 10000 })改完重新 build或者用开发模式启动前端。这个坑几乎每个前后端分离项目都会遇到提前改好能省很多时间。6. 答辩与二次开发把推荐结果做出差异化6.1 把推荐逻辑升级成协同过滤基础版推荐按类别和热度排序够用但不够智能。如果想让答辩更有亮点可以把它升级成基于用户的协同过滤。原理不复杂找到与当前用户行为最相似的其他用户把那些用户收藏过而当前用户没收藏过的景点推出来。用 Django ORM 写一个简化版def recommend_collaborative(user, top_n10): # 找到用户收藏过的景点 my_favs set( Favorite.objects.filter(useruser) .values_list(spot_id, flatTrue) ) if not my_favs: return ScenicSpot.objects.order_by(-heat)[:top_n] # 找到收藏过这些景点的其他用户 similar_users ( Favorite.objects .filter(spot_id__inmy_favs) .exclude(useruser) .values_list(user_id, flatTrue) .distinct() ) # 把这些用户收藏的其他景点统计出来按出现次数排序 from django.db.models import Count candidates ( Favorite.objects .filter(user_id__insimilar_users) .exclude(spot_id__inmy_favs) .values(spot_id) .annotate(cntCount(spot_id)) .order_by(-cnt)[:top_n] .values_list(spot_id, flatTrue) ) return ScenicSpot.objects.filter(id__incandidates)这个算法用annotate(cntCount(spot_id))统计候选景点的支持人数支持人数越多说明和你相似的人越喜欢。答辩时你要能说清楚这不是基于内容的推荐而是基于用户行为的协同过滤。这一句话就能把你和只会抄 CRUD 的同学区分开。6.2 用 ECharts 给后台加一个数据看板旅游推荐系统如果只有表格和列表视觉上会显得单薄。一个性价比极高的加分项是在首页加一个统计看板展示城市景点分布、类别占比、热度榜前五。前后端分离的架构下前端用 ECharts 是非常成熟的方案。后端只需提供一个汇总接口from django.db.models import Count from django.http import JsonResponse from .models import ScenicSpot def spot_stats(request): category_stats ( ScenicSpot.objects .values(category) .annotate(countCount(id)) .order_by(-count) ) return JsonResponse({ code: 0, data: { categories: [item[category] for item in category_stats], counts: [item[count] for item in category_stats], } })前端在 IndexMain.vue 里引入 ECharts请求这个接口渲染一个饼图出来。整个改动约等于新增一个接口加一个组件但演示效果提升非常明显。老师看到的不再是干巴巴的数据而是可视化呈现这个印象分很重要。6.3 答辩演示的完整路径答辩演示环节我建议你提前走一遍完整流程别等到现场才开项目。顺序可以固定为第一步展示登录页讲清楚认证方式第二步进入首页讲解推荐结果说明推荐依据是什么最好提前准备好几条收藏数据让推荐结果有内容可讲第三步演示景点管理现场新增一条景点记录刷新列表让老师看到数据变化第四步打开 Navicat对照数据库表讲解数据结构。这四个环节串起来从功能到数据到算法逻辑全部覆盖到了。演示前强制检查三件事数据库服务有没有启动Django 服务有没有启动前端页面能不能正常打开。很多现场翻车都出在我昨天还能跑这句话上因为 MySQL 服务没开机自启换了一台电脑就忘了装依赖。从那以后我每次演示前都会强制走一遍这套检查流程再拿一份离线备份放到 U 盘里万一环境坏了还有后悔药。希望帮到你祝答辩顺利。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。