基于Neo4j的水浒人物关系问答系统实战
简介这份资源围绕《水浒传》人物关系展开基于Neo4j图数据库构建可视化与问答系统面向计算机相关专业学生及企业员工可用于课程设计、大作业、毕设或初期项目立项演示也适合作为图数据库与知识图谱方向的实战练习素材。压缩包共197个文件约22.84MB包含8个Python源码文件、4个HTML页面、8个JavaScript脚本、11个CSS样式表以及大量jpg与png示例图片另附说明文档、PPT演示文稿和PDF资料覆盖系统实现、界面展示与答辩汇报等环节。目前已有343人学习下载。读者可从中获得可运行的完整项目代码、Neo4j数据建模与关系查询思路、前端可视化页面实现方式以及问答模块的交互逻辑配合说明文档与PPT能快速理解整体架构便于二次开发与学习借鉴。1. 从「一百单八将」到一张图为什么用 Neo4j 做水浒人物关系问答读《水浒传》最劝退的地方不是文言白话夹杂而是一百单八将加上高俅、西门庆、潘金莲这些配角人物关系盘根错节。你想查「宋江直接或间接认识哪些朝廷官员」翻书翻到崩溃你想知道「武松和李逵之间隔了几个人」靠脑子根本算不出来。这类问题本质上是图论问题不是文本检索问题。用 Neo4j 把人物和关系存成节点与边再配一个 Python 问答系统把自然语言转成 Cypher 查询就能把「翻书」变成「查图」。这套方案适合三类人想入门知识图谱的 Python 开发者、需要做课程设计的学生、以及想用真实数据练 Neo4j 查询的工程师。它不依赖大模型纯规则匹配就能跑通落地成本低但能让你把图数据库的核心操作全部走一遍。2. 数据建模与 Neo4j 环境搭建从人物表到图结构2.1 为什么选属性图而不是关系表水浒人物关系如果用 MySQL 存至少需要三张表人物表、关系表、关系类型表。查「宋江的朋友的朋友」要写三层自连接SQL 又长又慢。Neo4j 的属性图模型把人物存成节点关系存成边边本身可以带属性比如「结拜」关系可以带结拜地点。查多跳关系就是MATCH (a)-[:REL*1..3]-(b)一行搞定。更关键的是问答系统需要把自然语言里的「谁和谁是什么关系」映射成图查询图结构天然贴合这种表达。常见做法是人物节点标签用Person关系类型用中文或拼音比如结拜、师徒、上下级、亲属。我一般会把关系类型统一成英文大写避免 Cypher 里中文转义问题比如SWORN_BROTHER、MASTER_STUDENT。2.2 安装 Neo4j 社区版并导入水浒数据Neo4j 社区版免费Windows 和 macOS 都有桌面版Linux 用 tar.gz 离线包。安装时最容易翻车的是 Java 版本Neo4j 4.x 需要 JDK 115.x 需要 JDK 17。装完启动浏览器打开http://localhost:7474默认账号密码都是neo4j首次登录强制改密码。下面是用 Python 驱动批量导入人物和关系的脚本数据源可以自己整理成 CSV两列from,to,relation。from neo4j import GraphDatabase import csv # 连接本地 Neo4j默认 bolt 端口 7687 driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, 你的密码)) def load_data(tx, rows): # 先合并人物节点MERGE 避免重复创建 for row in rows: tx.run( MERGE (a:Person {name: $from}) MERGE (b:Person {name: $to}) MERGE (a)-[r:REL {type: $relation}]-(b), fromrow[from], torow[to], relationrow[relation] ) with open(shuihu_relations.csv, encodingutf-8) as f: reader list(csv.DictReader(f)) with driver.session() as session: session.execute_write(load_data, reader) driver.close()这段代码的逻辑是逐行读取 CSV用MERGE保证节点不重复关系类型存在边的type属性里。参数说明from和to是人物姓名relation是关系描述。注意MERGE在数据量大时性能一般如果超过一万行建议先用LOAD CSV命令导入速度能快五到十倍。导入完成后在 Neo4j 浏览器里执行MATCH (n) RETURN count(n)验证节点数正常应该在一百五十到两百之间含配角。2.3 验证图结构三个必查的 Cypher导入后别急着写问答先跑三条查询确认图是对的。第一条查节点总数和关系总数第二条查宋江的所有直接关系第三条查任意两人之间的最短路径。// 1. 统计节点和关系数量 MATCH (n:Person) RETURN count(n) AS 人物数; MATCH ()-[r]-() RETURN count(r) AS 关系数; // 2. 查宋江的直接关系 MATCH (a:Person {name: 宋江})-[r]-(b) RETURN a.name, type(r), b.name LIMIT 20; // 3. 查武松到李逵的最短路径 MATCH p shortestPath((a:Person {name: 武松})-[*..6]-(b:Person {name: 李逵})) RETURN p;第一条确认数据完整第二条检查关系方向是否正确如果查不到多半是导入时方向反了第三条验证图连通性如果返回空说明两人之间没有路径需要检查数据是否漏了关键人物。这里有个血泪经验shortestPath的*..6表示最多六跳水浒人物关系一般三跳内就能连通设太大反而拖慢查询。3. 问答系统实现把自然语言转成 Cypher 查询3.1 规则匹配还是大模型选型理由问答系统有两种做法一是用大模型做意图识别和 Cypher 生成二是用规则模板匹配。大模型方案灵活但需要 API 费用而且生成 Cypher 容易出错尤其是中文人名和关系类型。规则方案虽然死板但可控、可调试、零成本。对于水浒这种封闭领域人物和关系类型有限规则匹配足够覆盖百分之八十的常见问题。我一般会先定义问题模板比如「A和B是什么关系」「A的朋友有哪些」「A和B之间隔了几个人」然后用正则提取人名填充到 Cypher 模板里。这样即使新手也能看懂每一步在做什么方便二次修改。3.2 用 Python 写一个最小可用的问答引擎下面是一个基于正则和模板的问答引擎核心代码。它接收用户输入提取人名匹配问题类型执行 Cypher返回结果。import re from neo4j import GraphDatabase driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, 你的密码)) # 问题模板正则 Cypher 模板 PATTERNS [ (r(.?)和(.?)是什么关系, MATCH (a:Person {name: $a})-[r]-(b:Person {name: $b}) RETURN type(r) AS 关系), (r(.?)的朋友有哪些, MATCH (a:Person {name: $a})-[:REL {type: 朋友}]-(b) RETURN b.name AS 朋友), (r(.?)和(.?)之间隔了几个人, MATCH p shortestPath((a:Person {name: $a})-[*..6]-(b:Person {name: $b})) RETURN length(p) - 1 AS 中间人数), ] def answer(question): for pattern, cypher in PATTERNS: match re.search(pattern, question) if match: params {a: match.group(1), b: match.group(2)} if match.lastindex 2 else {a: match.group(1)} with driver.session() as session: result session.run(cypher, **params) return [record.data() for record in result] return 暂时无法回答这个问题 # 测试 print(answer(宋江和武松是什么关系)) print(answer(宋江的朋友有哪些)) print(answer(武松和李逵之间隔了几个人))逻辑说明PATTERNS列表按顺序匹配先匹配到的先执行。re.search提取括号里的内容作为参数。session.run执行 Cypher 并返回结果。参数说明$a和$b是 Cypher 参数占位符避免 SQL 注入。注意match.lastindex判断提取了几个人名一个参数的模板只传a。这个引擎的局限是只能处理固定句式但胜在稳定新手可以在此基础上加更多模板。3.3 可视化用 pyvis 把查询结果画成关系图问答系统返回文字结果不够直观加上可视化才算完整。用pyvis可以把 Neo4j 查询结果渲染成交互式 HTML 图。下面代码查宋江的两跳关系并生成网页。from pyvis.network import Network from neo4j import GraphDatabase driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, 你的密码)) def visualize(name): net Network(height600px, width100%, directedFalse) with driver.session() as session: result session.run( MATCH (a:Person {name: $name})-[r*1..2]-(b) RETURN a.name AS 起点, b.name AS 终点, [rel IN r | type(rel)] AS 关系链, namename ) for record in result: net.add_node(record[起点], labelrecord[起点]) net.add_node(record[终点], labelrecord[终点]) net.add_edge(record[起点], record[终点], titlestr(record[关系链])) net.show(shuihu_graph.html) visualize(宋江)参数说明height和width控制画布大小directedFalse表示无向图。r*1..2表示查一到两跳关系跳数越多图越密建议不超过三跳。生成的 HTML 文件用浏览器打开节点可以拖拽边悬停显示关系链。注意pyvis依赖networkx安装时用pip install pyvis networkx。4. 避坑与排查导入、查询、可视化里的五个真实翻车点4.1 现象Neo4j 启动报错「Java not found」原因Neo4j 依赖 JDK但系统没装或者版本不对。Neo4j 4.4 需要 JDK 115.x 需要 JDK 17。解决先java -version确认版本不对就卸载重装对应版本。Windows 用户注意环境变量JAVA_HOME要指向 JDK 根目录不是 bin 目录。4.2 现象Python 驱动连接超时「Unable to connect to localhost:7687」原因Neo4j 服务没启动或者防火墙拦了 bolt 端口。解决先确认 Neo4j 桌面版或服务已运行浏览器能打开http://localhost:7474。如果浏览器能开但 Python 连不上检查neo4j.conf里dbms.connector.bolt.listen_address是否为:7687。Linux 下用systemctl status neo4j看服务状态。4.3 现象导入后查不到关系MATCH ()-[r]-() RETURN count(r)返回 0原因CSV 里关系类型列名和代码里的$relation不匹配或者 CSV 编码不是 UTF-8。解决用head -5 shuihu_relations.csv检查列名确保是from,to,relation。编码问题用file -i查看不是 UTF-8 就用iconv转换。另外MERGE关系时如果没加type属性查type(r)会返回空。4.4 现象问答系统提取人名错误比如「宋江和武松是什么关系」提取出「宋江和武松」原因正则(.?)和(.?)是贪婪匹配的变体但中文里「和」可能出现在人名中比如「何和」。解决把人名列表预先加载用|连接做精确匹配比如(宋江|武松|林冲|...)和(宋江|武松|...)。或者用jieba分词先切出人名再匹配。我一般会维护一个person_list从 Neo4j 里查所有Person节点动态生成正则。4.5 现象pyvis 生成的图节点重叠看不清原因默认布局是随机力导向节点多了会挤在一起。解决在Network初始化时加notebookFalse和cdn_resourcesin_line然后设置net.barnes_hut(gravity0.5, central_gravity0.3, spring_length200)。如果还乱减少查询跳数或者只画指定关系类型。另外net.show()在 Jupyter 里可能不显示改用net.save_graph(graph.html)再手动打开。5. 进阶技巧用 APOC 插件做路径推荐和问答扩展5.1 装 APOC 插件解锁更多图算法Neo4j 社区版自带的功能有限装 APOC 插件后可以用apoc.path.expandConfig做更灵活的路径查询还能调用apoc.algo里的图算法。安装方法从 Neo4j 官方下载对应版本的 APOC jar 包放到plugins目录然后在neo4j.conf里加一行dbms.security.procedures.unrestrictedapoc.*重启服务。验证在浏览器执行RETURN apoc.version()能返回版本号就成功了。5.2 用 APOC 查「宋江到高俅的最短关系链」普通shortestPath只能查跳数最少的路径但你可能想查「经过最多结拜关系」的路径。APOC 的apoc.path.expandConfig支持按关系类型过滤和排序。MATCH (a:Person {name: 宋江}), (b:Person {name: 高俅}) CALL apoc.path.expandConfig(a, { relationshipFilter: REL, minLevel: 1, maxLevel: 6, terminatorNodes: [b], uniqueness: NODE_PATH }) YIELD path RETURN path, length(path) AS 跳数 ORDER BY 跳数 ASC LIMIT 5;参数说明relationshipFilter: REL表示只沿REL类型的关系正向扩展terminatorNodes指定终点uniqueness: NODE_PATH避免重复节点。这条查询会返回五条从宋江到高俅的路径按跳数排序。你可以把结果喂给问答系统回答「宋江怎么认识高俅的」这类问题。5.3 问答系统加一个「关系推理」模板有了 APOC问答系统可以支持更复杂的问题比如「宋江通过谁认识的高俅」。实现思路先查最短路径再提取路径上的中间节点。def how_connected(a, b): cypher MATCH (a:Person {name: $a}), (b:Person {name: $b}) CALL apoc.path.expandConfig(a, { relationshipFilter: REL, minLevel: 1, maxLevel: 6, terminatorNodes: [b], uniqueness: NODE_PATH }) YIELD path RETURN [n IN nodes(path) | n.name] AS 路径 ORDER BY length(path) ASC LIMIT 1 with driver.session() as session: result session.run(cypher, aa, bb) record result.single() if record: path record[路径] return - .join(path) return 没有找到连接路径 print(how_connected(宋江, 高俅))这段代码返回一条最短路径的节点序列比如「宋江 - 吴用 - 高俅」。逻辑说明nodes(path)提取路径上所有节点n.name取姓名join拼成字符串。注意 APOC 的expandConfig在数据量大时可能慢建议加LIMIT和maxLevel限制。我一般会把常用查询缓存到字典里避免重复查库。5.4 一个我踩过的坑APOC 版本必须和 Neo4j 严格对应有次我图省事把 Neo4j 4.4 的 APOC 包放到 5.3 的 plugins 目录结果启动直接报ClassNotFoundException。APOC 的版本号前两位必须和 Neo4j 一致比如 Neo4j 5.3 只能用 APOC 5.3.x。下载时看清楚文件名里的版本号别下错。另外dbms.security.procedures.unrestricted配置项在 Neo4j 5 里改成了dbms.security.procedures.unrestrictedapoc.*但如果你只用了部分过程可以只放开用到的减少安全风险。这套方案从建图到问答再到可视化完整走一遍大概需要两三天其中环境搭建占一半时间。我的习惯是先把 Neo4j 跑起来导入十条测试数据确认查询和可视化都通了再批量导入全量数据。这样出问题能快速定位是数据问题还是代码问题。希望帮到你。本文还有配套的精品资源点击获取