基于RAG+Streamlit的智慧文旅系统实战:1500行代码构建“智游八桂”
手把手教你用Streamlit+高德API+大模型构建"智游八桂"广西文旅智能规划平台
在规划一次旅行时,你是否有过这样的经历:查景点要去小红书,查酒店要去携程,查交通要看高德地图,最后在Excel里手动拼凑一份行程表,发现时间根本排不开……
这是大多数自由行游客的真实困境——信息碎片化。
先看效果:全程两分钟,展示从提问到生成行程的完整流程:
智游八桂
本文将分享我如何用Streamlit + 高德地图API + GLM-4大模型,构建了一个名为"智游八桂"的广西文旅智能规划系统。从数据采集、RAG问答引擎到3D地图可视化和行程规划,全程约1500行Python代码。
读完本文你将收获:
✅ 如何用高德API采集POI数据并构建知识库
✅ 如何基于RAG架构构建精准的智能问答系统
✅ 如何用Streamlit快速搭建数据驱动的Web应用
✅ 如何集成3D地形地图和PDF行程导出
✅ 如何设计容错机制避免AI"幻觉"
源码已开源,欢迎Star⭐
一、为什么做这个项目?
1.1 一个真实的痛点
五一假期前,朋友问我:“我想去桂林玩三天,有什么推荐?”
我花了整整一个晚上:小红书查攻略、携程看酒店、高德量距离、大众点评找美食,最后在Excel里拼出一份行程表发给他。他回了一句:“能不能再帮忙看看北海?”
这种经历你大概率也有过——信息碎片化让一次普通的旅行规划变成了信息检索的体力活。
1.2 为什么不用传统方案?
市面上已经有“一键游广西”这类省级平台,它们解决的是“信息在哪里”的问题(把数据集中到一个APP里),但没有解决“信息如何被高效使用”的问题——用户依然需要自己浏览、筛选、整合。
而大语言模型的出现带来了新可能:用户直接问“桂林有哪些必去的景点”,AI就能给出精准推荐。
但纯大模型有幻觉问题——它会一本正经地编造不存在的景区。怎么解决?RAG(检索增强生成)让模型基于可信知识库回答,从源头杜绝编造。
1.3 我要做什么
一个覆盖广西14个城市的文旅智能系统,核心功能:
💬 用户用自然语言问景点/酒店,系统精准回答
🗺️ 3D地图可视化展示所有POI分布
📅 根据城市、天数、风格一键生成行程
📊 景区口碑自动分析
1.4 项目成果预览
| 指标 | 数据 |
|---|---|
| 覆盖城市 | 广西14个地级市 |
| 景区POI | 750+ 条 |
| 酒店POI | 500+ 条 |
| 代码量 | 约1500行 Python |
| 开发周期 | 2-3周 |
| 功能模块 | 6大核心功能 |
二、技术选型
2.1 整体技术栈
| 模块 | 技术 | 选型理由 |
|---|---|---|
| Web框架 | Streamlit | 纯Python,无需前端知识,快速开发 |
| 数据采集 | 高德地图API | POI数据全面,覆盖广西全境 |
| 大模型 | GLM-4-Flash | 中文能力强,推理快,成本低 |
| 问答架构 | RAG | 基于知识库,避免幻觉 |
| 可视化 | Plotly + 高德JS API | 交互式图表 + 3D地形 |
| PDF生成 | FPDF | 轻量级,纯Python |
2.2 关键技术决策
为什么选RAG而不是微调?
微调需要大量高质量的标注数据(景区-问题对),成本高、周期长。RAG只需构建结构化知识库即可快速上线,且知识更新灵活——景区信息变化时更新库即可,无需重新训练模型。
为什么选Streamlit?
传统Web开发需要前后端分离(React/Vue + Flask/Django),代码量通常在5000行以上。Streamlit约1500行即可构建完整的交互式应用,开发周期从数月缩短到2-3周。对于个人开发者和毕业设计来说,效率优势非常明显。
为什么选GLM-4-Flash?
智谱AI的GLM-4-Flash在中文理解和生成方面表现优异,推理速度快,且提供OpenAI兼容接口,接入成本低。对于旅游问答这种需要流畅中文对话的场景,GLM系列相比GPT系列在中文表达上更自然。
三、系统架构设计
3.1 整体分层架构
系统采用经典的三层架构,自下而上分为:
数据层:
高德地图API数据采集 → 原始CSV → 数据清洗 → 结构化DataFrame → 知识库文本
核心数据模型:景区POI(名称、地址、城市、分类、经纬度、电话、星级)
酒店POI(与景区共享相同的地理位置和城市属性)
服务层:
RAG问答引擎:检索(关键词匹配 + 城市权重增强)→ 生成(GLM-4-Flash)
路线规划:高德驾车路线API → 距离/时间计算
情感分析:基于正负面关键词词典的规则分类
PDF生成:FPDF库 → 行程单导出
应用层(Streamlit 6大页面):
数据总览 | 地图可视化 | 数据分析 | 口碑分析 | AI旅行助手 | 行程规划
3.2 RAG问答核心流程
检索策略的核心优化:
城市关键词享有5倍匹配权重,确保地域相关性
知识库包含"景区条目 + 酒店条目 + 城市摘要条目"三种粒度
双层降级保障:API调用失败时自动切换本地规则回答
3.3 数据流设计
系统的数据流分为两条路径:
离线路径(数据采集):高德API → 原始CSV → 数据清洗 → 结构化DataFrame → 知识库文本
在线路径(用户服务):用户提问 → RAG检索 → 大模型生成 → 回答输出
两条路径的解耦保证了知识库更新不影响系统运行,也使得数据采集和问答服务可以独立迭代。
四、核心功能展示
4.1 数据总览
页面顶部展示四个关键指标卡片:景区总数753个、酒店总数500+、覆盖城市14个、POI总数1253个。
下方是两个交互式柱状图,分别展示景区和酒店数量的TOP10城市排名。从图表中可以清晰看到:桂林、南宁、北海三城断崖式领先,这与广西旅游资源的实际分布完全吻合。
4.2 AI旅行助手(核心功能)
这是系统的灵魂模块。用户输入自然语言问题,系统走完“检索→生成”全链路,返回精准回答。
三种问答能力实测:
| 问题类型 | 示例问题 | 系统表现 |
|---|---|---|
| 事实型 | “桂林有哪些必去的景点?” | 从知识库检索桂林景区列表,生成带推荐语回答 |
| 条件筛选型 | “推荐适合亲子游的景区” | 自动筛选分类含“动物园/海洋馆/游乐场”的景区 |
| 综合判断型 | “北海适合玩几天?” | 基于景区数量和类型,给出2-3天的建议 |
防幻觉能力展示:
当用户问“广西有哪些非遗文化?”这种知识库外的问题时,系统不会编造,而是触发降级机制,给出友好提示并引导用户询问已知信息。
实测效果总结:
| 测试场景 | 用户问题示例 | 系统表现 | 状态 |
|---|---|---|---|
| 事实型问答 | “桂林有哪些必去的景点?” | 返回漓江、象鼻山、两江四湖等5个景点,附一句话推荐 | ✅ 通过 |
| 条件筛选型 | “推荐适合亲子游的景区” | 自动匹配分类含“动物园/海洋馆/游乐场”的景区 | ✅ 通过 |
| 综合判断型 | “北海适合玩几天?” | 基于景区数量(银滩+涠洲岛+老街等)给出2-3天建议 | ✅ 通过 |
| 知识库外问题 | “广西有哪些非遗文化?” | 触发降级机制,引导用户询问已知信息 | ✅ 通过 |
| 模糊意图 | “你好” | 打招呼并介绍可提供的服务 | ✅ 通过 |
| 酒店查询 | “南宁有什么好的酒店?” | 返回南宁市8家推荐酒店 | ✅ 通过 |
4.3 3D地形地图(视觉亮点)
集成高德JS API的3D地形模式,广西典型的喀斯特地貌(峰林、丘陵)得到立体呈现。
标记规则:
🔴红色标记→ 景区
🔵蓝色标记→ 酒店
交互方式:
鼠标左键拖拽 → 旋转视角
鼠标滚轮 → 缩放
下拉选择城市 → 动态聚焦,地图自动定位到该城市几何中心
使用体验:当你想了解桂林的整体旅游格局时,只需选择“桂林”城市,地图自动放大聚焦到桂林区域,所有的景区和酒店分布一目了然。结合3D地形,还能直观感受到桂林“山环水绕”的城市格局。
4.4 智能行程规划(实用功能)
用户只需配置三个参数,点击“生成行程计划”,系统自动输出:
每日行程安排:每天上午/下午各一个景点(含名称、地址、分类)
交通参考:相邻景点间的驾车距离和预计时间(调用高德路线规划API)
晚间活动:对应城市的美食街推荐(如桂林→正阳路步行街)
住宿推荐:3家随机推荐酒店
PDF导出:一键下载完整行程单
风格筛选逻辑:
| 旅行风格 | 匹配的景区分类关键词 |
|---|---|
| 经典必游 | 景区、风景、公园、名胜、旅游 |
| 自然风光 | 自然、山水、森林、生态、峡谷 |
| 文化体验 | 博物馆、文化、历史、古迹、民俗、古镇 |
| 休闲度假 | 度假、温泉、乡村、休闲、养生 |
行程生成示例(桂林,3天,经典必游):
4.5 口碑分析(趣味功能)
基于模拟评论数据进行情感倾向分析。采用关键词匹配方法:
统计评论中的正面词(“好”“棒”“推荐”“震撼”“漂亮”“喜欢”“开心”“舒适”“干净”“热情”“完美”等)
统计评论中的负面词(“差”“坑”“失望”“拥挤”“脏”“乱”“贵”“不值”“一般”“普通”“糟糕”等)
计算情感得分 = 正面词数 - 负面词数
得分 ≥ 1 → 好评 | 得分 ≤ -1 → 差评 | 其他 → 中性
展示形式:
📊 各景区平均情感得分柱状图
🥧 评论数量景区分布饼图
🔍 按景区/情感类型筛选查看
五、核心代码实现
5.1 数据采集模块
def search_poi(keyword, city='广西', page=1, page_size=20): """搜索高德POI数据""" url = "https://restapi.amap.com/v3/place/text" params = { 'key': AMAP_KEY, 'keywords': keyword, 'city': city, 'citylimit': True, 'offset': page_size, 'page': page, 'extensions': 'all' } resp = requests.get(url, params=params, timeout=10) data = resp.json() if data['status'] == '1': return data['pois'] return [] def collect_data(keywords, category): """收集指定类别的POI数据""" all_data = [] for kw in keywords: page = 1 while True: pois = search_poi(kw, page=page) if not pois: break for p in pois: item = { '名称': p.get('name'), '地址': p.get('address'), '城市': p.get('cityname').replace('市', ''), '分类': p.get('type'), '经度': p.get('location', ',').split(',')[0], '纬度': p.get('location', ',').split(',')[1], '电话': p.get('tel'), '星级': p.get('biz_ext', {}).get('rating') } all_data.append(item) if len(pois) < 20: break page += 1 time.sleep(0.5) return pd.DataFrame(all_data)5.2 RAG检索与生成
class TravelRAG: def search(self, query, top_k=15): """城市权重增强的检索策略""" # 提取关键词 stop_words = ['必去', '推荐', '好玩', '哪里', '什么'] keywords = [w for w in query.split() if w not in stop_words and len(w) > 1] scored = [] for item in self.knowledge_base: score = 0 for kw in keywords: if kw in item: # 城市关键词权重×5 if kw in ALL_CITIES: score += 5 else: score += 1 if score > 0: scored.append((score, item)) scored.sort(reverse=True, key=lambda x: x[0]) # 降级保障:命中<3条时补充城市摘要 if len(scored) < 3: for city in ALL_CITIES: if city in query: for item in self.knowledge_base: if f'【城市摘要】{city}' in item: return [item] + [it for _, it in scored[:10]] return [item for _, item in scored[:top_k]] def ask(self, query): """RAG问答主流程""" context_items = self.search(query) context = "\n".join(context_items) system_prompt = """ 你是一位专业的广西旅游规划师,名叫"小桂"。 必须基于知识库回答,不要编造不存在的信息。 """ user_prompt = f""" 知识库信息: {context} 用户问题:{query} """ try: response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], temperature=0.3, max_tokens=600 ) return response.choices[0].message.content except: return get_local_answer(query) # 降级5.3 3D地图集成
# 核心HTML模板 amap_html = f""" <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://webapi.amap.com/maps?v=2.0&key={AMAP_KEY}"></script> </head> <body> <div id="container"></div> <script> var map = new AMap.Map('container', {{ zoom: 7, center: [{center_lon}, {center_lat}], viewMode: '3D', pitch: 55, rotation: -20, mapStyle: 'amap://styles/light' }}); // 开启地形 var terrainLayer = new AMap.Terrain({{zIndex: 1, terrain: true}}); map.add(terrainLayer); // 添加景区标记 scenicPoints.forEach(function(item) {{ var marker = new AMap.Marker({{ position: [item.经度, item.纬度], label: {{content: item.名称, direction: 'top'}}, icon: new AMap.Icon({{image: 'mark_r.png'}}) }}); marker.setMap(map); }}); </script> </body> </html> """ html(amap_html, height=680)六、踩坑与优化
6.1 高德API的那些坑
经纬度格式问题:高德返回的是"经度,纬度"字符串,需要分割转换。有些记录经纬度为空或格式异常,需要容错处理。
翻页限制:单次最多返回20条,需要循环翻页。某些关键词总条数超过1000,但翻页到第5页后数据开始重复,需要去重。
6.2 大模型调用的"降级思维"
AI API调用存在很多不稳定因素:Key限额用完、网络超时、模型服务降级等。
我的策略是:大模型是锦上添花,不是雪中送炭。任何时候API调用失败,系统都应该能退回到本地规则回答,保证基本可用性。
try: answer = rag_agent.ask(query) if len(answer) < 20 or "暂时没有" in answer: return get_local_answer(query) # 回答质量不足,降级 return answer except Exception: return get_local_answer(query) # API异常,降级6.3 Streamlit性能优化
数据缓存:加载CSV文件用@st.cache_data,避免每次交互都重新读取。
@st.cache_data def load_data(): return pd.read_csv('guangxi_scenic_raw.csv')地图标记点数量控制:一次渲染300个标记点以上会导致地图卡顿,设置最大显示数量。
七、未来展望
7.1 短期优化方向
| 方向 | 具体措施 | 预期效果 |
|---|---|---|
| 知识库扩展 | 接入餐饮、购物、交通等POI | 服务更完整 |
| 语义检索升级 | 关键词匹配 → 向量检索 | 理解更精准 |
| 个性化推荐 | 引入用户画像和协同过滤 | 推荐更贴合 |
7.2 长期迭代方向
知识库实时更新:当前数据是静态的,后续可接入高德实时API,让景区开放时间、门票价格等信息保持动态更新。
多模态能力:接入图像识别,用户上传景点照片即可获取介绍;接入语音交互,通过语音完成问答和规划。
云端部署:将系统迁移至云平台,开发微信小程序或移动App版本,降低用户使用门槛。
7.3 给想复现的同学的建议
先跑通数据采集:高德API注册免费,每天有调用限额。先用小范围(比如桂林)测试,确认数据正常后再扩展全广西。
RAG从简单开始:先实现关键词匹配检索,跑通流程后再优化为向量检索。不要一上来就追求完美架构。
大模型API选免费或便宜的:GLM-4-Flash、DeepSeek都是不错的选择。旅游问答不需要超强推理能力,性价比更重要。
Streamlit官方文档+社区是最好老师:遇到组件使用问题,先去官方文档找示例,比看零散的博客高效得多。
八、总结
本文分享了"智游八桂"广西文旅智能规划系统的设计与实现全过程:
数据层面,利用高德地图API采集了广西14个城市的景区和酒店POI数据,经过清洗构建了本地知识库。
技术层面,基于RAG架构集成了GLM-4-Flash大语言模型,通过城市权重增强检索、多粒度知识库和双重降级保障实现了精准、可控的旅游问答。
应用层面,提供了数据总览、3D地图可视化、数据分析、口碑分析、AI旅行助手和行程规划六大功能模块。
架构层面,约1500行Python代码即可构建完整的交互式应用,验证了轻量化技术路径在智慧旅游领域的可行性。
如果觉得有帮助,欢迎Star⭐支持!
有任何问题,欢迎在评论区留言交流。
