1. 项目概述:旅游推荐系统的技术架构与核心价值
这个基于Python的旅游数据分析推荐系统,本质上是一个融合了数据采集、清洗、存储、分析和智能推荐的全栈项目。作为计算机专业的毕业设计选题,它完美覆盖了Web开发、数据爬取、算法应用和可视化展示这四大核心技能模块。
系统采用Django作为基础框架,这是Python领域最成熟的Web开发框架之一。选择Django而非Flask或FastAPI的主要原因在于其"开箱即用"的特性——自带的Admin后台、ORM系统和模板引擎,能大幅降低开发复杂度。对于毕业设计这类需要快速验证的项目,这种"全包式"框架能让学生把精力集中在核心业务逻辑上,而不是重复造轮子。
数据采集层使用requests库构建爬虫模块,这是Python生态中最基础的HTTP请求库。虽然Scrapy等专业框架功能更强大,但requests+BeautifulSoup的组合对初学者更友好,也足以应对旅游数据的采集需求。实测中,单线程爬取携程、马蜂窝等主流旅游网站时,requests的稳定性完全能满足毕业设计的数据量要求。
推荐算法采用协同过滤(Collaborative Filtering)作为核心,这是推荐系统领域最经典也最容易理解的算法之一。相比内容推荐或深度学习方案,协同过滤的实现门槛更低,且能直观展示"用户-物品"矩阵的数学原理。对于需要答辩的毕业设计,这种可解释性强的算法更容易获得导师认可。
可视化部分通常集成ECharts或Pyecharts,这两个库都能与Django无缝配合。它们提供的折线图、热力图和地图组件,特别适合展示旅游数据中的季节趋势、热门景点分布等关键信息。
提示:在实际开发中,建议先用Jupyter Notebook单独测试每个模块(爬虫、算法、可视化),确认无误后再集成到Django项目中。这种模块化开发方式能有效降低调试难度。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建与Django项目初始化
2.1 Python环境配置
推荐使用Python 3.8+版本,这是目前最稳定的Django兼容版本。避免使用Python 3.10+,某些依赖库可能存在兼容性问题。安装时务必勾选"Add Python to PATH",这是后续pip安装依赖的关键步骤。
创建虚拟环境是必须的:
bash复制python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate.bat # Windows
核心依赖库安装清单:
bash复制pip install django==3.2.16 # LTS版本
pip install requests beautifulsoup4 # 爬虫基础
pip install numpy pandas # 数据处理
pip install scikit-learn # 协同过滤算法
pip install pyecharts # 可视化
2.2 Django项目骨架搭建
使用标准命令创建项目和应用:
bash复制django-admin startproject travel_recommend
cd travel_recommend
python manage.py startapp recommend
关键目录结构调整建议:
code复制travel_recommend/
├── recommend/ # 主应用
│ ├── spiders/ # 爬虫模块
│ ├── algorithms/ # 推荐算法
│ ├── templates/ # 前端模板
│ └── views.py # 业务逻辑
├── static/ # 静态资源
└── data/ # 原始数据集
在settings.py中需要特别关注的配置:
python复制INSTALLED_APPS = [
'recommend.apps.RecommendConfig', # 注册应用
]
# 静态文件配置
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
# 媒体文件配置(用于存储用户上传)
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
踩坑记录:Django 3.x版本默认关闭了ASGI支持,如果后续需要接入WebSocket实现实时推荐,需手动安装channels并修改asgi.py配置。
3. 旅游数据爬虫开发实战
3.1 目标网站分析与反爬策略
以携程景点数据为例,通过浏览器开发者工具(F12)分析请求规律。关键发现:
- 列表页URL格式:https://you.ctrip.com/sight/{城市拼音}/{页码}.html
- 详情页包含:景点名称、评分、评论数、门票价格、标签等关键字段
- 主要反爬机制:请求频率限制(429状态码)、User-Agent验证
应对方案代码示例:
python复制import requests
from time import sleep
from fake_useragent import UserAgent
def get_with_retry(url, max_retries=3):
ua = UserAgent()
headers = {'User-Agent': ua.random}
for _ in range(max_retries):
try:
resp = requests.get(url, headers=headers, timeout=10)
if resp.status_code == 200:
return resp.text
elif resp.status_code == 429:
sleep(2 ** _) # 指数退避
except Exception as e:
print(f"Request failed: {e}")
sleep(5)
return None
3.2 数据清洗与存储模型设计
爬取到的原始数据需要经过以下处理流程:
- 缺失值处理:评分缺失时用同类景点平均值填充
- 异常值过滤:门票价格超过3倍标准差的数据剔除
- 文本清洗:使用正则表达式去除评论中的特殊符号
Django模型设计示例(models.py):
python复制from django.db import models
class ScenicSpot(models.Model):
name = models.CharField(max_length=100)
city = models.CharField(max_length=50)
rating = models.FloatField(default=0)
price = models.IntegerField(null=True)
tags = models.JSONField(default=list) # 存储标签数组
description = models.TextField()
class Meta:
indexes = [
models.Index(fields=['city']),
models.Index(fields=['rating']),
]
class UserBehavior(models.Model):
user_id = models.IntegerField()
spot = models.ForeignKey(ScenicSpot, on_delete=models.CASCADE)
behavior_type = models.SmallIntegerField( # 1-浏览 2-收藏 3-购买
choices=[(1, 'View'), (2, 'Favorite'), (3, 'Purchase')]
)
created_at = models.DateTimeField(auto_now_add=True)
经验分享:对于旅游数据,地理位置信息至关重要。建议集成django-geoposition或PostGIS扩展,便于后续实现基于位置的推荐。
4. 协同过滤推荐算法实现
4.1 用户-景点评分矩阵构建
从用户行为日志中提取隐式评分:
- 浏览记录:+1分
- 收藏记录:+3分
- 购买记录:+5分
使用pandas构建稀疏矩阵:
python复制import pandas as pd
from recommend.models import UserBehavior
def build_rating_matrix():
behaviors = UserBehavior.objects.all().values()
df = pd.DataFrame(behaviors)
# 行为类型映射为分数
behavior_score = {1: 1, 2: 3, 3: 5}
df['score'] = df['behavior_type'].map(behavior_score)
# 构建(user, spot)评分矩阵
rating_matrix = df.pivot_table(
index='user_id',
columns='spot_id',
values='score',
fill_value=0
)
return rating_matrix
4.2 基于用户的协同过滤实现
使用scikit-learn的cosine_similarity计算用户相似度:
python复制from sklearn.metrics.pairwise import cosine_similarity
def user_based_recommend(user_id, rating_matrix, top_n=5):
# 计算用户相似度矩阵
user_sim = cosine_similarity(rating_matrix)
# 获取目标用户的相似用户
sim_users = user_sim[user_id].argsort()[::-1][1:] # 排除自己
# 加权平均相似用户的评分
recommendations = {}
for spot in rating_matrix.columns:
if rating_matrix.loc[user_id, spot] == 0: # 只推荐未交互的
weighted_sum = 0
sim_sum = 0
for sim_user in sim_users[:10]: # 取前10个相似用户
if rating_matrix.loc[sim_user, spot] > 0:
weighted_sum += user_sim[user_id, sim_user] * rating_matrix.loc[sim_user, spot]
sim_sum += user_sim[user_id, sim_user]
if sim_sum > 0:
recommendations[spot] = weighted_sum / sim_sum
# 返回TopN推荐
return sorted(recommendations.items(), key=lambda x: x[1], reverse=True)[:top_n]
4.3 冷启动问题解决方案
对于新用户或新景点,采用混合推荐策略:
- 基于内容的推荐:使用景点标签的TF-IDF计算相似度
- 热门推荐:选择当前城市评分最高的前5个景点
- 随机推荐:当数据不足时展示随机景点
实现代码片段:
python复制from sklearn.feature_extraction.text import TfidfVectorizer
def content_based_recommend(spot_id, top_n=5):
spots = ScenicSpot.objects.all().values('id', 'tags')
spot_df = pd.DataFrame(spots)
# 将标签列表转为字符串
spot_df['tags_str'] = spot_df['tags'].apply(lambda x: ' '.join(x))
# 计算TF-IDF特征
tfidf = TfidfVectorizer()
tfidf_matrix = tfidf.fit_transform(spot_df['tags_str'])
# 计算余弦相似度
from sklearn.metrics.pairwise import linear_kernel
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)
# 获取相似景点
idx = spot_df[spot_df['id']==spot_id].index[0]
sim_scores = list(enumerate(cosine_sim[idx]))
sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
sim_indices = [i[0] for i in sim_scores[1:top_n+1]]
return spot_df.iloc[sim_indices]['id'].tolist()
5. 数据可视化与系统集成
5.1 Pyecharts可视化实现
景点评分分布雷达图示例:
python复制from pyecharts import options as opts
from pyecharts.charts import Radar
def create_radar_chart(spot_id):
spot = ScenicSpot.objects.get(id=spot_id)
data = [
{
"value": [spot.rating, spot.comment_count/100, spot.price/50],
"name": spot.name
}
]
radar = (
Radar()
.add_schema(
schema=[
opts.RadarIndicatorItem(name="评分", max=5),
opts.RadarIndicatorItem(name="热度", max=10),
opts.RadarIndicatorItem(name="价格", max=500),
]
)
.add("景点指标", data)
.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
)
return radar.render_embed()
5.2 Django模板集成
在template中引入ECharts:
html复制<!-- recommend/templates/recommend/detail.html -->
{% extends "base.html" %}
{% block content %}
<div id="radar" style="width: 600px;height:400px;"></div>
<script>
// 从Django视图传递图表配置
const chart_data = {{ chart_json|safe }};
const chart = echarts.init(document.getElementById('radar'));
chart.setOption(chart_data);
</script>
{% endblock %}
5.3 推荐结果展示优化
使用Django的ListView和DetailView快速构建界面:
python复制# recommend/views.py
from django.views.generic import ListView, DetailView
from .models import ScenicSpot
class SpotListView(ListView):
model = ScenicSpot
template_name = 'recommend/list.html'
paginate_by = 10
def get_queryset(self):
city = self.request.GET.get('city')
if city:
return ScenicSpot.objects.filter(city=city)
return ScenicSpot.objects.all()
class SpotDetailView(DetailView):
model = ScenicSpot
template_name = 'recommend/detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['chart_json'] = create_radar_chart(self.object.id)
return context
6. 系统部署与性能优化
6.1 生产环境部署方案
推荐使用Nginx + Gunicorn组合:
bash复制# 安装Gunicorn
pip install gunicorn
# 启动命令(在项目根目录)
gunicorn --workers 4 --bind 0.0.0.0:8000 travel_recommend.wsgi:application
Nginx配置示例(/etc/nginx/sites-available/travel):
nginx复制server {
listen 80;
server_name your_domain.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /static/ {
alias /path/to/your/static/;
}
}
6.2 缓存策略优化
使用Django-redis实现多级缓存:
- 视图层缓存:缓存整个推荐结果页面
- 数据层缓存:缓存热门景点查询结果
- 算法层缓存:缓存用户相似度矩阵
配置示例(settings.py):
python复制CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
# 视图缓存装饰器示例
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # 缓存15分钟
def spot_detail(request, pk):
...
6.3 推荐算法性能提升
对于大规模数据,考虑以下优化方案:
- 使用稀疏矩阵存储用户-物品评分矩阵
- 采用MinHash或LSH近似计算相似度
- 离线预计算相似度矩阵,定时更新
稀疏矩阵优化示例:
python复制from scipy.sparse import csr_matrix
def build_sparse_matrix():
behaviors = UserBehavior.objects.all().values()
df = pd.DataFrame(behaviors)
# 构建稀疏矩阵
users = pd.factorize(df['user_id'])[0]
spots = pd.factorize(df['spot_id'])[0]
scores = df['score'].values
return csr_matrix((scores, (users, spots)))
7. 毕业设计答辩要点
7.1 技术亮点提炼
建议重点展示以下创新点:
- 混合推荐策略解决冷启动问题
- 基于用户行为的隐式评分构建
- 可视化分析与推荐结果的结合
- 针对旅游领域的特殊优化(季节因素、地理位置)
7.2 系统演示技巧
演示流程建议:
- 先展示数据爬取和清洗过程(证明数据来源可靠)
- 演示推荐算法在不同场景下的效果(新用户/老用户)
- 对比协同过滤与热门推荐的结果差异
- 展示可视化看板的交互功能
7.3 常见问题准备
导师可能提出的问题及应对:
Q:如何评估推荐效果?
A:采用离线评估(划分训练测试集计算召回率)与在线评估(A/B测试点击率)结合
Q:数据量不足怎么办?
A:1) 使用公开数据集补充 2) 设计数据增强策略 3) 强调在小数据量下算法依然有效
Q:为什么选择协同过滤而非深度学习?
A:1) 数据量限制 2) 可解释性强 3) 符合毕业设计的复杂度要求
在实际开发过程中,我最大的体会是旅游推荐系统需要特别关注时空维度。比如在节假日期间,用户的偏好会明显偏向周边游;而不同季节的热门景点也差异巨大。这些业务特性需要通过特征工程融入推荐算法,简单的协同过滤可能无法捕捉这些复杂模式。一个实用的技巧是在用户-物品矩阵中加入时间衰减因子,让近期的行为拥有更高权重。
