
简介本资源为基于知识图谱的可视化问答系统完整项目代码包面向计算机相关专业学生、知识图谱初学者及需要完成类似毕设或课程设计的开发者。项目以医疗领域为背景采用Python 3.10开发后端依托Neo4j图数据库存储实体与关系前端结合JavaScript与CSS实现可视化交互覆盖数据预处理、图谱构建、问句解析与答案检索等核心环节。压缩包共82个文件包含11个Python脚本、9个JavaScript文件、12个CSS样式表、3个HTML页面以及jpg图片、字体文件、json配置与txt词典数据等整体约45.19MB目录结构清晰便于按模块阅读与二次开发。目前已有372人学习下载。读者可从中获得一套可运行的知识图谱问答系统实现方案理解从原始医疗数据到图谱入库、再到前端可视化展示的完整链路并参考其中的分词、问句分类与答案搜索等模块设计适合作为学习知识图谱工程落地的实践参考。1. 从一堆散乱文档到一张可交互的关系网这个项目到底在做什么手里有一批论文、人物、机构、事件数据想快速看出谁和谁合作过、哪个方向最近扎堆、某个关键词背后连着哪些节点——如果还在用 Excel 一行行筛效率会低到让人怀疑人生。这个基于知识图谱的可视化项目解决的就是这件事把结构化或半结构化的实体关系数据导入 Neo4j再用 Python 做后端查询接口前端用 JavaScript 配合 CSS 渲染出一张能拖拽、能缩放、能点开看详情的图谱。它适合两类人一类是刚接触图数据库、想找一个能跑通的完整 Demo 来练手的开发者另一类是手里已经有关系数据、需要一个可视化壳子快速验证业务假设的人。整个项目不依赖复杂中间件核心就是 Neo4j Python 驱动 前端图可视化库跑起来门槛不高但麻雀虽小节点、关系、属性、查询、渲染这条链路是完整的。2. 技术选型与整体架构为什么是 Neo4j 加 Python 再加前端图库2.1 图数据库选型关系数据用关系库存查询会先把自己绕晕知识图谱的核心是「关系」而关系型数据库在处理多跳查询时JOIN 会随着跳数增加呈指数级膨胀。这个项目选 Neo4j本质上是把「关系」作为一等公民来存。节点Node和关系Relationship都带属性查询用 Cypher写起来接近自然语言描述路径。常见做法是实体表映射成节点标签Label比如Person、Institution、Topic关联表映射成关系类型Type比如COOPERATE_WITH、BELONGS_TO、RELATED_TO。这样一条「查某个人两跳内的所有合作者」的查询在 Cypher 里就是一行路径匹配不用写嵌套子查询。// 查询与指定人物直接合作过的所有人物及其机构 MATCH (p:Person {name: $name})-[:COOPERATE_WITH]-(partner:Person) OPTIONAL MATCH (partner)-[:BELONGS_TO]-(org:Institution) RETURN partner.name AS partnerName, org.name AS orgName这里$name是参数化传入避免拼接字符串带来的注入风险。OPTIONAL MATCH保证即使合作者没有机构信息也不会把这条记录丢掉。参数说明p是起点节点partner是合作者节点org是机构节点返回的是合作者姓名和机构名两个字段。2.2 后端分层Python 做查询代理不把数据库连接暴露给前端项目后端用 Python 写核心职责是接收前端请求、拼装 Cypher、调用 Neo4j 驱动、把结果整理成前端图库能吃的 JSON 格式。常见做法是用 Flask 或 FastAPI 起一个轻量服务路由按功能拆成「查节点」「查关系」「查子图」「搜索」几个接口。from neo4j import GraphDatabase from flask import Flask, request, jsonify app Flask(__name__) driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, password)) def query_subgraph(tx, keyword, depth): # 以关键词匹配起点节点再按指定深度扩展子图 cypher MATCH (n) WHERE n.name CONTAINS $keyword WITH n LIMIT 1 MATCH path (n)-[*1..%d]-(m) RETURN path % depth result tx.run(cypher, keywordkeyword) return [record[path] for record in result] app.route(/api/subgraph) def subgraph(): keyword request.args.get(keyword, ) depth int(request.args.get(depth, 2)) with driver.session() as session: paths session.execute_read(query_subgraph, keyword, depth) return jsonify(serialize_paths(paths))逻辑说明query_subgraph先用CONTAINS做模糊匹配找到起点再用变长路径[*1..depth]扩展。depth参数控制跳数前端一般限制在 1 到 3 之间再大查询会明显变慢。serialize_paths是自定义函数把 Neo4j 的 Path 对象转成{nodes: [], links: []}的结构这是前端图库的标准输入格式。参数说明keyword是搜索词depth是扩展深度bolt://localhost:7687是 Neo4j 默认 Bolt 协议地址。2.3 前端渲染JavaScript 负责交互CSS 负责视觉分层前端用 JavaScript 调用后端接口拿到节点和边数据再交给图可视化库渲染。常见做法是用 D3.js 或 ECharts 的 graph 系列。D3 更灵活但学习曲线陡ECharts 上手快、配置项多。这个项目如果追求快速出效果ECharts 的graph类型是更稳的选择。// 请求子图数据并渲染 fetch(/api/subgraph?keyword${encodeURIComponent(keyword)}depth2) .then(res res.json()) .then(data { const chart echarts.init(document.getElementById(graph)); chart.setOption({ series: [{ type: graph, layout: force, // 力导向布局节点自动散开 roam: true, // 允许缩放和拖拽 label: { show: true }, // 显示节点名称 force: { repulsion: 200 },// 节点斥力值越大越分散 data: data.nodes, links: data.links, categories: data.categories }] }); });逻辑说明layout: force让节点按力导向算法自动排布避免手动算坐标。roam: true开启鼠标缩放和拖拽这是图谱交互的基础。repulsion控制节点之间的斥力太小会挤成一团太大会散到屏幕外一般从 100 到 500 之间调。categories用于按节点类型着色CSS 里可以进一步覆盖图例和 tooltip 的样式。2.4 数据流全景一次查询从点击到渲染经过了什么把上面三层串起来看用户在搜索框输入关键词前端发请求到/api/subgraphPython 后端收到后拼 Cypher通过 Bolt 协议发给 Neo4jNeo4j 执行路径查询返回 Path 对象后端把 Path 序列化成{nodes, links}前端拿到 JSON 后交给 ECharts 渲染成力导向图。整条链路没有缓存层每次查询都实时打数据库适合数据量不大、追求实时性的场景。如果节点上万常见做法是在后端加一层内存缓存或 Redis把热门子图结果缓存几秒到几分钟。3. 目录结构与核心模块拆解拿到 zip 包先看哪里3.1 目录结构前后端分离配置集中管理解压后一般能看到这样的结构knowledge-graph-demo/ ├── backend/ │ ├── app.py # Flask 入口路由定义 │ ├── db.py # Neo4j 驱动封装 │ ├── serializer.py # Path 转 JSON 的序列化逻辑 │ └── requirements.txt # Python 依赖 ├── frontend/ │ ├── index.html # 页面骨架 │ ├── js/ │ │ ├── main.js # 请求与渲染主逻辑 │ │ └── config.js # 接口地址、图参数配置 │ └── css/ │ └── style.css # 图容器、图例、tooltip 样式 ├── data/ │ └── import.cypher # 初始化节点和关系的 Cypher 脚本 └── README.mdbackend和frontend完全分离前端可以直接用浏览器打开index.html也可以挂到 Nginx 下。data/import.cypher是数据入口里面是一批CREATE或MERGE语句用来把示例数据灌进 Neo4j。config.js里通常放API_BASE_URL和图布局参数改接口地址不用翻main.js。3.2 数据导入模块用 Cypher 脚本批量建节点和关系数据导入是这个项目最先要跑通的一步。import.cypher里一般会先建约束再建节点最后建关系。// 建唯一约束避免重复插入同名节点 CREATE CONSTRAINT person_name IF NOT EXISTS FOR (p:Person) REQUIRE p.name IS UNIQUE; // 批量创建人物节点 MERGE (a:Person {name: 张三}) SET a.age 35; MERGE (b:Person {name: 李四}) SET b.age 42; // 创建合作关系 MATCH (a:Person {name: 张三}), (b:Person {name: 李四}) MERGE (a)-[:COOPERATE_WITH {project: 某课题}]-(b);逻辑说明CREATE CONSTRAINT保证Person的name唯一这样MERGE不会重复建节点。MERGE是「有则匹配、无则创建」比CREATE更适合导入场景。关系上的project属性可以记录合作项目名前端点击边的时候可以展示。参数说明约束名person_name可自定义IF NOT EXISTS避免重复执行报错。3.3 查询接口模块把前端参数翻译成 Cypher后端接口的核心是把前端的keyword、depth、limit翻译成安全的 Cypher。除了子图查询通常还有一个「节点详情」接口返回某个节点的所有属性和直接邻居。app.route(/api/node/node_id) def node_detail(node_id): with driver.session() as session: result session.run( MATCH (n) WHERE id(n) $node_id OPTIONAL MATCH (n)-[r]-(m) RETURN n, r, m , node_idint(node_id)) return jsonify(serialize_node_detail(result))逻辑说明id(n)是 Neo4j 内部节点 ID前端渲染时把每个节点的 ID 存下来点击时回传。OPTIONAL MATCH保证即使节点没有邻居也能返回自身信息。参数说明node_id从 URL 路径取转成 int 后传入。注意 Neo4j 的内部 ID 在删除重建后可能复用生产环境更稳妥的做法是给每个节点加一个业务唯一 ID 属性。3.4 前端渲染模块力导向布局参数怎么调前端渲染的难点不在「画出来」而在「画得能看」。力导向布局有几个关键参数repulsion斥力、edgeLength边长、gravity向心力。节点少的时候默认值就够节点一多就容易糊成一团。force: { repulsion: 300, // 节点间斥力越大越分散 edgeLength: 120, // 边的理想长度 gravity: 0.1, // 向心力防止节点飘出画布 layoutAnimation: true }逻辑说明repulsion和edgeLength需要配合调。如果节点密集先加repulsion如果边太长导致图太散减edgeLength。gravity一般保持小值太大所有节点会挤向中心。参数说明这些值没有万能公式常见做法是先用默认值渲染再根据实际数据密度微调。4. 跑起来从零到浏览器里出现一张图的完整步骤4.1 环境准备Neo4j 和 Python 依赖先装 Neo4j社区版就够。装完后启动服务默认 Bolt 端口 7687HTTP 端口 7474。浏览器打开http://localhost:7474能用默认账号neo4j和初始密码登录首次登录会强制改密码。Python 侧建议用虚拟环境python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r backend/requirements.txtrequirements.txt里通常有neo4j、flask、flask-cors三个包。flask-cors是为了解决前端直接打开index.html时的跨域问题。4.2 导入示例数据先跑通再换自己的数据在 Neo4j Browser 里打开data/import.cypher整段复制执行。如果数据量大用cypher-shell命令行导入更快cat data/import.cypher | cypher-shell -u neo4j -p your_password执行完后在 Browser 里跑一句MATCH (n) RETURN count(n)确认节点数。如果返回 0说明脚本没执行成功检查约束是否冲突、语法是否有拼写错误。4.3 启动后端确认接口能返回 JSONcd backend python app.py默认跑在5000端口。用 curl 测一下curl http://localhost:5000/api/subgraph?keyword张三depth2如果返回{nodes: [...], links: [...]}结构说明后端通了。如果报连接错误检查 Neo4j 是否启动、密码是否对、db.py里的地址是否写错。4.4 打开前端改配置、看效果frontend/js/config.js里把API_BASE_URL改成http://localhost:5000。然后用浏览器直接打开index.html或者在frontend目录下起一个静态服务python -m http.server 8080访问http://localhost:8080在搜索框输入一个示例数据里存在的名字点查询。如果图没出来按 F12 看 Network 面板确认请求是否发出、返回是否正常、控制台有没有报错。5. 避坑与常见问题那些第一次跑必踩的坑5.1 现象前端请求报 CORS 错误图一直空白原因前端直接以file://协议打开或者前端端口和后端端口不一致浏览器拦截跨域请求。解决后端加flask-cors或者用python -m http.server起前端保证前后端都在http://下。更省事的做法是在app.py里加一行CORS(app)。5.2 现象Cypher 查询报「变量未定义」或「语法错误」原因Cypher 对大小写和标点敏感MATCH写成match一般没事但关系类型写成小写、属性名拼错、漏掉冒号都会报错。解决先在 Neo4j Browser 里单独跑一遍 Cypher确认语法通过再放进 Python。Browser 的报错信息比驱动返回的更具体。5.3 现象图渲染出来但节点全挤在中心拖不动原因repulsion太小或者roam没开。解决把repulsion调到 300 以上确认roam: true。如果节点超过 200 个力导向布局会明显卡顿常见做法是限制返回节点数或者改用layout: circular做概览。5.4 现象Neo4j 连接报「authentication failure」原因密码没改或者改完没同步到db.py。解决Neo4j 首次登录必须改密码改完后把db.py里的auth元组同步更新。如果忘了密码停掉 Neo4j删掉data/dbms/auth文件重启可以重置。5.5 现象导入脚本执行到一半报约束冲突原因重复执行import.cypher时CREATE CONSTRAINT已经存在会报错。解决约束语句加IF NOT EXISTS节点用MERGE而不是CREATE。如果已经产生重复节点先MATCH (n:Person) DETACH DELETE n清空再重新导入。6. 二次开发换数据、加交互、做权限的改造思路拿到这个项目后最直接的改造是换数据。把import.cypher里的示例节点替换成自己的实体关系类型按业务重新命名。如果数据在 CSV 或 Excel 里常见做法是写一个 Python 脚本读表用session.run批量MERGE比手写 Cypher 快得多。import csv from neo4j import GraphDatabase driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, password)) def import_person(tx, name, age): tx.run(MERGE (p:Person {name: $name}) SET p.age $age, namename, ageage) with open(data/persons.csv, encodingutf-8) as f: reader csv.DictReader(f) with driver.session() as session: for row in reader: session.execute_write(import_person, row[name], int(row[age]))逻辑说明execute_write会自动管理事务适合批量写入。MERGE保证重复执行不会产生重复节点。参数说明CSV 表头需要和代码里的字段对应age转 int 前要确保数据干净。第二个改造方向是加交互。默认只有点击节点看详情可以扩展成右键菜单、双击展开邻居、框选批量操作。ECharts 的graph支持emphasis高亮和select选中配合chart.on(click, ...)就能拿到节点数据。如果要加「展开邻居」就在点击事件里再发一次/api/subgraph把当前节点 ID 作为起点把返回的新节点合并到现有数据里再setOption刷新。第三个方向是权限。如果图谱数据分不同用户可见后端接口需要加鉴权。常见做法是加一个简单的 token 校验或者在 Neo4j 里给节点打上owner属性查询时带上WHERE n.owner $user。这一步不要等到上线才做因为 Cypher 拼接一旦涉及用户输入注入风险比 SQL 更隐蔽。最后一个技巧是关于性能的。当节点超过 500 个前端渲染会开始卡。我一般会在后端加一个limit参数默认只返回 100 个节点前端加「加载更多」按钮。另外Neo4j 的变长路径查询在深度超过 3 之后会明显变慢如果业务确实需要深路径考虑用 APOC 库的路径扩展过程或者提前把常用路径物化成关系。从那以后我每次拿到一个图数据库项目都会先把depth限制在 2 以内跑通全链路再逐步放开。这个习惯帮我省了很多次「查询跑飞、浏览器卡死」的后悔药。希望帮到你。本文还有配套的精品资源点击获取