大家好,我是python222_小锋老师,分享一套优质的基于Python的旅游推荐协同过滤算法系统(数据分析及可视化(Django+echarts+爬虫)) 。
项目简介
随着旅游业的快速发展和人们对个性化旅游需求的增加,旅游推荐系统成为提升用户体验和促进旅游市场发展的重要工具。近年来,信息技术在旅游行业中的应用日益广泛,智慧旅游兴起,为游客提供了全新的服务体验。
我国已连续 4 年成为世界第一大出境旅游消费国,对全球旅游收入贡献平均超过 13%。同时,国家旅游局提出我国将争取用 10 年左右时间,初步实现基于信息技术的“智慧旅游”,把旅游业发展成为高信息含量、知识密集的现代服务业。
在这样的背景下,基于 Python+Django+MySQL 的旅游推荐算法协同过滤系统应运而生。该系统旨在利用先进的技术手段,为用户提供精准的旅游推荐服务,满足用户个性化的旅游需求。
源码下载
链接: pan.baidu.com/s/1NeQ-jgTZ…
提取码: 1234
相关截图
核心代码
#render函数用于将数据渲染到指定的模板中,并返回生成的HTML内容
#redirect允许你将用户从一个URL重定向到另一个URL,通常用于处理单表提交、用户登录、注册等操作后的页面跳转
from django.db import models # 核心修正点
from django.db.models import Q
from django.shortcuts import render,redirect
from app.models import User, TravelInfo, Comment
from app.recommdation import getUser_ratings,user_bases_collaborative_filtering
from app.utils import errorResponse,getHomeData,getPublicData,getChangeSelfInfoData,getAddCommentsData,getEchartsData,getRecommendationData
from django.contrib.auth.hashers import check_password # 用于安全验证密码
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib.sessions.backends.db import SessionStore
#todo 新的登录模块代码
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
else:
# todo 新增验证码模块 start
# 新增验证码验证逻辑
captcha_input = request.POST.get('captcha', '').lower().strip() # 获取并处理输入
captcha_session = request.session.get('captcha', '').lower() # 获取session存储值
# 验证码校验(顺序不能颠倒)
if not captcha_input:
return errorResponse.errorResponse(request, '验证码不能为空')
if captcha_input != captcha_session:
return errorResponse.errorResponse(request, '验证码错误')
# 验证通过后立即删除session中的验证码
if 'captcha' in request.session:
del request.session['captcha']
# todo 新增验证码模块 end
username = request.POST.get('username')
password = request.POST.get('password')
# 基本输入校验
if not username or not password:
return errorResponse.errorResponse(request, '用户名和密码不能为空')
try:
# 1. 查询用户是否存在
user = User.objects.get(username=username)
# 2. 使用 check_password 验证密码(与注册时的 make_password 匹配)
if check_password(password, user.password):
# 3. 登录成功:设置 session
request.session['username'] = username
return redirect('/app/home')
else:
# 密码错误
return errorResponse.errorResponse(request, '账号或密码错误')
except User.DoesNotExist:
# 用户不存在
return errorResponse.errorResponse(request, '账号或密码错误')
#todo 新的注册模块代码
def register(request):
if request.method == 'GET':
return render(request, 'register.html')
else:
username = request.POST.get('username')
password = request.POST.get('password')
confirm_password = request.POST.get('confirmPassword')
# 校验输入
if not username or not password or not confirm_password:
return errorResponse.errorResponse(request, '不允许为空值')
if password != confirm_password:
return errorResponse.errorResponse(request, '两次密码不一致')
# 检查用户是否已存在
if User.objects.filter(username=username).exists():
return errorResponse.errorResponse(request, '该账号已存在')
# 创建用户(使用 make_password 加密)
user = User(username=username)
user.set_password(password) # 自动加密
user.save()
return redirect('/app/login')
def logOut(request):
request.session.clear() #退出登录时,清除request.session
return redirect('/app/login') #退出登录 转到登陆页面
def home(request):
username = request.session.get('username')
userInfo = User.objects.get(username=username)
a5Len,commentsLenTitle,provienceDicSort = getHomeData.getHomeTagData()
scoreTop10Data,saleCountTop10 = getHomeData.getAnthorData()
year,mon,day = getHomeData.getNowTime()
geoData = getHomeData.getGeoData()
userBarCharData = getHomeData.getUserCreateTimeData()
#字典
return render(request,'home.html',{
'userInfo':userInfo,
'a5Len':a5Len,
'commentsLenTitle':commentsLenTitle,
'provienceDicSort':provienceDicSort,
'scoreTop10Data':scoreTop10Data,
'nowTime':{
'year':year,
'mon':getPublicData.monthList[mon - 1],
'day':day
},
'geoData':geoData,
'userBarCharData':userBarCharData,
'saleCountTop10':saleCountTop10
})
def changeSelfInfo(request):
username = request.session.get('username')
userInfo = User.objects.get(username=username)
year,mon,day = getHomeData.getNowTime()
if request.method == 'POST':
getChangeSelfInfoData.changeSelfInfo(username,request.POST,request.FILES)
userInfo = User.objects.get(username=username)
return render(request,'changeSelfInfo.html',{
'userInfo':userInfo,
'nowTime': {
'year': year,
'mon': getPublicData.monthList[mon - 1],
'day': day
},
})
#todo 新的修改密码代码
def changePassword(request):
# 验证登录状态
username = request.session.get('username')
if not username:
return redirect('/app/login')
try:
userInfo = User.objects.get(username=username)
except User.DoesNotExist:
return errorResponse.errorResponse(request, '用户不存在')
if request.method == 'POST':
# 获取表单数据(字段名称与前端完全一致)
old_password = request.POST.get('oldPassword', '').strip() # 注意这里是 oldPassword
new_password = request.POST.get('newPassword', '').strip() # 注意这里是 newPassword
confirm_password = request.POST.get('newPasswordConfirm', '').strip() # 注意这里是 newPasswordConfirm
# 字段非空校验
if not all([old_password, new_password, confirm_password]):
return errorResponse.errorResponse(request, '所有字段不能为空')
# 验证旧密码
if not userInfo.check_password(old_password):
return errorResponse.errorResponse(request, '原密码错误')
# 验证新密码一致性
if new_password != confirm_password:
return errorResponse.errorResponse(request, '两次新密码不一致')
# 更新密码
userInfo.set_password(new_password)
userInfo.save()
# 清除会话并跳转登录
request.session.flush()
return redirect('/app/home')
# GET请求处理(保持原有时间逻辑)
year, mon, day = getHomeData.getNowTime()
return render(request, 'changePassword.html', {
'userInfo': userInfo,
'nowTime': {
'year': year,
'mon': getPublicData.monthList[mon - 1],
'day': day
},
})
# todo 新增搜索功能和分页功能
def tableData(request):
# 用户信息获取
username = request.session.get('username')
try:
userInfo = User.objects.get(username=username)
except User.DoesNotExist:
# 处理用户不存在的情况,这里可以重定向到登录页
return redirect('login')
# 获取当前时间
year, mon, day = getHomeData.getNowTime()
# 获取初始查询集(确保返回的是QuerySet)
try:
# 如果getAllTravelInfoMapData返回的是QuerySet
queryset = getPublicData.getAllTravelInfoMapData()
# 保险检查:如果不是QuerySet则重新获取
if not isinstance(queryset, models.QuerySet):
queryset = TravelInfo.objects.all()
except Exception as e:
# 处理异常情况,使用备用查询方式
queryset = TravelInfo.objects.all()
# 处理搜索关键词
keyword = request.GET.get('keyword', '').strip()
if keyword:
queryset = queryset.filter(
Q(title__icontains=keyword) |
Q(province__icontains=keyword) |
Q(level__icontains=keyword)
).distinct()
# 分页处理(带安全限制)
paginator = Paginator(queryset, 10) # 每页10条
page_number = request.GET.get('page', 1)
# 安全处理页码参数
try:
page_number = int(page_number)
if page_number < 1 or page_number > 10: # 限制最多显示10页
page_number = 1
except (ValueError, TypeError):
page_number = 1
try:
talbeData = paginator.page(page_number)
except EmptyPage:
# 如果页码超出范围,返回最后一页
talbeData = paginator.page(paginator.num_pages)
# 构造上下文
context = {
'userInfo': userInfo,
'nowTime': {
'year': year,
'mon': getPublicData.monthList[mon - 1],
'day': day
},
'talbeData': talbeData,
'search_keyword': keyword,
'paginator': paginator
}
return render(request, 'tableData.html', context)
def addComments(request,id):
username = request.session.get('username')
userInfo = User.objects.get(username=username)
year, mon, day = getHomeData.getNowTime()
travelInfo = getAddCommentsData.getTravelById(id)
# todo 修改部分
# 修改查询条件:删除user过滤,保留景点过滤
history_comments = Comment.objects.filter(
travel=travelInfo
).select_related('user').order_by('-created_at') # 使用模型默认排序,可省略
if request.method == 'POST':
# 修改后的保存逻辑
rate = request.POST.get('rate')
content = request.POST.get('content', '').strip()
error = None
if not rate:
error = "请选择评分!"
elif not content:
error = "评论内容不能为空!"
else:
try:
rate = int(rate)
if rate < 1 or rate > 5:
error = "评分必须在1-5分之间!"
except ValueError:
error = "无效的评分值!"
if error:
return render(request, 'addComments.html', {
'history_comments': history_comments,
'userInfo': userInfo,
'nowTime': {
'year': year,
'mon': getPublicData.monthList[mon - 1],
'day': day
},
'travelInfo': travelInfo,
'id': id,
'error': error
})
Comment.objects.create(
user=userInfo,
travel=travelInfo,
rate=rate,
content=content
)
return redirect('/app/tableData')
return render(request, 'addComments.html', {
'history_comments': history_comments,
'userInfo': userInfo,
'nowTime': {
'year': year,
'mon': getPublicData.monthList[mon - 1],
'day': day
},
'travelInfo':travelInfo,
'id':id,
})
# todo 添加新的功能,根据省份去查询
# 修改后的视图函数
def cityChar(request):
username = request.session.get('username')
userInfo = User.objects.get(username=username)
year, mon, day = getHomeData.getNowTime()
# 获取所有省份(去重并排序)
provinces = sorted(TravelInfo.objects.values_list('province', flat=True).distinct())
# 获取筛选条件
selected_province = request.GET.get('province', '')
# 修改点:直接传递省份参数而不是字典
Xdata, Ydata = getEchartsData.cityCharDataOne(province=selected_province)
resultData = getEchartsData.cityCharDataTwo(province=selected_province)
return render(request, 'cityChar.html', {
'userInfo': userInfo,
'provinces': provinces,
'nowTime': {
'year': year,
'mon': getPublicData.monthList[mon - 1],
'day': day
},
'cityCharOneData': {
'Xdata': Xdata,
'Ydata': Ydata
},
'cityCharTwoData': resultData
})
def rateChar(request):
username = request.session.get('username')
userInfo = User.objects.get(username=username)
year, mon, day = getHomeData.getNowTime()
cityList = getPublicData.getCityList()
travelList = getPublicData.getAllTravelInfoMapData(cityList[0])
charOneData = getEchartsData.getRateCharDataOne(travelList)
charTwoData = getEchartsData.getRateCharDataTwo(travelList)
if request.method == 'POST':
travelList = getPublicData.getAllTravelInfoMapData(request.POST.get('province'))
charOneData = getEchartsData.getRateCharDataOne(travelList)
charTwoData = getEchartsData.getRateCharDataTwo(travelList)
return render(request, 'rateChar.html', {
'userInfo': userInfo,
'nowTime': {
'year': year,
'mon': getPublicData.monthList[mon - 1],
'day': day
},
'cityList':cityList,
'charOneData':charOneData,
'charTwoData':charTwoData
})
# todo 价格和月销量分析新增加省份功能
def priceChar(request):
username = request.session.get('username')
userInfo = User.objects.get(username=username)
year, mon, day = getHomeData.getNowTime()
# 获取原始数据(直接使用ORM查询集)
queryset = TravelInfo.objects.all() # 直接获取模型对象列表
# 处理省份筛选(不区分大小写)
province = request.GET.get('province', '').strip()
if province:
queryset = queryset.filter(province__icontains=province)
# 直接传递模型对象列表(不要转成字典)
travel_list = list(queryset) # 这里是关键改动!!!
# 生成图表数据
xData, yData = getEchartsData.getPriceCharDataOne(travel_list)
x1Data, y1Data = getEchartsData.getPriceCharDataTwo(travel_list)
disCountPieData = getEchartsData.getPriceCharDataThree(travel_list)
return render(request, 'priceChar.html', {
'userInfo': userInfo,
'nowTime': {
'year': year,
'mon': getPublicData.monthList[mon - 1],
'day': day
},
'echartsData': {
'xData': xData,
'yData': yData,
'x1Data': x1Data,
'y1Data': y1Data,
'disCountPieData': disCountPieData
}
})
# todo 新增根据省份去查询数据
def commentsChar(request):
username = request.session.get('username')
userInfo = User.objects.get(username=username)
year, mon, day = getHomeData.getNowTime()
province = request.GET.get('province') # 获取省份参数
# 根据省份参数获取对应数据
if province:
xData, yData = getEchartsData.getCommentsCharDataOne(province)
commentsScorePieData = getEchartsData.getCommentsCharDataTwo(province)
x1Data, y1Data = getEchartsData.getCommentsCharDataThree(province)
else:
xData, yData = getEchartsData.getCommentsCharDataOne()
commentsScorePieData = getEchartsData.getCommentsCharDataTwo()
x1Data, y1Data = getEchartsData.getCommentsCharDataThree()
return render(request, 'commentsChar.html', {
'userInfo': userInfo,
'nowTime': {
'year': year,
'mon': getPublicData.monthList[mon - 1],
'day': day
},
'echartsData': {
'xData': xData,
'yData': yData,
'commentsScorePieData': commentsScorePieData,
'x1Data': x1Data,
'y1Data': y1Data
}
})
def recommendation(request):
username = request.session.get('username')
userInfo = User.objects.get(username=username)
year, mon, day = getHomeData.getNowTime()
try:
user_ratings = getUser_ratings()
recommended_items = user_bases_collaborative_filtering(userInfo.id, user_ratings)
resultDataList = getRecommendationData.getAllTravelByTitle(recommended_items)
except:
resultDataList = getRecommendationData.getRandomTravel()
return render(request, 'recommendation.html', {
'userInfo': userInfo,
'nowTime': {
'year': year,
'mon': getPublicData.monthList[mon - 1],
'day': day
},
'resultDataList':resultDataList
})
def detailIntroCloud(request):
username = request.session.get('username')
userInfo = User.objects.get(username=username)
year, mon, day = getHomeData.getNowTime()
return render(request, 'detailIntroCloud.html', {
'userInfo': userInfo,
'nowTime': {
'year': year,
'mon': getPublicData.monthList[mon - 1],
'day': day
}
})
def commentContentCloud(request):
username = request.session.get('username')
userInfo = User.objects.get(username=username)
year, mon, day = getHomeData.getNowTime()
return render(request, 'commentContentCloud.html', {
'userInfo': userInfo,
'nowTime': {
'year': year,
'mon': getPublicData.monthList[mon - 1],
'day': day
}
})
# todo 添加图片验证码功能
from django.http import HttpResponse
from django.views.decorators.http import require_GET
from .utils.captcha import generate_captcha
from io import BytesIO
@require_GET
def captcha_view(request): # 重命名视图函数
# 生成验证码
text, image = generate_captcha() # 直接调用函数
# 将验证码文本存入session
request.session['captcha'] = text
# 生成图片响应
buf = BytesIO()
image.save(buf, format='PNG')
return HttpResponse(buf.getvalue(), content_type='image/png')