资讯详情 红楼梦知识图谱毕设系统:Neo4j+D3.js+规则问答闭环实现
📅 2026/10/10 21:45:08
简介这是一套面向计算机专业本科生的毕业设计实战资源聚焦知识图谱技术在古典文学分析中的落地应用为正在开展毕设、课程设计或期末大作业的学生提供可直接运行的完整解决方案。资源包含247个文件以8个核心Python脚本构建知识抽取与问答逻辑8个JS和11个CSS文件支撑前端可视化交互184张JPG图片用于人物关系图谱渲染与界面展示另有HTML页面、JSON数据及Markdown说明文档整体压缩包仅5.71MB轻量易部署。已有134人下载学习项目经导师指导并获99分高分评价代码结构清晰、注释完整配套文档涵盖环境配置、数据预处理、Neo4j图数据库搭建、Flask后端接口及前端可视化实现全流程特别适合零基础学生快速上手知识图谱构建与问答系统开发。1. 这不是又一个“红楼梦词云图”它是一套能跑通、能提问、能查关系的毕业设计闭环系统你肯定见过那种毕设演示视频——点开网页飘着几个红色圆点连几条线标题写着“基于知识图谱的红楼梦人物关系可视化”然后导师点头答辩通过。但真让你本地跑起来pip install 一堆报错neo4j 配置卡在 78%前端页面空白问答接口返回 500……最后只能截图静态图交差。这套源码不是那样。它是我去年帮三个学弟复现时从原始提交记录里扒出来的完整闭环数据清洗 → 图谱构建Neo4j→ 前端力导向图渲染D3.js Nifty UI→ 自然语言问句解析基于规则关键词匹配→ 答案生成与高亮回溯。99 分不是水来的——评审老师当场用手机拍下“贾宝玉和林黛玉共同出现的章回”这个问题系统秒回第3、23、27、34回并在图上自动高亮两人及中间路径节点。适合正在赶毕设 deadline 的大四学生不依赖 GPU、不强制 Python 3.10、所有依赖版本写死在 requirements.txt 里也适合想练手知识图谱落地的同学它没用 LLM 做问答避免 token 限流/部署成本而是用可解释、可调试的规则引擎每一步输入输出都留痕。你不需要懂图神经网络但得会改 config.py 里的数据库密码、会把 data/raw 目录下的 .txt 文本按格式整理好——这恰恰是工业级知识图谱项目最常卡住新人的第一关。2. 从《红楼梦》文本到 Neo4j 图谱三步清洗 两层映射 一个验证脚本2.1 文本结构化为什么不用现成的“红楼梦分章本”而要自己切章回项目正文里列了一堆 CSS 文件bootstrap.min.css、nifty.min.css 等但真正决定图谱质量的是data/raw/目录下那 120 个.txt文件——它们不是网上随便下载的带广告的盗版文本而是人工校对过的程乙本分回文本每文件命名严格为chapter_001.txt到chapter_120.txt。关键在于这些文本已预处理掉所有括号注释、诗词分行、标点乱码。如果你直接扔进python build_graph.py第一步就会崩在re.split(r第[零一二三四五六七八九十百千]回, text)上——因为正则匹配失败导致后续人物抽取全乱。我建议先运行scripts/validate_chapter_format.py# scripts/validate_chapter_format.py import os import re def check_chapter_files(): raw_dir data/raw pattern r^chapter_\d{3}\.txt$ for f in os.listdir(raw_dir): if not re.match(pattern, f): print(f❌ 命名错误{f}应为 chapter_XXX.txt 格式) continue with open(os.path.join(raw_dir, f), r, encodingutf-8) as fp: content fp.read(200) # 只读前200字 if not re.search(r第[零一二三四五六七八九十百千]回, content): print(f❌ 内容缺失{f} 开头未含‘第X回’字样) print(✅ 所有章节文件格式校验通过) if __name__ __main__: check_chapter_files()提示这个脚本必须在data/raw/目录存在且非空时运行。它不修复问题只报错——这是刻意设计的。很多同学跳过这步直接跑构建脚本结果图谱里只有 3 个节点贾母、王熙凤、刘姥姥因为其他文件因编码或命名问题被 skip 掉了。2.2 人物实体识别不用 BERT用“规则词典”稳准狠项目没用任何预训练模型核心是utils/entity_extractor.py里的extract_persons_from_chapter()函数。它依赖两个硬编码资源data/dict/person_names.txt共 127 个核心人物含别名如“贾宝玉|神瑛侍者|宝二爷|怡红公子”占一行data/dict/relation_keywords.txt关系触发词表如“之子”“之女”“妻”“妾”“兄”“妹”“表姐”“结拜兄弟”等 42 个词。抽取逻辑分三步粗筛用person_names.txt全局匹配所有出现人名支持别名用|分隔上下文过滤剔除“梦中见”“书中说”“诗曰”等引述场景正则r(?:梦中|书中|诗曰|赞曰|道“.*?”|叹曰)关系绑定扫描人名前后 15 字若含relation_keywords.txt中词则生成(主语, 关系, 宾语)三元组。例如“王夫人乃贾政之妻生有贾珠、元春、宝玉” → 提取三元组(王夫人, 妻, 贾政)、(王夫人, 母, 贾珠)、(王夫人, 母, 元春)、(王夫人, 母, 宝玉)。注意这里不推导间接关系如“贾政之妻”不自动推出“贾政与王夫人是夫妻”避免噪声爆炸——这是毕设可控性的关键取舍。2.3 Neo4j 图谱构建从 CSV 批量导入到索引优化所有三元组最终导出为data/output/nodes.csv和data/output/relationships.csv格式严格遵循 Neo4j 的LOAD CSV要求nodes.csvid:ID,name,:LABEL如p001,贾宝玉,Personrelationships.csv:START_ID,:END_ID,:TYPE如p001,p002,夫妻。导入命令写在scripts/load_to_neo4j.cypher里但不能直接 copy-paste 进 Neo4j Browser// scripts/load_to_neo4j.cypher USING PERIODIC COMMIT 1000 LOAD CSV WITH HEADERS FROM file:///nodes.csv AS row CREATE (:Person {id: row.id, name: row.name}); USING PERIODIC COMMIT 1000 LOAD CSV WITH HEADERS FROM file:///relationships.csv AS row MATCH (a:Person {id: row.:START_ID}) MATCH (b:Person {id: row.:END_ID}) CREATE (a)-[:RELATION {type: row.:TYPE}]-(b);参数说明PERIODIC COMMIT 1000是必须的——否则 120 回产生的 8000 关系会内存溢出file:///路径要求 Neo4j 的import目录下有对应 CSVWindows 用户注意斜杠方向MATCH而非MERGE是为了防止重复创建节点我们已确保 nodes.csv 无重复 id。导入后立即执行索引命令否则前端查询超时CREATE INDEX ON :Person(name); CREATE INDEX ON :Person(id);2.4 验证图谱质量用 Cypher 查 3 个必检问题别急着启动 Web 服务。先在 Neo4j Browser 里跑这三条语句确认图谱没崩// Q1总节点数是否合理应 120200 MATCH (n:Person) RETURN count(n) // Q2贾宝玉是否至少有 15 个直接关系他和 15 人有显式互动 MATCH (p:Person {name:贾宝玉})-[r]-() RETURN count(r) // Q3是否存在“林黛玉-父-林如海”这条边核心家族链必须存在 MATCH (a:Person {name:林黛玉})-[:RELATION {type:父}]-(b:Person {name:林如海}) RETURN a,b如果 Q1 返回 0检查data/output/nodes.csv是否为空Q2 10说明文本清洗漏掉了大量对话Q3 不返回结果大概率是relation_keywords.txt里漏了“父”字常见坑用“父亲”但原文写“父”。3. 前端可视化Nifty UI D3.js 力导向图的 4 个定制点3.1 为什么选 Nifty 而不是 BootstrapUI 组件与图谱交互的强耦合项目正文列出的 CSS 文件nifty.min.css,nifty-demo.min.css,datatables.bootstrap.css不是凑数的。Nifty 是一个轻量级 Admin UI 框架它的nifty-demo.min.css里预置了折叠侧边栏、响应式卡片、带搜索的 DataTables 表格——这些恰好对应本系统的三大视图左侧人物列表可搜索、可按关系类型筛选中央D3 力导向图节点拖拽、点击高亮、关系路径展开右侧问答输入框 答案展示区带 Markdown 渲染。Bootstrap 缺少原生的“可折叠导航栏联动内容区”能力而 Nifty 的>{ nodes: [{id:p001,name:贾宝玉,group:1}], links: [{source:p001,target:p002,value:夫妻}] }D3 渲染逻辑在static/js/graph.js核心是d3.forceSimulation()的 5 个力参数参数值作用修改建议force(link, d3.forceLink().id(d d.id).distance(120))distance120控制边长默认 120px人物多时调大至 150防重叠force(charge, d3.forceManyBody().strength(-300))strength-300节点斥力负值越大越分散若图太散改为 -150force(center, d3.forceCenter(width/2, height/2))固定中心锚定图居中必须保留否则拖拽后失焦force(collide, d3.forceCollide().radius(30))radius30节点碰撞半径节点文字多时调大至 40force(x, d3.forceX().x(width/2).strength(0.05))strength0.05X轴向心力防止图横向漂移勿删血泪经验strength参数调错是前端最玄学的翻车点。曾有学弟把forceManyBody().strength(-300)改成-1000结果所有节点炸飞到画布右下角调试半小时才发现是力太猛——D3 的物理引擎没有“最大速度限制”负值过大直接超光速。3.3 节点点击交互如何实现“点击贾宝玉 → 高亮所有关联人物 显示关系路径”graph.js中node.on(click, function(event, d) {...})的核心逻辑是用d3.select(this).classed(active, true)高亮当前节点用links.filter(l l.source.id d.id || l.target.id d.id)找出所有直连边对每条边再递归找l.source和l.target的直连边深度1构成二级关系网络将所有相关节点/边临时添加highlightclass并设置 CSSstroke-width: 3px; opacity: 0.9。关键代码段// static/js/graph.js function highlightNeighbors(nodeId) { // 清除之前高亮 d3.selectAll(.node, .link).classed(highlight, false); // 获取当前节点的所有直连边 const directLinks links.filter(l l.source.id nodeId || l.target.id nodeId); // 获取直连节点 ID 集合 const neighborIds new Set(); directLinks.forEach(l { neighborIds.add(l.source.id); neighborIds.add(l.target.id); }); // 高亮节点和边 d3.selectAll(.node).filter(d neighborIds.has(d.id)).classed(highlight, true); d3.selectAll(.link).filter(l neighborIds.has(l.source.id) neighborIds.has(l.target.id) ).classed(highlight, true); }注意这个函数只做两级高亮本人直连不做全图路径搜索——毕设性能边界在此。若需三级需改用 Neo4j 的shortestPath查询前端只传startId和maxDepth3。3.4 响应式适配当屏幕宽度 768px 时如何保证力导向图不被侧边栏挤压Nifty 的nifty.min.css默认在media (max-width: 767px)下隐藏侧边栏但graph.js的 SVG 宽度仍按document.getElementById(graph).clientWidth计算导致图宽0。解决方案在static/js/main.js的resizeGraph()函数里function resizeGraph() { const graphContainer document.getElementById(graph); let width graphContainer.clientWidth; let height window.innerHeight * 0.7; // 移动端高度固定为视口70% // 关键移动端强制宽度为 window.innerWidth if (window.innerWidth 768) { width window.innerWidth; // 同时隐藏右侧问答区腾出空间 document.getElementById(qa-panel).style.display none; } svg.attr(width, width).attr(height, height); simulation.force(center, d3.forceCenter(width / 2, height / 2)); }避坑这个resizeGraph()必须绑定到window.addEventListener(resize, resizeGraph)且在DOMContentLoaded时手动调用一次——否则首次加载时图还是窄的。4. 问答系统规则引擎如何扛住“贾宝玉和薛宝钗是什么关系”这类复杂问句4.1 问句解析三阶段分词 → 实体识别 → 关系意图分类问答入口在app.py的/api/qa接收 POST 请求JSON body 如{question: 贾宝玉和林黛玉的感情如何}。解析流程分词用jieba.cut()切词但禁用 TF-IDF 或停用词表——因为“如何”“什么”“是不是”是关键意图词删掉就废了实体识别遍历分词结果匹配data/dict/person_names.txt中的姓名支持别名存入entities [贾宝玉, 林黛玉]意图分类用硬规则匹配问句关键词什么关系|是谁的.*?|.*?的.*?→ 关系查询intent relation出现|在哪|第几回|章回→ 章回定位intent chapter谁|有哪些|多少个→ 聚合查询intent aggregate。为什么不用 spaCy 或 LTP因为毕设环境不允许装 C 依赖。jieba纯 Pythonrequirements.txt里只有一行jieba0.42.1Windows/macOS/Linux 全兼容。4.2 关系查询Cypher 查询模板与动态拼接当intent relation且len(entities) 2时生成 Cypher 查询// 模板查两人间所有直接关系 MATCH (a:Person {name:$name1})-[r]-(b:Person {name:$name2}) RETURN r.type AS relation, a.name AS source, b.name AS target但真实情况更复杂若entities [贾宝玉, 王夫人]需查MARRIAGE夫妻、PARENT_CHILD母子两种关系若entities [贾宝玉, 秦钟]原文无直接关系但系统会 fallback 到“共同出现章回”用MATCH (a)-[]-(c)-[]-(b)查共同邻居。app.py中get_relations_between_two()函数处理此逻辑def get_relations_between_two(name1, name2): # 步骤1查直接关系 query_direct MATCH (a:Person {name:$name1})-[r]-(b:Person {name:$name2}) RETURN r.type AS relation, a.name AS source, b.name AS target result run_cypher(query_direct, {name1: name1, name2: name2}) if result: return {type: direct, data: result} # 步骤2查共同章回fallback query_common MATCH (a:Person {name:$name1})-[]-(c:Chapter)-[]-(b:Person {name:$name2}) RETURN c.id AS chapter_id, c.title AS chapter_title LIMIT 5 common_chapters run_cypher(query_common, {name1: name1, name2: name2}) return {type: common_chapter, data: common_chapters}参数说明run_cypher()是封装好的 Neo4j Driver 调用自动处理连接池和异常。LIMIT 5是防慢查询——毕设不追求全量只给用户前 5 个线索。4.3 答案生成Markdown 渲染 前端高亮联动答案返回 JSON 结构{ answer: 贾宝玉与林黛玉是表兄妹且互为知己。他们在第3、23、27、34回共同出现。, highlight_nodes: [贾宝玉, 林黛玉], highlight_links: [表兄妹, 知己] }前端static/js/qa.js接收后将answer用marked.parse()渲染为 HTML支持**加粗**、*斜体*调用highlightNeighbors()高亮节点在图上用d3.selectAll(.link text)找到对应关系标签设opacity: 0.9。关键点highlight_links数组存的是关系类型字符串如表兄妹不是 ID——因为前端图谱里边的text标签内容就是d.type直接匹配即可。4.4 常见问题排查问答系统 4 大翻车现场现象1问“贾宝玉喜欢谁”返回空→原因问句含“喜欢”但relation_keywords.txt里没录入“喜欢”“爱慕”“倾心”等情感词意图分类落到intentunknown直接返回默认提示。→解决编辑data/dict/relation_keywords.txt追加喜欢|爱慕|倾心|思慕|恋慕重启 Flask 服务。现象2问“王熙凤的丈夫是谁”返回“贾琏”但图谱里MARRIAGE关系是(王熙凤)-[夫妻]-(贾琏)而查询用的是-[r]-(b)无向边导致也匹配到(王熙凤)-[姑侄]-(贾母)等干扰边。→原因Cypher 查询用了无向边-[]-但婚姻关系是有向的-表示“妻子-丈夫”。→解决修改get_relations_between_two()中的查询用有向边-[r]-(b)并补充反向查询-[r]-(a)合并结果。现象3中文问句返回乱码如“贾宝玉”显示为“贾å®ç”→原因Flask 默认返回Content-Type: text/html; charsetutf-8但 AJAX 请求未声明contentType: application/json; charsetutf-8浏览器用 ISO-8859-1 解析。→解决在static/js/qa.js的fetch()调用中明确设置请求头fetch(/api/qa, { method: POST, headers: { Content-Type: application/json; charsetutf-8, }, body: JSON.stringify({question: q}) })现象4问答响应时间 5sChrome 控制台报net::ERR_CONNECTION_TIMED_OUT→原因Neo4j 未启用dbms.connector.http.enabledtrue或防火墙拦截了 7474 端口。→解决检查conf/neo4j.conf确认dbms.connectors.default_listen_address0.0.0.0且dbms.connector.http.listen_address:7474Windows 用户需在防火墙入站规则中放行 TCP 7474。5. 部署与调试从本地开发到答辩演示的 3 个保命技巧5.1 一键启动用start.batWindows或start.shmacOS/Linux绕过所有环境变量项目根目录下有start.batWindows和start.shmacOS/Linux内容极简:: start.bat echo off cd /d %~dp0 call venv\Scripts\activate.bat python app.py pause#!/bin/bash # start.sh cd $(dirname $0) source venv/bin/activate python app.py为什么不用flask run因为app.py里写了app.run(host0.0.0.0, port5000, debugFalse)——debugTrue在生产环境会暴露代码答辩时被老师点开调试器就完了。start.bat/sh强制关闭 debug且指定 host0.0.0.0允许同网段手机扫码访问演示时投屏用。5.2 数据热更新不重启服务实时刷新图谱与问答答辩前临时发现某个人物关系错了比如把“薛蟠之妻”写成“夏金桂”实际是“夏金桂”改完data/output/relationships.csv后不要重启 Flask直接访问http://127.0.0.1:5000/api/reload-graphGET 请求后端会重新读取nodes.csv和relationships.csv执行MATCH (n) DETACH DELETE n清空图谱重新运行LOAD CSV导入同 2.3 节逻辑返回{status: success, nodes: 156, relations: 2345}。注意此接口仅在DEBUGTrue时启用app.py第 22 行if app.debug:正式答辩前务必注释掉——否则评委扫二维码就能删你图谱。5.3 答辩演示 checklist5 分钟内完成的 7 个动作别信“我演示时肯定没问题”。按顺序执行每个动作限时 40 秒步骤操作预期结果失败急救1双击start.bat等待 CMD 窗口显示* Running on http://127.0.0.1:5000Flask 启动成功若报ModuleNotFoundError立刻pip install -r requirements.txt2浏览器打开http://127.0.0.1:5000看左上角是否显示“红楼梦知识图谱系统”页面加载左侧人物列表有数据若空白按 F12 看 Console 是否报Failed to load resource: net::ERR_CONNECTION_REFUSED→ 检查 Flask 是否真启动3在左侧人物列表搜“贾宝玉”点击看中央图是否高亮其节点及连线节点变红边加粗若无反应检查static/js/graph.js是否被浏览器缓存CtrlF5 强刷4在右上角问答框输入“贾宝玉和薛宝钗是什么关系”回车返回“夫妻”且图上高亮两人及“夫妻”边若返回空检查data/dict/person_names.txt是否含“薛宝钗”5输入“林黛玉的父亲是谁”回车返回“林如海”并高亮(林黛玉)-[父]-(林如海)若返回“未知”检查relation_keywords.txt是否含“父”6点击图中“王熙凤”节点看右侧是否显示“管家婆”“弄权铁槛寺”等标签标签区有文字若空白检查data/output/nodes.csv中p003行是否有label字段应为Person7手机扫码http://[本机IP]:5000如http://192.168.1.100:5000验证响应式布局手机端图谱完整侧边栏自动隐藏若打不开检查 Windows 防火墙是否放行 5000 端口5.4 最后一道后悔药答辩前 1 小时的终极备份方案我带过的 12 个毕设学生8 个在答辩前 2 小时遇到“图谱突然不显示”的玄学问题。根源往往是Neo4j 数据库文件损坏data/databases/graph.db目录被误删venv环境里某个包版本冲突如neo4j-driver升级到 5.x但代码只兼容 4.4.x。保命操作在项目根目录新建backup/文件夹复制整个data/databases/graph.db目录进去运行pip freeze backup/requirements_frozen.txt打包backup/为backup_final.zip发邮箱、存 U 盘、传网盘三份。答辩当天若翻车直接解压backup/覆盖原目录pip install -r backup/requirements_frozen.txt双击start.bat——5 分钟满血复活。这招救过我的命也救过三个学弟的学位证。从那以后我每次打包毕设交付物都强制走一遍backup/流程不是怕出错是怕出错时没时间重来。希望帮到你。本文还有配套的精品资源点击获取
相关阅读
Happier通信协议详解:WebSocket与Socket.IO如何驱动实时同步
【免费下载链接】happier Web, Desktop & Mobile client and orchestrator for Codex, Claude Code, OpenCode, Pi, Cursor, Grok, Antigravity, Kimi, Augment Code, Qwen, fully end-to-end encrypted 项目地址: https://gitcode.com/gh_mirrors/hap/happier …
2026/10/10 22:55:17 阅读全文 →