
简介面向毕业设计及课程设计场景这套基于Neo4j的水浒传人物关系可视化与问答系统源码适合计算机、人工智能、自动化等专业学生与从业者学习使用。项目围绕古典文本中的人物关系建模覆盖实体抽取、图数据库存储、前端图谱展示和问答接口实现等关键环节答辩评审达98分代码经调试后可运行具有很高的实践参考价值。资源共197个文件约22.86MB包含8个Python源码文件、HTML/CSS/JS前端资源以及129张JPG图片另附答辩PPT与PDF文档包内素材较丰富既能支撑图谱展示效果也便于多角度查阅项目资料。已有274人浏览学习适合期末课程设计、课程大作业或毕业设计也可用于学习Neo4j知识图谱的构建方法、图谱可视化与自然语言问答的设计思路并在此基础上扩展自己的功能。整体实现链路完整能帮助快速理解从原始文本到图数据库、再到交互问答的落地过程。1. 这项目到底在做什么人物关系不是表格是图把《水浒传》里一百单八将和主要配角的关系铺开用传统表格或关系型数据库存查“宋江和晁盖之间隔了几个人”这种问题会写到怀疑人生。这个基于 Neo4j 的水浒传人物关系可视化及问答系统本质上就是先把原著里的人物实体和“结拜、上下级、仇敌、师徒”这类关系抽出来存进图数据库再在上层做两件事一是用 ECharts 渲染出力导向图让整个梁山关系网肉眼可见二是用 Python 写一个基于规则模板的问答接口里输入“武松的结拜兄弟有哪些”它把自然语言拆成 Cypher 查询再返回答案。毕业设计或课程设计选这个题好处是技术栈清晰、演示效果好、答辩时容易讲深项目源码和答辩 PPT 通常是配套打包的但拿到源码只是起点能讲清楚建模和查询逻辑才是拿高分的关键。2. 从原著到图谱水浒人物数据建模与 Neo4j 导入的完整步骤2.1 关系怎么设计先把“关系”浓缩成五到八种拿到源码的第一步不是先看查询代码而是看它的数据模型。常见做法是把人物设为 Label 为 Person 的节点属性只有 name、alias绰号、rank座次、梁山内职位等关系则是重头戏。我见过有项目把所有交互都抽象成泛泛的 related这种模型画图还行问答阶段就废了——用户问“宋江和卢俊义谁级别高”你没法用 related 回答。高分的建模会把关系收敛到有限枚举。参考常见课程设计最常出现的关系有六到八种关系方向示例属性建议结拜双向宋江-武松无师徒单向师→徒林冲-曹正无上下级单向上→下晁盖-阮小二职位/管辖仇敌双向武松-西门庆冲突原因亲属双向扈三娘-王英关系类型同乡双向呼延灼-杨志籍贯对抗双向梁山-高俅战役为什么强调“有限枚举”因为后续问答系统要做问题分类每个问题类型对应一类 Cypher 模式关系种类越多规则模板越难维护。五到八种是最平衡的区间。项目源码里如果关系类型超过十种建议合并——把“义兄义弟”和“结拜”合并成结拜否则问答准确率会崩。2.2 用手工整理转 CSV数据清洗这一步决定了项目下限课程设计级别的项目一般不会用 NLP 从原著自动抽取关系准确率不可控答辩也难解释而是人工或半人工整理。这里给出一个稳妥的数据准备流程先准备 nodes.csv 和 relations.csv。nodes.csv 格式示例name,alias,rank,role,status 宋江,及时雨,1,总头领,梁山 卢俊义,玉麒麟,2,副头领,梁山 ...relations.csv 格式示例source,target,relation 宋江,武松,结拜 林冲,曹正,师徒 高俅,林冲,仇敌清洗时建议直接用 pandas 做三件事去重检查同名人物注意“宋江”和“宋公明”指同一人、过滤去掉只出现过名字但没有实际关系互动的角色、规范统一人名和绰号。import pandas as pd nodes pd.read_csv(raw_nodes.csv, encodingutf-8) relations pd.read_csv(raw_relations.csv, encodingutf-8) # 1. 人名归一化把同一人物的不同称谓映射到统一姓名 alias_map {宋公明: 宋江, 呼保义: 宋江, 鲁提辖: 鲁智深, 豹子头: 林冲} nodes[name] nodes[name].replace(alias_map) relations[source] relations[source].replace(alias_map) relations[target] relations[target].replace(alias_map) # 2. 删除信息缺失且无关紧要的行 nodes nodes.dropna(subset[name]).drop_duplicates(subset[name]) # 3. 只保留双方都是有效人物的关系避免悬空引用 valid_names set(nodes[name]) relations relations[relations[source].isin(valid_names) relations[target].isin(valid_names)] nodes.to_csv(nodes.csv, indexFalse, encodingutf-8) relations.to_csv(relations.csv, indexFalse, encodingutf-8) print(f节点数: {len(nodes)}, 关系数: {len(relations)})代码逻辑说明第一步用 replace 做人物别名归并原著里同一人常有多个称呼这一步不做后面查询会出来两个孤立节点第二步 drop_duplicates 保证 Person 节点 name 唯一Neo4j 唯一性约束也是靠这个字段第三步过滤悬空关系——如果 source 或 target 不在 nodes 里导入后会生成孤儿节点。参数方面encoding 建议统一 utf-8avoid 用 utf-8-sig不然 Neo4j 的 LOAD CSV 读中文容易踩编码坑这一点在第 5 章单独说。2.3 LOAD CSV 导入与约束Cypher 写得好不如约束建得早Neo4j 导入常用两种方式数据量小几千条以内用 LOAD CSV 足够方便数据量大再考虑 neo4j-admin import。水浒传一百零八将加配角满打满算两三百节点LOAD CSV 完全没压力。Neo4j Desktop 的操作路径是先建一个项目启动本地数据库默认 Bolt 端口 7687HTTP 端口 7474在 Browser 中打开localhost:7474。注意 2026 年新版 Neo4j Desktop 已经默认要求密码复杂度数据库启动失败时先看密码策略这一条经常让新手卡住。连接上后先建唯一性约束再导节点最后导关系CREATE CONSTRAINT person_name_unique IF NOT EXISTS FOR (p:Person) REQUIRE p.name IS UNIQUE; LOAD CSV WITH HEADERS FROM file:///nodes.csv AS row MERGE (p:Person {name: row.name}) SET p.alias row.alias, p.rank toInteger(row.rank), p.role row.role; LOAD CSV WITH HEADERS FROM file:///relations.csv AS row MATCH (source:Person {name: row.source}) MATCH (target:Person {name: row.target}) MERGE (source)-[r:RELATION {type: row.relation}]-(target);参数说明WITH HEADERS表示第一行是属性名MERGE比CREATE安全相同 name 的节点不会重复创建toInteger()把 CSV 里的字符串座次转成整数排序时才能按数字比大小关系上的type属性用来存“结拜/师徒/仇敌”等类型而不是把每种关系各做成一个 Relationship Type这是后期用r.type做问答过滤的关键。导入完成后验证这两条命令MATCH (n:Person) RETURN count(n)应该等于 nodes.csv 行数MATCH (:Person)-[r]-(:Person) RETURN r.type, count(*)可以看各类型关系分布是否合理。这一步做好了可视化层才有干净的数据源。3. 可视化层用 ECharts 力导向图把人物关系网画出层次感3.1 为什么不用 Neo4j Browser 直接当可视化方案Neo4j Browser 本身能展示图数据但它有两个先天问题一是界面风格固定答辩演示时观感像在操作数据库工具不够“产品化”二是它渲染的是 Cypher 原样返回的节点和关系没法做节点大小按度值缩放、颜色按阵营分组这种定制。课程设计要高分演示界面的第一印象占比很大常见做法是自建一个 Flask 后端把 Neo4j 查询结果转成 ECharts 图数据结构前端用 ECharts 的 graph 类型渲染力导向图。这里顺带说明可视化技术选型如果做网页展示ECharts 是首选百度系产品文档全中文社区案例多graph 类型天然支持力导向布局和拖拽想做得更炫可以上 G6蚂蚁出品适合交互式探索但课程设计不建议直接用 G6学习成本高答辩时间紧容易顾此失彼。可视化大屏方向的同学也可以参考 ECharts 的visualMap组件给人物按“梁山内/梁山外”分组着色这个在答辩 PPT 里很出效果。3.2 Flask 后端封装查询接口把 Cypher 藏在 HTTP 后面完整的可视化链路是前端页面 → Flask API → Neo4j → 返回到前端渲染。这里给出 Flask 端查询全量人物关系的核心代码from flask import Flask, jsonify from py2neo import Graph app Flask(__name__) graph Graph(bolt://localhost:7687, auth(neo4j, your_password)) app.route(/api/graph) def get_graph(): # 查询人物节点和关系LIMIT 控制一次返回的上限避免前端卡死 cql MATCH (p:Person) OPTIONAL MATCH (p)-[r]-(target:Person) RETURN p.name AS name, p.alias AS alias, p.rank AS rank, r.type AS rel_type, target.name AS target_name LIMIT 500 data graph.run(cql).data() nodes, edges [], [] node_set set() # 把平铺的查询结果转成 ECharts 需要的 nodes / edges 数组 for record in data: if record[name] and record[name] not in node_set: nodes.append({ id: record[name], name: record[name], alias: record.get(alias), rank: record.get(rank), symbolSize: 10 (record.get(rank) or 100) // 10 # 座次越靠前节点越大 }) node_set.add(record[name]) if record[target_name]: edges.append({ source: record[name], target: record[target_name], relation: record[rel_type] }) return jsonify({nodes: nodes, edges: edges}) if __name__ __main__: app.run(port5000, debugFalse)逻辑说明Cypher 里用了OPTIONAL MATCH而不是MATCH原因是有些配角人物可能没有出度关系OPTIONAL MATCH保证这些人也能出现在 nodes 里LIMIT 500是对演示环境的保护水浒人物关系全量可能上千条边一次性返回前端会白屏。 参数说明symbolSize用座次 rank 映射节点直径这是力导向图信息层级的关键——做题好的图第一眼就能看出宋江、卢俊义是大节点debugFalse建议保持Flask debug 模式在答辩现场经常因为自动重启导致端口占用这种意外不值得冒。3.3 前端渲染ECharts graph 类型的关键参数设置后端把数据拼好前端只需要一小段配置就能出图核心在force和edgeSymbol两个参数上!DOCTYPE html html head meta charsetutf-8 script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script /head body div idgraph stylewidth: 100%; height: 80vh;/div script fetch(/api/graph) .then(res res.json()) .then(data { const chart echarts.init(document.getElementById(graph)); const option { tooltip: { formatter: (params) params.dataType node ? 姓名${params.data.name}br/绰号${params.data.alias || 无}br/座次${params.data.rank || 无} : 关系${params.data.relation} }, series: [{ type: graph, layout: force, roam: true, draggable: true, data: data.nodes, edges: data.edges, force: { repulsion: 300, // 节点间斥力值越大图越疏散 edgeLength: 100 // 边长度值越大边越长 }, edgeLabel: { show: true, formatter: (params) params.data.relation || }, label: { show: true, position: right } }] }; chart.setOption(option); }); /script /body /html关键参数force.repulsion建议 200 到 400 之间太小整张图挤成一团看不出层次太大人物会被甩出屏幕edgeLabel.show在关系边上显示“结拜/师徒/仇敌”等字这是答辩时最抢眼的效果也是直观证明“关系已入图”的演示点。roam和draggable打开让评委可以拖拽人物节点交互感比静态图好很多。这里有个前端坑需要注意ECharts 5 里symbolSize如果在data中定义必须保证它是个数字否则会在控制台报错并导致传图失败。我在 3.2 的代码里用10 rank // 10如果 rank 是字符串比如 CSV 里没转 int这里就会翻车。所有从后端进到前端的数据show 之前加一层console.log检查类型这是做可视化项目的基本习惯。4. 问答系统用规则模板把“武松的结拜兄弟有谁”变成 Cypher4.1 为什么问答用规则模板就够了不用硬上 LLM很多拿到这套源码的人第一反应是想接大模型进去但在答辩场景下规则模板有不可替代的优势可解释、可复现、不依赖网络。评委问“这个答案怎么来的”你可以现场演示问题分类和模板映射过程而接 ChatGPT 接口的方案评委一句“断网还能答吗”就露怯了。2026 年热词里 dify neo4j 这类把知识库接进 LLM 的方案确实火但那是生产级架构放在课程设计里属于给自己挖坑。规则模板的适用边界要认清它能覆盖高频问题类型比如“X 和 Y 什么关系”“X 的结拜兄弟有哪些”“谁和谁有仇”但兜不住“为什么武松和宋江关系好”这类需要语义推理的问题。做系统设计时把支持的问题类型写清楚反倒在答辩时是加分项——说明你清楚自己的方案边界。4.2 实体识别用一个名字词典做匹配问答系统第一步是把用户问题里的人物名抠出来。没有用分词组件因为水浒人名是有限集直接用词典匹配又快又稳import re PERSON_DICT [宋江, 卢俊义, 吴用, 武松, 林冲, 鲁智深, 李逵, 高俅, 西门庆, 潘金莲, 孙二娘, 扈三娘, 阮小二, 杨志, 晁盖, 花荣, 戴宗, 燕青, 李师师, 王英] RELATION_DICT [结拜, 师徒, 仇敌, 亲属, 上下级, 同乡, 对抗] def extract_persons(question: str) - list: 从问题里找出所有出现的人物名 found [] for name in PERSON_DICT: if name in question: found.append(name) return found # 按问题长度排序更保险但简单场景顺序无关 def extract_relation(question: str) - str: 匹配关系类型关键词 for rel in RELATION_DICT: if rel in question: return rel return None逻辑说明extract_persons是纯字符串包含匹配复杂度 O(人物数 × 问题长度)水浒这个量级完全够用。注意一个细节如果问题里出现“宋江”和“宋公明”词典里两个都要有或者做归一化否则查询会漏人。 参数说明RELATION_DICT的顺序无所谓因为关系类型两两互斥但人物提取顺序有讲究——如果词典里有“宋江”又有“宋江老婆”要从长词向短词匹配否则长词永远匹配不上。实际源码里最好加一行found.sort(keylen, reverseTrue)做保护。4.3 三类最常见问题的模板与 Cypher 生成把问题归成三类分别对应三种 Cypher 模板。这是问答系统的核心也是源码里最有含金量的部分def generate_cypher(question: str, persons: list, relation: str): if not persons: return None, 未识别到人物请尝试使用全名提问 if len(persons) 2: # 类型AX 和 Y 是什么关系 return ( fMATCH (a:Person {{name:{persons[0]}}})-[r]-(b:Person {{name:{persons[1]}}}) fRETURN type(r) AS rel, r.type AS detail, relation_query ) if len(persons) 1 and relation: # 类型BX 的结拜兄弟有哪些 target b return ( fMATCH (a:Person {{name:{persons[0]}}})-[r]-(b:Person) fWHERE r.type {relation} fRETURN b.name AS name, b.alias AS alias, one_hop_query ) if len(persons) 1 and 认识 in question or 认识 in question: # 类型CX 认识哪些人返回一跳邻居 return ( fMATCH (a:Person {{name:{persons[0]}}})-[r]-(b:Person) fRETURN DISTINCT b.name AS name, b.alias AS alias, neighbor_query ) return None, 无法识别的问题类型逻辑说明类型 A 用无方向匹配-[r]-查双向关系类型 B 用有向匹配-[r]-但注意问题里如果问“谁和 X 结拜”方向是反的实际使用建议把有向匹配改成无向再在WHERE里过滤r.type能少踩一半坑。 参数说明这个函数的返回结构是 (cypher, 问题类型)问题类型可以在后续逻辑里拼提示语。模板拼 Cypher 用 f-string 直观但存在注入风险——人物名如果混入特殊字符会破坏查询结构。对于标题这个项目人物是白名单词典匹配结果风险可控但代码里仍建议对提取到的名字做一层re.match(r^[\u4e00-\u9fa5]{2,4}$, name)校验只允许中文人名通过。问答入口的逻辑很简单Flask 接收POST {question: 武松的结拜兄弟有哪些}→ 提取人物和关系 → 生成 Cypher → py2neo 执行 → 格式化返回 JSON 数组。整个链路在答辩演示时只需要一个 curl 命令就能走通效果比 UI 上点按钮更让评委信服。5. 避坑指南从装环境到答辩现场最常翻车的五个环节5.1 Neo4j 版本和 py2neo 不兼容到嘴的报错最难受现象照源码里的from py2neo import Graph跑起来提示AttributeError: Graph object has no attribute run或者连接时报ValueError: The Neo4j server does not support this driver version。原因py2neo 4.x 和 5.x 的 API 差异很大Neo4j 服务端版本也从 4.x 升到了 5.x2026 年新装基本是 5.x。py2neo 老版本驱动用的graph.run()在 5.x 里还能用但graph.data()和graph.cypher.execute()这类老接口已经被移除或改签名。源码里如果写的是老用法在 Neo4j 5.x 上直接崩。解决优先看源码里 requirements.txt 的 py2neo 版本号。如果是 2021.x 的写法安装py2neo2021.2.4配 Neo4j 4.4 最省事如果坚持用 Neo4j 5.x Desktop就把源码里的查询调用统一改成graph.run(cql).data()新风格。课件和博客里写的from py2neo import Node, Relationship, Graph是新 API遇到老接口先 diff 一下别对着报错硬猜。5.2 LOAD CSV 中文乱码csv 文件第一行有看不见的字符现象节点导进去后name 属性显示成\ufeff宋江或者 Browser 里中文全部变成 ?查询永远匹配不上。原因Windows 下用 Excel 编辑 CSV 默认保存为 ANSIGBK或带 BOM 的 UTF-8Neo4j LOAD CSV 要求 UTF-8 无 BOM。\ufeff是 BOM 头会粘在第一个字段名和值前面导致row.name取出来带着不可见字符。解决写 Python 代码统一转码再入库别在编辑软件里折腾import pandas as pd for fname in [nodes.csv, relations.csv]: df pd.read_csv(fname, encodinggbk, errorsignore) df.to_csv(fname, indexFalse, encodingutf-8-sig)说明errorsignore处理 ANSI 和 GBK 混合内容时的解码失败转成utf-8-sig是为了让 Excel 二次编辑时中文不乱码但 Neo4j 能正常识别它。导入后第一时间用MATCH (p:Person) RETURN p.name LIMIT 10看原始值确认没有\ufeff这一步 30 秒能省后面两小时。5.3 同名人物被 Neo4j 合并李逵和李鬼不是一个人现象查询“谁和宋江有仇”出来一堆莫名其妙的人名检查数据发现某些同名角色被合并了。原因建模时把 name 当唯一标识但原著里有重名或近似名如“李逵”和“李鬼”不同人但无关紧要的路人也被录入有的项目把绰号也当人物名导致节点合并错乱。解决给 Person 加一个内部 id如pid所有关系用 pid 关联展示层再用 name。或者严格清洗人物名单source 和 target 必须都在白名单里这条在 2.2 的代码里已经做了。实际上水浒题材最稳的做法是一百单八将加十几个关键配角大约 120 个节点封顶人工维护一份白名单比什么都靠谱。5.4 Cypher 查询 500匹配到多路径的报错看不懂现象执行“X 和 Y 什么关系”时Neo4j 报错说返回多行导致 map 投影失败。原因X 和 Y 之间有多条关系路径既有师徒又有结拜前端或后端代码用单行结果处理返回数组超过一行就异常。解决两种情况分开处理——数据上查询语句加LIMIT 20并返回数组不要假设唯一业务上关系类型有两三种其实是正常情况前端 tooltip 做成列表展示答非所问变成信息丰富。这道题也是答辩高频追问“你怎么处理多重关系路径”提前把这段代码写对能直接回答。5.5 答辩时评委问“这系统的创新点在哪”“源码哪里来的”的应对现象答辩现场最致命的问题不是技术而是“这个项目你自己做了多少”“核心代码你说一下”。原因标题是高分项目买源码的人多评委见过太多次专挑实现细节追问。解决建议原文复现之前把这三段背熟——第一段是数据建模为什么选六种关系类型而不是自由文本第二段是问答模板生成 Cypher 的 if-else 分支逻辑第三段是 ECharts 力导向图为什么用repulsion300这个参数。哪怕源码是买的把这三段讲成自己调试的心得评委基本不会再追问。如果被问“有没有考虑复杂关系推理”老老实实答范围外再补一句“后续可以用 Neo4j 的 k 跳查询扩展”就是很好的收尾。说实话每年都有大量同学在这类项目上翻车大多不是代码跑不起来而是拿到新环境后 Windows 路径、Python 版本、Neo4j 认证方式一变就手足无措。先在 Desktop 里把数据库启动并跑通 Browser 查询再谈后端这是这个项目性价比最高的排障顺序。6. 从能跑到高分答辩 PPT 的讲法与三个可加分的小特性先说 PPT 怎么讲。高分项目的答辩 PPT 不建议按“背景、技术、实现、总结”四段论平铺而是按这个顺序讲选题背景一句话带过直接展示最终效果图——全梁山人物力导向图撒在屏幕上然后顺着这张图讲数据建模讲关系类型为什么只有 6 种而非 20 种讲问答演示跳转。评委注意力最集中的前 3 分钟抓住他比后面 10 分钟讲技术细节都有效。源码包里的答辩 PPT 如果超过 15 页删到 10 页内演示效果好过资料全面。再加三个可加分的小特性都是基于现有代码做小改动k 跳扩展查询新增一个depth参数MATCH (a:Person {name:宋江})-[:关系*1..3]-(b) RETURN DISTINCT b.name能回答“宋江的社交圈有多大”这类层次问题比远近邻查询高一个等级代码只需要二十行。中心人物 Top N用度中心性计算一条 Cypher 搞定MATCH (p:Person)-[r]-() RETURN p.name AS name, count(r) AS degree ORDER BY degree DESC LIMIT 10把结果做成柱状图挂在页面上验证“宋江是全书关系枢纽”这个常识答辩时没人敢说你的系统没思考。关系路径 PI 展示支持输入两人名返回最短路径并高亮。ECharts 侧把option.series[0].data里路径上的节点itemStyle改成亮色即可后端就是MATCH path shortestPath((a)-[*..5]-(b)) RETURN path。我自己做这类图谱项目时的血泪经验是不要贪多。把节点颜色、关系标签、拖拽交互、问答演示这四件事做对了项目完成度已经超过大多数课程设计。那些试图装嘴简化、图注、用户系统的十有八九在主功能上顾此失彼。高频下载的免费源码也提醒你源码能找到很容易但能在新版本 Neo4j、新版本 Python 下跑通才是这个项目真正的分水岭。最后一句答辩前把 Neo4j Desktop 的服务手动重启一次把graph.run连通性测试跑一遍你就能省去现场全部尴尬——这些坑我都踩过希望帮到你。本文还有配套的精品资源点击获取