
简介这是一套基于Python与Vue.js构建的知识图谱驱动型智能旅游推荐系统完整代码工程面向计算机专业本科生、毕业设计学生及知识图谱初学者解决旅游场景下个性化景点、餐饮等多维度智能推荐问题。资源包共237个文件涵盖22个核心Python后端模块含Neo4j图数据库交互、推荐算法逻辑、24个Vue前端组件含交互式知识图谱可视化、25个JS业务脚本、18个CSS样式文件及90张界面截图与流程图整体压缩包大小为39.62MB结构清晰、模块解耦明确。已有266人下载学习项目经导师高度认可获评98分高分实践成果。代码全程手写并附详尽中文注释包含可直接运行的前后端分离部署方案、数据集示例xls/csv、知识图谱构建脚本py及推荐结果可视化页面vueinteractive-graph.min.css新手可快速上手调试与二次开发。1. 这不是又一个“景点列表页”而是一个能理解“川西小众但适合带老人自驾”需求的知识驱动推荐系统你有没有试过在旅游平台搜“适合带父母的云南行程”结果首页全是网红打卡点和年轻人徒步路线这个基于 Python Vue 的知识图谱旅游推荐系统恰恰解决了这个断层——它不靠关键词匹配而是把景点、交通、餐饮、气候、海拔、适老设施、历史背景等上百个维度建模成结构化三元组让系统真正“理解”什么是“适合带老人”。后端用 Neo4j 存储图谱关系Flask 提供语义查询 API前端用 Vue 实现可交互的图谱可视化注意不是静态图是支持点击节点展开关联、拖拽缩放、高亮路径的 real-time graph并融合协同过滤与图神经网络GNN打分逻辑。项目已预置四川、云南、陕西三地 2000 实体及 8000 关系数据部署后输入自然语言如“想吃地道川菜、避开人挤人、有无障碍通道”系统会返回带推理路径的推荐结果例如推荐「钟水饺总店」→ 因其位于「春熙路地铁站」→ 该站有垂直电梯 → 且该店在知识图谱中标注了「适老餐位」「无台阶入口」。适合课程设计、毕设答辩、或作为知识图谱工程落地的最小可行案例。2. 知识图谱构建从原始旅游数据到 Neo4j 可查询三元组的完整链路2.1 为什么选 Neo4j 而非 MySQL 或 Elasticsearch旅游推荐的核心瓶颈不在存储容量而在关系遍历效率与语义表达能力。MySQL 的 JOIN 操作在 5 层以上关联时性能断崖式下降Elasticsearch 擅长全文检索但无法原生表达“景点A→被某旅行社包含在→亲子游线路→该线路要求游客年龄≤12岁→而用户孩子今年10岁”这类多跳推理链。Neo4j 的 Cypher 查询语言天然支持MATCH (p:Person)-[:HAS_CHILD]-(c:Child)-[:AGE]-(a:Age {value:10})这类模式匹配且单次查询毫秒级响应。本项目中我们定义了 7 类核心节点Place、Cuisine、Transport、Facility、Season、TourGroup、UserProfile和 12 种关系LOCATED_IN、SERVES_CUISINE、ACCESSIBLE_BY、HAS_FACILITY、BEST_IN_SEASON、INCLUDES_PLACE等所有实体属性均标注source来源携程/马蜂窝/文旅局公开数据和confidence置信度人工校验后赋值 0.7~0.95为后续 GNN 微调留出可信度权重接口。2.2 数据清洗与三元组抽取Python 脚本实操原始数据来自三类渠道① 文旅局发布的《四川省无障碍旅游设施名录》PDF 表格② 携程评论中提取的“带老人体验”高频句式如“电梯直达”“轮椅友好”“有休息区”③ 马蜂窝游记中人工标注的“美食-景点”共现关系。清洗脚本data_pipeline/extract_triples.py核心逻辑如下# data_pipeline/extract_triples.py import re import pandas as pd from py2neo import Graph # 1. 解析 PDF 表格使用 tabula-py df tabula.read_pdf(data/accessible_facilities.pdf, pagesall) # 清洗统一“无障碍坡道”“无障碍通道”为标准标签 RAMP_ACCESS df[facility_type] df[facility_type].str.replace(r无障碍[坡道|通道|电梯], RAMP_ACCESS, regexTrue) # 2. 从评论提取关系正则 规则模板 def extract_from_review(text): patterns [ (r电梯直达, HAS_FACILITY, ELEVATOR), (r轮椅友好, HAS_FACILITY, WHEELCHAIR_ACCESS), (r有休息区, HAS_FACILITY, REST_AREA), (r离地铁站(\d)米, ACCESSIBLE_BY, SUBWAY_{group1}m) # group1 为捕获的距离数字 ] triples [] for pattern, rel, obj in patterns: if re.search(pattern, text): # 从上下文提取景点名假设评论前10字含景点名 place re.match(r^.{0,10}, text).group().strip() triples.append((place, rel, obj)) return triples # 3. 写入 Neo4j批量提交避免逐条事务开销 graph Graph(bolt://localhost:7687, auth(neo4j, password)) tx graph.begin() for place, rel, obj in all_triples: tx.run( MERGE (p:Place {name: $place}) MERGE (o:Facility {type: $obj}) CREATE (p)-[:HAS_FACILITY {source: travel_comment, confidence: 0.85}]-(o), placeplace, objobj ) graph.commit(tx)提示MERGE保证节点不重复创建CREATE强制新建关系因同一景点可能多次被不同用户标注“轮椅友好”需保留多条关系以统计置信度confidence值在后续 GNN 推理中作为边权重直接影响推荐排序。2.3 图谱验证用 Cypher 快速定位数据质量问题部署 Neo4j 后必须验证三元组质量。以下 3 条 Cypher 是日常巡检必查项// 检查是否存在孤立节点无任何关系的景点 MATCH (p:Place) WHERE NOT (p)--() RETURN p.name, count(*) AS isolated_count // 检查“适老设施”关系是否全部指向标准 Facility 节点避免拼写错误如 wheelchair vs wheel_chair MATCH (p:Place)-[r:HAS_FACILITY]-(f) WHERE NOT f.type IN [ELEVATOR,WHEELCHAIR_ACCESS,REST_AREA,RAMP_ACCESS] RETURN p.name, f.type, r.source // 检查跨省关系合理性如“西安兵马俑”被标记为“位于云南”明显错误 MATCH (p:Place)-[:LOCATED_IN]-(prov:Province) WHERE p.name CONTAINS 兵马俑 AND prov.name 云南 RETURN p.name, prov.name运行结果若返回非空说明数据清洗规则需补充。例如第二条查出f.typewheel_chair则需在extract_triples.py中增加.replace(wheel_chair, WHEELCHAIR_ACCESS)。3. 后端服务Flask API 设计与语义查询引擎实现3.1 API 分层设计从 HTTP 请求到图谱推理的四层映射本系统 API 不是简单 CRUD而是将用户自然语言请求映射为图谱查询路径。app.py定义了/api/recommend入口其处理流程严格分为四层层级职责关键代码位置示例输入输出1. 意图解析层识别用户核心诉求如“适老”“美食”“避暑”nlu/intent_classifier.py输入“带爸妈去云南要凉快点的” → 输出{intent: climate_preference, target_climate: cool, user_profile: elderly_accompany}2. 实体链接层将模糊词如“凉快点”绑定到图谱节点Season {type: summer_cool}nlu/entity_linker.py“凉快点” →MATCH (s:Season) WHERE s.description CONTAINS 夏季凉爽3. 查询生成层根据意图实体动态拼装 Cypher 模板graph/query_builder.py生成MATCH (p:Place)-[:BEST_IN_SEASON]-(s:Season {type:summer_cool})-[:LOCATED_IN]-(prov:Province {name:云南}) RETURN p.name, p.rating4. 结果重排层对原始图谱结果融合 GNN 打分gnn/scorer.py与协同过滤cf/recommender.pyservice/recommender.py最终排序[{name:大理古城,score:0.92,reason:GNN路径古城→有电瓶车→电瓶车→适老→用户画像匹配}]3.2 核心推荐 API 实现支持多条件组合与解释性返回/api/recommend的完整实现含错误处理与日志# app.py from flask import Flask, request, jsonify from service.recommender import HybridRecommender from nlu.intent_classifier import classify_intent import logging app Flask(__name__) recommender HybridRecommender() # 初始化混合推荐器 app.route(/api/recommend, methods[POST]) def get_recommendation(): try: data request.get_json() user_query data.get(query, ).strip() if not user_query: return jsonify({error: query is required}), 400 # 1. 意图识别耗时50ms本地模型 intent_result classify_intent(user_query) # 2. 执行混合推荐图谱GNNCF results recommender.recommend( queryuser_query, intentintent_result[intent], constraintsintent_result.get(constraints, {}), top_k5 ) # 3. 构建可解释返回关键毕业答辩加分项 response { query: user_query, recommendations: [ { name: r[name], score: round(r[score], 3), explanation: r[explanation], # 如因您关注适老该景点有无障碍电梯置信度0.92且距地铁站100m details: { rating: r.get(rating, 0), address: r.get(address, ), image_url: r.get(image_url, ) } } for r in results ], debug: { intent: intent_result[intent], graph_query_time_ms: results[0].get(graph_time, 0) if results else 0, gnn_score_weight: 0.6 # 当前权重配置 } } return jsonify(response) except Exception as e: logging.error(fRecommendation failed: {str(e)}) return jsonify({error: Internal server error}), 500 if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse) # 生产环境禁用 debug注意explanation字段不是硬编码而是从 GNN 的注意力权重反向追踪路径生成。例如 GNN 对边HAS_FACILITY的注意力值最高则解释为“因该设施匹配您的需求”。3.3 GNN 打分模块PyTorch Geometric 实现轻量级图卷积gnn/scorer.py使用 PyTorch Geometric 实现 2 层 GCN输入为子图用户查询触发的 100 个节点内拓扑输出节点嵌入用于相似度计算# gnn/scorer.py import torch from torch_geometric.nn import GCNConv from torch_geometric.data import Data class GCN(torch.nn.Module): def __init__(self, num_features, hidden_channels, num_classes): super().__init__() self.conv1 GCNConv(num_features, hidden_channels) self.conv2 GCNConv(hidden_channels, num_classes) def forward(self, x, edge_index): x self.conv1(x, edge_index).relu() x self.conv2(x, edge_index) return x # 加载预训练权重项目已提供 model/gcn_weights.pth gcn_model GCN(num_features128, hidden_channels64, num_classes32) gcn_model.load_state_dict(torch.load(model/gcn_weights.pth)) gcn_model.eval() def score_places(subgraph_data: Data) - torch.Tensor: 对子图中所有 Place 节点打分 with torch.no_grad(): embeddings gcn_model(subgraph_data.x, subgraph_data.edge_index) # 计算与用户画像向量的余弦相似度 user_emb get_user_embedding(subgraph_data.user_profile) # 从 intent_result 提取 scores torch.cosine_similarity(embeddings, user_emb, dim1) return scores参数说明num_features128是节点初始特征维度由 BERT 提取的文本描述向量hidden_channels64平衡效果与速度num_classes32是最终嵌入维度足够区分旅游场景语义。4. 前端交互Vue 组件化实现知识图谱可视化与推荐卡片联动4.1 图谱可视化组件基于 vis.js 的可交互 force-directed 图src/components/InteractiveGraph.vue封装了 vis.js 的力导向图关键特性包括双击节点展开关联点击“成都”节点自动加载其LOCATED_IN、SERVES_CUISINE、ACCESSIBLE_BY相关节点路径高亮当推荐结果包含“钟水饺→春熙路地铁站→无障碍电梯”路径时自动用红色箭头高亮此链路右键菜单对任意节点右键可选择“查看详情”弹出 Neo4j 原始数据、“排除此节点”临时过滤、“添加到对比列表”。核心初始化代码!-- src/components/InteractiveGraph.vue -- template div idgraph-container styleheight: 600px; border: 1px solid #eee;/div /template script import * as vis from vis-network export default { name: InteractiveGraph, props: { initialNodes: Array, // 从 API 获取的初始节点数组 initialEdges: Array // 初始边数组 }, data() { return { network: null, nodes: new vis.DataSet(this.initialNodes), edges: new vis.DataSet(this.initialEdges) } }, mounted() { this.initNetwork() }, methods: { initNetwork() { const container document.getElementById(graph-container) const data { nodes: this.nodes, edges: this.edges } const options { physics: { stabilization: { iterations: 50 } }, interaction: { dragNodes: true, zoomView: true, multiselect: true }, nodes: { shape: dot, size: 20, font: { size: 12 } }, edges: { width: 2, arrows: { to: { enabled: true, scaleFactor: 0.8 } } } } this.network new vis.Network(container, data, options) // 双击节点事件加载其邻居 this.network.on(doubleClick, (params) { if (params.nodes.length 1) { this.loadNeighbors(params.nodes[0]) } }) }, loadNeighbors(nodeId) { // 调用后端 API 获取 nodeId 的直接邻居 this.$http.get(/api/graph/neighbors/${nodeId}).then(res { this.nodes.add(res.data.nodes) this.edges.add(res.data.edges) }) } } } /script4.2 推荐卡片组件Vue 响应式渲染与状态同步src/components/RecommendationCard.vue实现推荐结果的卡片式展示并与图谱组件双向联动!-- src/components/RecommendationCard.vue -- template div classrecommendation-list div v-for(item, index) in recommendations :keyitem.name classcard clickhighlightOnGraph(item.name) :class{ active: highlightedName item.name } h3{{ item.name }} span classscore★{{ item.score }}/span/h3 p classexplanation{{ item.explanation }}/p div classdetails span {{ item.details.address || 暂无地址 }}/span span v-ifitem.details.rating⭐ {{ item.details.rating }}/5/span /div /div /div /template script export default { name: RecommendationCard, props: { recommendations: Array, onHighlight: Function // 父组件传入的高亮回调 }, data() { return { highlightedName: } }, methods: { highlightOnGraph(name) { this.highlightedName name if (this.onHighlight) { this.onHighlight(name) // 通知图谱组件高亮该节点 } } } } /script style scoped .card { padding: 12px; margin: 8px 0; border-radius: 6px; background: #fff; cursor: pointer; transition: all 0.2s; border-left: 4px solid #42b883; } .card:hover { background: #f9f9f9; transform: translateX(4px); } .card.active { background: #e8f5e9; border-left-color: #42b883; } .score { font-size: 14px; color: #ff9800; margin-left: 8px; } /style提示clickhighlightOnGraph(item.name)触发图谱高亮onHighlight由父组件App.vue注入实现组件间通信。这种解耦设计使图谱与推荐列表可独立测试。4.3 环境配置与依赖管理Vue CLI 4.5 Python 3.8 最小兼容方案项目采用 Vue CLI 4.5非 Vite以确保vis-network兼容性package.json关键依赖{ dependencies: { axios: ^0.21.1, core-js: ^3.6.5, vis-network: ^9.1.2, // 注意Vite 对 vis-network 支持不佳故选 Vue CLI vue: ^2.6.11 }, devDependencies: { vue/cli-plugin-babel: ^4.5.0, vue/cli-plugin-eslint: ^4.5.0, vue/cli-service: ^4.5.0 } }Python 环境要求明确写入requirements.txt# requirements.txt Flask2.0.3 py2neo2021.2.3 torch1.10.2cpu # 项目已预编译 CPU 版本避免 CUDA 冲突 torch-geometric2.0.3 numpy1.21.6 pandas1.3.5 tabula-py2.5.0注意torch-geometric安装需严格匹配torch版本执行pip install torch-geometric -f https://data.pyg.org/whl/torch-1.10.2cpu.html项目文档已提供此命令。5. 部署与调试从本地启动到生产环境的平滑过渡技巧5.1 一键启动脚本解决端口冲突与服务依赖顺序新手常卡在“启动顺序”上Neo4j 未就绪时 Flask 就去连接导致报错。scripts/start_all.sh通过健康检查确保服务按序启动#!/bin/bash # scripts/start_all.sh echo ✅ 启动 Neo4j... sudo systemctl start neo4j # 等待 Neo4j 就绪检查 bolt 端口 while ! nc -z localhost 7687; do echo ⏳ 等待 Neo4j 启动... sleep 3 done echo ✅ Neo4j 已就绪 echo ✅ 启动 Flask 后端... cd backend nohup python app.py flask.log 21 FLASK_PID$! sleep 2 if ! kill -0 $FLASK_PID 2/dev/null; then echo ❌ Flask 启动失败请检查 flask.log exit 1 fi echo ✅ Flask 已就绪PID: $FLASK_PID echo ✅ 启动 Vue 前端... cd ../frontend npm run serve技巧nc -z localhost 7687比curl http://localhost:7474更可靠因 Neo4j 的 HTTP 服务可能先于 Bolt 服务就绪。5.2 常见部署问题与精准修复方案现象根本原因修复命令/操作Vue 页面空白控制台报vis_network_1 is not definedvis-network未正确全局注入在main.js顶部添加import * as vis from vis-network; window.vis vis;Flask 报错Connection refused连接 Neo4jNeo4j 未启用远程访问编辑/etc/neo4j/neo4j.conf取消注释dbms.connectors.default_listen_address0.0.0.0并重启sudo systemctl restart neo4j推荐结果为空但图谱查询正常GNN 模型权重文件路径错误检查gnn/scorer.py中torch.load(model/gcn_weights.pth)的相对路径应为os.path.join(os.path.dirname(__file__), ../model/gcn_weights.pth)中文乱码如景点名显示为?Neo4j 默认字符集非 UTF-8在/etc/neo4j/neo4j.conf添加dbms.directories.import/var/lib/neo4j/import并确保导入 CSV 文件为 UTF-8 BOM 编码5.3 毕业答辩演示技巧3 分钟讲清技术亮点答辩时评委最关注“你做了什么独特工作”。建议按此结构陈述配屏幕共享第一分钟直击痛点“传统旅游推荐只做关键词匹配比如搜‘带老人’返回的是标题含‘老人’的景点。而我们的系统通过知识图谱将‘轮椅通道’‘电梯直达’‘休息区’等设施与景点关联并用 GNN 计算这些设施与用户画像的语义距离——这才是真正的‘理解需求’。”第二分钟展示核心证据切换到 Neo4j Browser执行MATCH (p:Place {name:钟水饺总店})-[:HAS_FACILITY]-(f) RETURN p.name, f.type, f.confidence展示三条设施关系及置信度再切到前端输入“带爸妈吃川菜”演示推荐卡片中“解释性文本”如何引用这些置信度数据。第三分钟强调工程价值“代码全部模块化nlu/下意图识别可替换为你们学校的 NLP 课设模型gnn/下打分模块可接入你们实验室的图神经网络论文graph/下 Cypher 查询可直接复用于文旅局的数据治理项目——这不是玩具是可演进的知识图谱工程骨架。”最后一步在答辩 PPT 末页放上项目 GitHub 仓库二维码即使私有也生成临时公开链接并注明“代码含详细中文注释README.md提供从零部署的每一步截图”。这比说“我写了 10000 行代码”有力得多。本文还有配套的精品资源点击获取