资讯动态

Neo4j水浒传人物关系可视化与问答系统:图数据库毕设源码全解析

发布时间:2026/10/3 14:43:52 来源:尧图企业网站定制
简介基于Neo4j的水浒传人物关系可视化及问答系统是一份面向计算机、通信、人工智能、自动化相关专业学生与从业者的毕业设计源码及答辩资料。项目以Python实现后端逻辑结合Neo4j图数据库构建人物关系图谱并提供Web端可视化与问答入口可完整支撑课程设计、大作业或毕业设计二次开发答辩评审分数达98分代码经调试测试可运行降低了直接上手使用的门槛。资源共197个文件压缩包约22.86MB主要包含8个Python源码、HTML/CSS/JS前端页面、Neo4j数据与配置文件、答辩PPT及PDF文档等另附大量人物关系图与界面截图便于对照查看效果。前端基于Bootstrap、Font Awesome、DataTables等常见组件搭建目录结构清晰适合分层阅读与局部修改。目前已有274人浏览学习整体具有较强的学习借鉴价值从入门进阶到答辩展示均能提供完整参考。1. Neo4j水浒传人物关系可视化及问答系统一份能一次跑通的毕设源码Neo4j水浒传人物关系可视化及问答系统这套毕设源码我拿到手的第一反应不是看代码而是先把它跑起来。它把Neo4j图数据库、Python后端和前端关系图可视化串成了一条完整链路相当于一个迷你版知识图谱应用108将的人物关系用图结构铺开带一个能回答“宋江和吴用是什么关系”这类问句的问答模块还配了答辩PPT。对于正在做毕业设计、课程大作业的人或者想从关系数据库跳到图数据库、想搞懂知识图谱落地套路的从业者这套源码的价值在于结构完整、能运行、能照着改。2. 数据建模先行人物节点、关系类型与CSV导入的落库设计2.1 节点与关系类型的设计为什么关系要比人物多花心思拿到这套源码第一步先看它的数据建模。人物节点好设计无非是姓名、绰号、星号、座次这类属性难的是关系怎么抽。水浒传里人物关系远比“认识”复杂宋江和吴用是结义兄弟卢俊义和燕青是主仆林冲和鲁智深是结拜兄弟扈三娘和王英是夫妻中间还穿插师徒、父子、同僚、敌对。建模时如果把所有关系都塞进一个“related”标签后面问答系统根本没法区分“宋江的师父是谁”和“宋江的结义兄弟是谁”。我一般会把关系类型收敛成 8 种以内兄弟、师徒、夫妻、父子、主仆、同僚、敌对、恩仇。关系上挂两个属性一个是事件出处记录这段关系出自原著第几回或哪个情节另一个是强度权重取值 1 到 5这个权重后面做推荐排序和问答打分都能用上。人物属性里除了基础信息建议保留“上山前身份”和“结局”这样问答系统能覆盖“武松的绰号是什么”“鲁智深最后去了哪里”这类信息查询题。表这套系统里常见的关系类型与权重参考关系类型典型场景强度权重兄弟宋江与吴用、林冲与鲁智深5师徒卢俊义与史文恭、王进与史进4夫妻王英与扈三娘、张清与琼英4父子宋太公与宋江、阮氏三兄弟之父4主仆卢俊义与燕青3同僚梁山五虎将之间2敌对武松与蒋门神2恩仇杨志与牛二22.2 数据导入LOAD CSV批量导入与py2neo脚本两种方式人物和关系数据整理成 CSV 后导入 Neo4j 有两条路。最省事的是用 Neo4j 自带的 LOAD CSV把文件丢进安装目录的 import 文件夹直接写 Cypher 导入。另一条路是项目里用 Python 写脚本通过 py2neo 或官方驱动逐条写入适合数据需要先清洗或者要动态生成关系类型的场景。// 导入人物节点CSV放在Neo4j安装目录的import文件夹下 LOAD CSV WITH HEADERS FROM file:///person.csv AS line CREATE (p:Person { name: line.name, // 姓名 nick: line.nick, // 绰号 star: line.star, // 星号如“天魁星” rank: toInteger(line.rank), // 座次 identity: line.identity // 上山前身份 });这段 Cypher 的关键是WITH HEADERS它会把 CSV 第一行当作字段名后面通过line.字段名逐行取值。rank用了toInteger做类型转换因为座次在 CSV 里是字符串存进 Neo4j 后需要是整数才能排序和范围查询。人物节点建完下一步导关系。// 导入关系relation.csv包含start_name、end_name、rel_type、weight、source LOAD CSV WITH HEADERS FROM file:///relation.csv AS line MATCH (a:Person {name: line.start_name}) MATCH (b:Person {name: line.end_name}) CALL apoc.merge.relationship(a, line.rel_type, {}, {weight: toFloat(line.weight), source: line.source}, b, {}) YIELD rel RETURN count(rel);这里用了 APOC 插件的apoc.merge.relationship好处是关系类型可以从 CSV 里动态读不用针对每种关系写一条MERGE。如果你的环境没装 APOC常见做法是按关系类型拆多个MATCH ... MERGE (a)-[:兄弟]-(b)。注意MATCH定位两端的节点依赖第 2.3 节的唯一约束没有约束的话同名人物会匹配出多行导致关系成倍重复这是后面避坑章节第一个要讲的坑。2.3 约束与索引问答请求每次都靠它定位节点这套系统的问答接口每个问题进来都要按人物名字定位节点所以Person.name上必须建唯一约束。数据量虽然只有一百多人但约束带来的索引能让 Cypher 的MATCH (a:Person {name:宋江})走索引查找而不是全表扫描这也是知识图谱应用的一个基本习惯。CREATE CONSTRAINT person_name_unique IF NOT EXISTS FOR (p:Person) REQUIRE p.name IS UNIQUE;约束建完以后重复执行人物 CSV 的导入脚本会直接报错而不是产生脏数据。这一点在毕设答辩时很加分评审问“你怎么保证数据一致性”这一句约束就是最直接的答案。我自己的习惯是数据一进库就先建约束再跑一遍节点数和关系数统计确认数字对得上再开发上层接口。3. 可视化与问答落地Flask接口、ECharts关系图和Cypher查询的串接3.1 后端查询接口Flask路由与Neo4j官方驱动的连接姿势源码的后端是 Python 写的查询接口走 Flask 路由连接 Neo4j 用官方驱动neo4j而不是 py2neo。原因很简单py2neo 到 4.1 版本后基本停更新项目用官方驱动更稳妥连接池管理、session 生命周期都是现成的。我一般把 driver 实例化放在模块级别避免每个请求都新建连接。from flask import Flask, jsonify from neo4j import GraphDatabase app Flask(__name__) # bolt协议连到Neo4j的7687端口auth里的密码改成你自己的 driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, 123456)) def fetch_all_relationships(limit300): with driver.session() as session: result session.run( MATCH (a:Person)-[r]-(b:Person) RETURN a.name AS source, b.name AS target, type(r) AS relation, r.weight AS weight LIMIT $limit, limitlimit ) return [dict(record) for record in result.data()] app.route(/api/graph) def api_graph(): return jsonify({data: fetch_all_relationships()})三个参数值得说明bolt://localhost:7687是 Neo4j 的二进制协议地址和浏览器访问用的http://localhost:7474不是一回事写错端口必报错session.run是短查询的常见用法事务性操作可以换session.execute_writeLIMIT $limit用参数化写法一个是防 Cypher 注入一个是前端图谱渲染几百条边已经是上限全量导出一千多条关系会让浏览器卡死。3.2 前端渲染ECharts关系图与nifty后台模板的资源规划源码包里那一串 CSS 文件对应的是这套系统的前端骨架。bootstrap.min.css 是基础样式nifty.min.css 是后台管理框架侧边栏、顶栏、卡片布局都靠它wiki.css 是人物百科详情页的样式datatables.bootstrap.css 是人物列表表格用的配合 datatables.responsive.css 做响应式ionicons.min.css 和 font-awesome.min.css 是两套图标库那个带 hash 的 css 文件多半是打包工具生成的指纹文件不用管它。这种结构意味着系统不是一个单页 demo而是有后台管理味道的完整 Web 应用。人物关系可视化图是这套系统的门面毕设里最常做的就是 ECharts 的 graph 关系图。把后端/api/graph返回的边数据映射成节点和连线再扔给 ECharts 渲染。// 从后端拉取关系数据 const response await fetch(/api/graph); const data await response.json(); const nodes []; const links []; data.data.forEach(item { if (!nodes.find(n n.name item.source)) { nodes.push({ name: item.source, symbolSize: 28 }); } if (!nodes.find(n n.name item.target)) { nodes.push({ name: item.target, symbolSize: 28 }); } links.push({ source: item.source, target: item.target, label: { show: true, formatter: item.relation } }); }); // 力导向布局节点可拖拽 chart.setOption({ series: [{ type: graph, layout: force, roam: true, draggable: true, data: nodes, links: links, force: { repulsion: 300, edgeLength: 100 }, lineStyle: { color: source, curveness: 0.1 } }] });layout: force让节点按力导向自动排布repulsion控制节点间的斥力数值越大节点越分散edgeLength是边长理想值curveness: 0.1给边加一点弧度——这个细节很实用两个人之间有来有回的双向关系直线会重叠成一根加弧度才能看出是两条边。3.3 问答系统正则模板匹配加Cypher参数化查询问答模块没有上大模型毕设级别用正则模板加 Cypher 查询完全够用。核心是把问句分类每类对应一个查询模式。我拿到源码后第一件事就是把问句分类列出来查询两人关系、查询人物属性、查询某人师父、查询某人结义兄弟。import re def answer_question(question): # 模板1A和B是什么关系 m re.search(r(.?)和(.?)是什么关系, question) if m: a, b m.group(1), m.group(2) with driver.session() as session: records session.run( MATCH (a:Person {name:$a})-[r]-(b:Person {name:$b}) RETURN type(r) AS rel, r.weight AS weight, aa, bb ).data() if records: return f{a}和{b}之间是{、.join(r[rel] for r in records)}关系 return f没查到{a}和{b}的直接关系 # 模板2A的师父/兄弟/妻子是谁 m2 re.search(r(.?)的(师父|兄弟|妻子|父亲)是谁, question) if m2: name, rel m2.group(1), m2.group(2) rel_map {师父: 师徒, 兄弟: 兄弟, 妻子: 夫妻, 父亲: 父子} with driver.session() as session: records session.run( MATCH (a:Person {name:$a})-[:$rel]-(b:Person) RETURN b.name AS name, aname, relrel_map[rel] ).data() return records[0][name] if records else f没查到{name}的{rel} return 这个问题我还答不上来正则顺序是个坑必须先匹配“A 和 B 是什么关系”再匹配“A 的 X 是谁”如果反过来“宋江的师父是谁”会被模板 1 的正则误拆成“宋江的师父”和“谁”两个实体。$a、$b这类参数化写法除了防注入还能避免中文特殊字符破坏 Cypher 语句。问句模板还可以继续叠比如“武松的绰号”“宋江排第几”每加一个模板问答系统的覆盖范围就大一圈这部分扩展空间很大。表问答模板与对应的 Cypher 模式问句类型正则示例Cypher 核心模式两人关系(.?)和(.?)是什么关系(a)-[r]-(b) RETURN type(r)人物属性(.?)的绰号是什么(a {name:$a}) RETURN a.nick关系对象(.?)的师父是谁(a)-[:师徒]-(b) RETURN b.name座次范围排在第几位(a) RETURN a.rank ORDER BY a.rank4. 联调避坑Neo4j安装、驱动连接与前端资源加载的常见问题排查4.1 环境版本搭配Neo4j、Python与驱动的兼容性选择跑这套源码之前先把环境版本对齐不然翻车都翻在起步阶段。Neo4j 社区版 4.x 需要 Java 115.x 需要 Java 17Python 驱动方面neo4j 官方驱动 4.4 系列配 Neo4j 4.4 最稳5.x 驱动配 5.x 服务器。Python 解释器建议 3.8 到 3.10太新的版本有时候会和驱动二进制依赖打架。表本套系统推荐的环境搭配组件推荐版本注意点Neo4j Community4.4.x 或 5.x5.x 必须装 Java 17Java11 或 17版本不对服务直接起不来Python3.8–3.103.11 需确认驱动有对应 wheelneo4j 驱动4.4.x 或 5.x大版本与服务器匹配APOC 插件与 Neo4j 同版本手动放 plugins 目录后重启APOC 插件是最容易忽略的一环如果源码里的导入脚本用了apoc.merge.relationship而你的 Neo4j 没装 APOC报错会非常隐晦直接提示Unknown function。装 APOC 就是把 jar 包丢进 Neo4j 的 plugins 目录然后重启服务。如果你不想依赖 APOC把导入脚本改成按关系类型拆多条MERGE也能跑就是代码冗余一些。4.2 五条踩坑记录现象、原因、解决坑一浏览器打不开 Neo4j 页面Python 连接报Failed to establish connection。现象是 localhost:7474 白屏代码报连不上 bolt 端口。原因多半是 Neo4j 服务根本没启动或者启动了但 7687 端口被占用。解决用neo4j console前台启动日志直接打到终端能看到启动到哪一步挂了端口占用的话lsof -i:7687查一下是谁占的。血泪经验是别用neo4j start后台启动报错信息会被吞掉前台启动才能看清问题。坑二LOAD CSV 导入后中文乱码字段名带\uFEFF。现象是人物名字变成乱码或者第一个字段匹配不上。原因是 Windows 记事本另存为 UTF-8 时会写入 BOM 头Neo4j 把 BOM 当成了字段名的一部分。解决用 VS Code 或 Notepad 把 CSV 转成 UTF-8 无 BOM 格式或者导入时在文件路径后加参数但最省心的还是直接另存为纯 UTF-8。坑三关系重复导入越导越多。现象是同样一条“宋江-吴用-兄弟”在图谱里出现好几遍。原因是导入脚本用了CREATE而不是MERGE或者节点没有唯一约束导致MATCH匹配到多个节点。解决先建 Person 唯一约束再导入关系写入用MERGE或者 APOC 的merge.relationship。这套顺序不能颠倒我在这上面吃过亏导完数据发现关系数翻了三倍又清空重来。坑四前端页面白屏CSS 全部 404。现象是页面结构出来了但没有任何样式F12 一看一堆 css 文件请求失败。原因是源码里的 HTML 直接写死了bootstrap.min.css这类相对路径和 Flask 的 static 目录结构对不上。解决把 CSS 文件放进static/cssHTML 里改用url_for(static, filenamecss/bootstrap.min.css)。那些 nifty 模板的 CSS 文件之间有依赖顺序别乱改加载顺序否则导航栏会塌掉。坑五问“宋江的师父是谁”问答系统答成“宋江和谁是什么关系”。现象是答案驴唇不对马嘴。原因是正则模板的匹配顺序写反了宽泛模板(.?)和(.?)是什么关系先命中把“宋江的师父”和“谁”当成了两个人名。解决把窄模板放前面先匹配“A 的 X 是谁”再匹配“A 和 B 是什么关系”并且正则里对“的”字结构单独处理。问答系统的玄学往往不在算法就在规则顺序。4.3 数据核验导入完先跑几个统计Cypher再开发导入完数据别急着写接口先跑几分钟的查询核验数据质量。这一步能避免你后面调试接口时根本分不清问题是出在代码还是出在数据。// 查节点总数和关系总数 MATCH (n:Person) RETURN count(n) AS person_cnt; MATCH ()-[r]-() RETURN count(r) AS rel_cnt; // 查孤立节点没有任何关系的角色 MATCH (p:Person) WHERE NOT (p)--() RETURN p.name AS lonely_person; // 查重复关系 MATCH (a)-[r]-(b) WITH a, b, type(r) AS t, count(*) AS c WHERE c 1 RETURN a.name, b.name, t, c LIMIT 20;第一个查询确认人物数量对不对第二个查孤立节点原著里有些角色提过名字但没建立关系这类节点如果太多前端图谱上会出现一堆孤零零的点演示时很难看。第三个查重复关系如果查出 c 大于 1 的记录回头检查导入脚本的MERGE写没写对。这三个查询跑完数据层基本就心里有底了。5. 进阶玩法给问答系统加多跳查询与关系权重推荐5.1 多跳关系检索用可变长度路径查宋江的“朋友的朋友”问答系统只能查直接关系是这套源码的一个边界。把它往上推一步可以做多跳查询用户问“宋江的结义兄弟的师父有哪些”这就是两跳路径。Cypher 的可变长度路径就是为这个场景设计的。MATCH p (a:Person {name: 宋江})-[:兄弟|师徒|同僚*1..2]-(b:Person) WHERE a b RETURN b.name AS target, length(p) AS hops, [r IN relationships(p) | type(r)] AS rels ORDER BY length(p) LIMIT 30;*1..2表示路径长度一到两跳关系类型限定在兄弟、师徒、同僚三种避免把“敌人的敌人”这种太远的路径也捞进来。返回的rels数组会显示路径上经过的关系类型比如[兄弟,师徒]前端可以直接展示成“宋江—结义兄弟—某人—师徒—目标”。5.2 关系权重排序让答案从“有一堆”变成“有顺序”多跳查询的结果往往很多直接列出来没有说服力。我一般会把第 2.1 节定义的关系权重叠加起来路径上所有边的权重累加作为这条路径的相关度得分然后按分数倒序返回。结义兄弟权重 5、师徒权重 4、同僚权重 2同样两跳路径“兄弟师徒”的组合自然排在“同僚同僚”前面符合人对关系密切程度的直觉。def multi_hop_ranked(name, max_hops2): with driver.session() as session: records session.run( MATCH p (a:Person {name:$name})-[:兄弟|师徒|夫妻|父子|主仆|同僚*1..2]-(b) WHERE a b RETURN b.name AS target, reduce(s0, r IN relationships(p) | s coalesce(r.weight, 1)) AS score ORDER BY score DESC LIMIT 20, namename ).data() return recordsreduce函数把路径上每条边的 weight 累加起来coalesce(r.weight, 1)处理老数据里没写权重的边默认给 1 分防止降级报错。这一步做完问答系统就从“返回一堆关系”升级成“返回按相关度排序的关系推荐”这个能力放在毕设的创新点里评审基本都会认可。后来我每次做图数据库项目都强制先把约束、数据核验、权重设计走一遍再写业务代码这套流程能救你于各种数据脏乱差的水火之中。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑