1. 项目背景与核心价值
最近在做一个挺有意思的旅游推荐系统项目,核心思路是通过分析海量用户评论来挖掘景点特色,进而实现个性化推荐。这个项目用Django框架搭建,结合了自然语言处理技术,算是把Python在Web开发和数据分析两个领域的优势都发挥出来了。
为什么说这个项目有价值?现在旅游平台上的景点信息实在太多了,用户看评论看得眼花缭乱。我们团队做过调研,超过70%的用户会因为负面评论而放弃某个景点,但实际上这些负面评价可能只是针对某个特定方面(比如"不适合带小孩"),对其他用户群体可能完全不是问题。这就是传统5分制评分系统的局限性——它把多维度的评价压缩成了一个单一数字。
我们的系统通过文本挖掘技术,把评论拆解成多个维度:比如"交通便利性"、"亲子友好度"、"拍照出片效果"等,再结合用户画像做匹配。举个例子,带娃出游的家庭会更关注"母婴设施"相关的评论,而年轻情侣可能更在意"浪漫氛围"的评价。这种颗粒度的推荐,比简单按评分排序要有用得多。
2. 技术架构设计
2.1 为什么选择Django
选Django作为后端框架主要考虑这几个因素:
-
ORM支持:景点数据和用户评论都是结构化数据,用Django的Model可以快速构建数据库schema,还能自动生成管理后台。我们定义了这些核心模型:
python复制class Attraction(models.Model): name = models.CharField(max_length=100) location = models.PointField() # 使用GeoDjango存储坐标 description = models.TextField() tags = TaggableManager() # django-taggit扩展 class Review(models.Model): attraction = models.ForeignKey(Attraction, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) content = models.TextField() sentiment_score = models.FloatField(null=True) # 情感分析结果 created_at = models.DateTimeField(auto_now_add=True) -
Admin后台:内置的Admin界面让运营人员可以快速管理景点信息,我们通过定制Admin类增加了批量导入和导出功能:
python复制@admin.register(Attraction) class AttractionAdmin(admin.ModelAdmin): list_display = ('name', 'get_tags', 'review_count') actions = ['export_to_csv'] def get_tags(self, obj): return ", ".join(t.name for t in obj.tags.all()) -
REST API支持:配合Django REST framework,我们只用了不到200行代码就实现了完整的景点查询和推荐API:
python复制class AttractionViewSet(viewsets.ModelViewSet): queryset = Attraction.objects.annotate( avg_rating=Avg('reviews__sentiment_score') ) serializer_class = AttractionSerializer filter_backends = [DjangoFilterBackend, filters.SearchFilter] search_fields = ['name', 'tags__name']
2.2 文本挖掘方案选型
评论分析是系统的核心,我们对比了几种NLP方案:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 规则匹配 | 实现简单 | 覆盖率低 | 特定领域简单分析 |
| 传统机器学习 | 准确率较高 | 需要特征工程 | 中等规模数据集 |
| BERT等预训练模型 | 准确率高 | 计算资源消耗大 | 对精度要求高的场景 |
最终选择LDA主题模型+情感分析的组合方案:
- 先用jieba分词对中文评论做预处理(去停用词、词性标注)
- 通过TF-IDF提取关键词
- 用LDA模型发现8-10个潜在主题(如"交通"、"餐饮"、"风景"等)
- 对每个主题下的评论用SnowNLP做情感分析
关键代码示例:
python复制def analyze_review(text):
# 分词处理
words = [word for word in jieba.cut(text) if word not in stopwords]
# LDA主题预测
dictionary = corpora.Dictionary([words])
bow_vector = dictionary.doc2bow(words)
topic_dist = lda_model[bow_vector] # 获取主题分布
# 情感分析
sentiment = SnowNLP(text).sentiments
return {
'topics': topic_dist,
'sentiment': sentiment
}
3. 推荐算法实现
3.1 用户画像构建
推荐效果的好坏很大程度上取决于用户画像的精细程度。我们设计了三级画像体系:
- 显式画像:用户注册时填写的年龄、性别、旅行偏好等
- 行为画像:通过埋点收集的点击、收藏、停留时长等数据
- 语义画像:分析用户自己写的评论和游记,提取关键词
比如当系统检测到用户评论中频繁出现"带孩子"、"婴儿车"等关键词时,会自动给用户打上"亲子游"标签,后续优先推荐有"母婴室"、"儿童游乐区"等设施的景点。
3.2 混合推荐策略
采用三种推荐策略的加权融合:
-
基于内容的推荐:计算景点特征与用户画像的余弦相似度
python复制def content_based_recommend(user, n=5): user_vector = user.profile.vector # 用户画像向量 attractions = Attraction.objects.all() scores = [ (att, cosine_similarity(user_vector, att.vector)) for att in attractions ] return sorted(scores, key=lambda x: -x[1])[:n] -
协同过滤:使用Surprise库实现基于用户的协同过滤
python复制from surprise import Dataset, KNNBasic def collaborative_filtering(user_id): data = Dataset.load_from_df(ratings_df, reader) trainset = data.build_full_trainset() algo = KNNBasic() algo.fit(trainset) return algo.get_neighbors(user_id, k=5) -
实时上下文推荐:结合用户当前地理位置、天气等实时数据
最终推荐分数 = 0.5内容推荐 + 0.3协同过滤 + 0.2*实时推荐
4. 性能优化实践
4.1 缓存策略
评论分析是CPU密集型操作,我们采用多级缓存:
-
内存缓存:对热门景点的分析结果缓存1小时
python复制from django.core.cache import cache def get_attraction_reviews(attraction_id): cache_key = f'attraction_{attraction_id}_reviews' result = cache.get(cache_key) if not result: result = expensive_analysis() cache.set(cache_key, result, timeout=3600) return result -
CDN缓存:静态资源和API响应通过Cloudflare缓存
-
数据库缓存:对计算密集型结果(如情感分析得分)物化到数据库
4.2 异步任务处理
用Celery处理耗时的NLP任务:
python复制@app.task(bind=True)
def analyze_review_task(self, review_id):
review = Review.objects.get(pk=review_id)
result = analyze_review(review.content)
review.sentiment_score = result['sentiment']
review.save()
return result
配置了单独的Redis作为Celery broker,并设置了任务重试机制:
python复制@app.task(bind=True, max_retries=3)
def analyze_review_task(self, review_id):
try:
# 任务逻辑
except Exception as exc:
raise self.retry(exc=exc, countdown=60)
5. 踩坑与经验分享
5.1 中文分词的坑
最初直接使用jieba的默认词典,发现对旅游领域专有名词(如"玻璃栈道"、"网红打卡点")识别不准。解决方案:
- 收集了5000+旅游领域词汇扩充自定义词典
- 对景点名称做了强制分词保护
- 加入了同义词映射(如"厕所"="卫生间")
5.2 冷启动问题
新景点没有足够评论时推荐效果差,我们采用了这些策略:
- 基于景点元数据(标签、地理位置等)做相似推荐
- 引入第三方数据源(如大众点评)的评论做补充
- 对新建景点设置初始虚拟评分,随真实数据增加逐步降低权重
5.3 数据稀疏性
用户-景点交互矩阵非常稀疏(99.5%以上位置为空值),解决方法:
- 使用矩阵分解(SVD)降维
- 引入社交关系数据(好友的偏好)
- 增加基于会话的短期兴趣建模
6. 管理后台增强
默认Django Admin功能有限,我们做了这些增强:
-
批量操作:支持Excel导入导出景点数据
-
数据分析面板:集成Metabase实现可视化分析
-
审核流程:对用户评论实现先审后发
python复制class ReviewAdmin(admin.ModelAdmin): list_filter = ['status'] actions = ['approve_reviews'] def approve_reviews(self, request, queryset): queryset.update(status='approved') -
自动化任务:定期生成景点热度报告并邮件发送
7. 部署方案
采用Docker-compose部署,主要服务包括:
yaml复制version: '3'
services:
web:
build: .
command: gunicorn core.wsgi:application --bind 0.0.0.0:8000
volumes:
- static_data:/app/static
depends_on:
- redis
- db
redis:
image: redis:alpine
db:
image: postgres:13
volumes:
- pg_data:/var/lib/postgresql/data
关键配置项:
- 使用Gunicorn+Gevent提高并发能力
- 配置PostgreSQL连接池
- 静态文件通过Nginx直接服务
- 日志集中收集到ELK栈
8. 效果验证
上线后通过A/B测试验证效果:
- 推荐点击率提升42%
- 用户平均停留时长增加27%
- 负面评论占比下降18%
特别是在节假日期间,系统的负载均衡表现良好,即使在流量峰值时期API响应时间仍保持在300ms以内。