尧图精选

基于Neo4j的红楼梦知识图谱构建与查询实战

🕒 发布时间:2026/10/2 3:50:29 📁 来源:尧图网络
简介这份资源围绕《红楼梦》知识图谱的构建与可视化展示面向知识图谱入门学习者、自然语言处理方向的学生以及需要Neo4j实战案例的开发者。它解决的是从文本到结构化知识网络的落地问题涵盖实体识别、关系抽取与图数据库存储的完整链路适合作为课程设计或自学练手项目。压缩包共7个文件约1.62MB包含csv三元组数据、Python脚本、Neo4j数据库备份文件、图谱展示图片及说明文档分别对应数据源、构建代码、数据库还原与效果预览等环节。目前已有160人学习下载。读者可借助三元组数据与脚本理解知识图谱的构建流程通过数据库备份快速还原红楼梦人物关系网络并参考图谱截图与说明文档完成展示与验证从而掌握Neo4j图数据库的基本操作与知识图谱应用思路。1. 红楼梦知识图谱展示与Neo4j数据库从文本到可查询关系网络把《红楼梦》里的人物关系、家族谱系、诗词归属、事件因果整理成一张能点、能查、能扩展的关系网络这件事听起来像数字人文的论文题目但落到工程上它其实是一个标准的图数据库建模与查询问题。我最初接触这个方向是因为一个做语文教育产品的朋友提了个需求学生读《红楼梦》时最头疼的是人物关系——贾宝玉和林黛玉是什么关系薛宝钗和王熙凤有没有直接交集贾母到底有几个孙辈这些问题用传统关系型数据库做需要大量 JOIN查询一深就慢得离谱。而 Neo4j 这类图数据库天生就是为「关系」设计的节点存实体边存关系查「某人的所有亲戚」就是一次图遍历直观且高效。这个方案适合谁如果你正在做知识图谱构建、课程设计、或者想找一个有真实数据、有展示价值、又能练手 Neo4j 的项目红楼梦知识图谱是一个非常好的切入点。它数据量适中几百个人物、上千条关系语义清晰而且有大量公开文本可以抽取。更重要的是它能让你完整走一遍「本体建模 → 数据抽取 → 图库导入 → 查询展示」的链路这套链路放到工业场景下的知识图谱设计里骨架是一样的。下面我就按实际做过的顺序把每一步拆开讲。2. 本体建模与数据准备先想清楚「节点」和「边」到底存什么2.1 红楼梦知识图谱的本体设计五类节点与八种关系动手写代码之前必须先定本体。本体建模不是学术游戏它直接决定你后面查询能不能写、展示能不能看。我一般会先问三个问题谁做了什么和谁有关对应到红楼梦我最终定下来的节点类型有五种节点标签含义示例关键属性Person人物贾宝玉、林黛玉name, gender, rankFamily家族/府邸贾府、荣国府、宁国府name, typeEvent事件元妃省亲、黛玉葬花name, chapterPoem诗词葬花吟、芙蓉女儿诔title, contentPlace地点大观园、潇湘馆name, type关系类型我定了八种FATHER_OF父子、MOTHER_OF母子、SPOUSE_OF夫妻、SIBLING_OF兄弟姐妹、SERVANT_OF主仆、FRIEND_OF朋友、LOVES爱慕、LIVES_IN居住。这里有个血泪经验关系方向一定要统一。比如FATHER_OF统一从父亲指向子女不要一会儿正一会儿反否则后面查「贾宝玉的父亲」和「贾政的子女」要写两套逻辑维护起来就是灾难。提示本体不是越细越好。我见过有人把「同父异母」和「同母异父」拆成两种关系结果数据录入时根本分不清查询也几乎用不到。先覆盖高频查询场景再考虑扩展。2.2 从文本到结构化数据人物关系抽取的三种落地方式数据从哪来常见做法有三种按投入从低到高排第一种手工整理核心人物。红楼梦前八十回核心人物约 120 人主要关系约 400 条。我一般会先用 Excel 建两张表persons.csv和relations.csv。persons.csv字段是name, gender, family, rankrelations.csv字段是from, relation, to, chapter。这种方式最笨但最准适合课程设计或原型验证。第二种半自动抽取。用 jieba 分词加正则匹配从原文里抓「某某是某某的父亲」这类句式。但红楼梦的语言是古白话句式变化多召回率大概只有 60%剩下 40% 还得人工补。我试过用 HanLP 做依存句法分析效果比正则好一些但依然需要人工校验。第三种用现成的开源人物关系数据集。网上有一些红楼梦人物关系 CSV但质量参差不齐字段定义也不统一。我一般会拿它做底稿然后对照原文逐条核对。注意不要直接拿来就用错误的关系比没有关系更误导人。下面是一个最小可用的数据准备脚本把 CSV 转成 Neo4j 能导入的格式import csv import json # 读取人物表生成节点导入文件 persons [] with open(persons.csv, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: persons.append({ name: row[name], gender: row[gender], family: row[family], rank: row[rank] }) # 读取关系表生成边导入文件 relations [] with open(relations.csv, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: relations.append({ from: row[from], relation: row[relation], to: row[to], chapter: int(row[chapter]) if row[chapter] else 0 }) # 输出为 JSON方便后续用 apoc 或 LOAD CSV 导入 with open(persons.json, w, encodingutf-8) as f: json.dump(persons, f, ensure_asciiFalse, indent2) with open(relations.json, w, encodingutf-8) as f: json.dump(relations, f, ensure_asciiFalse, indent2) print(f人物节点{len(persons)} 条关系边{len(relations)} 条)这段代码的逻辑很直接把两张 CSV 分别读成字典列表再输出 JSON。参数上唯一要注意的是chapter字段如果 CSV 里是空值我给了默认 0表示关系不绑定具体回目。实际导入时relation字段的值必须和你在 Neo4j 里定义的关系类型完全一致大小写敏感所以建议在 CSV 里就用大写加下划线比如FATHER_OF。注意CSV 的编码必须是 UTF-8不要用 Excel 默认的 GBK 保存否则 Neo4j 导入时中文会变乱码。我在这上面翻过车排查了半天才发现是编码问题。3. Neo4j 安装配置与图数据导入把 CSV 变成可查询的图3.1 Neo4j 社区版安装与内存参数调整Neo4j 有社区版和企业版做红楼梦知识图谱展示社区版完全够用。安装方式我推荐两种Windows 和 macOS 用 Neo4j DesktopLinux 用 tar.gz 包直接跑。Neo4j Desktop 的好处是自带浏览器界面不用额外配。但要注意Neo4j Desktop 默认给的内存比较保守导入几千条数据没问题但如果你后面想加全文索引或者跑复杂查询需要手动调内存。配置文件在conf/neo4j.conf关键参数有三个# 堆内存初始值和最大值建议设为物理内存的 25% 左右 dbms.memory.heap.initial_size1G dbms.memory.heap.max_size2G # 页面缓存用于缓存图数据和索引建议设为物理内存的 50% 左右 dbms.memory.pagecache.size2G # 允许从文件导入 CSV默认是关闭的 apoc.import.file.enabledtrue这里有个常见坑Neo4j 社区版没有使用配置文件内存的情况往往是因为你改错了文件或者启动时没有指定配置文件。Linux 下启动命令要写成./bin/neo4j console它会自动读conf/neo4j.conf。如果你用neo4j start没生效先检查neo4j.conf里有没有被注释掉的行#开头的都是不生效的。安装完成后浏览器打开http://localhost:7474默认用户名和密码都是neo4j首次登录会强制改密码。改完密码后先跑一句RETURN 1确认连接正常。3.2 用 LOAD CSV 和 Cypher 批量导入人物与关系数据导入有两种方式LOAD CSV和apoc.load.json。我一般用LOAD CSV因为它不依赖 APOC 插件社区版开箱即用。但LOAD CSV要求文件放在 Neo4j 的import目录下路径是相对路径。先导入人物节点// 导入人物节点使用 MERGE 避免重复创建 LOAD CSV WITH HEADERS FROM file:///persons.csv AS row MERGE (p:Person {name: row.name}) SET p.gender row.gender, p.family row.family, p.rank row.rank RETURN count(p) AS personCount;这段 Cypher 的逻辑是逐行读取 CSV用MERGE按name查找或创建 Person 节点然后设置属性。MERGE和CREATE的区别很关键——MERGE是「有则匹配无则创建」CREATE是无脑新建。导入数据一定要用MERGE否则跑两遍就多出一倍节点。RETURN count(p)用来确认导入了多少条正常应该和你 CSV 的行数一致。再导入关系边// 导入关系边先匹配两端节点再创建关系 LOAD CSV WITH HEADERS FROM file:///relations.csv AS row MATCH (from:Person {name: row.from}) MATCH (to:Person {name: row.to}) CALL apoc.merge.relationship(from, row.relation, {}, {}, to) YIELD rel RETURN count(rel) AS relationCount;这里用了apoc.merge.relationship因为关系类型是动态的不能直接写在 Cypher 里。如果你不想装 APOC也可以按关系类型拆成多条语句比如MERGE (from)-[:FATHER_OF]-(to)但那样要写八遍。APOC 的merge.relationship第一个参数是起始节点第二个是关系类型字符串第三个和第四个是关系的属性这里留空第五个是目标节点。导入完成后跑一句验证// 查看图规模 MATCH (n) RETURN count(n) AS nodes; MATCH ()-[r]-() RETURN count(r) AS relationships;如果节点数和关系数都对得上说明导入成功。如果关系数是 0大概率是MATCH没匹配到节点检查 CSV 里的名字和已导入的 Person 节点名字是否完全一致包括空格和标点。4. 查询与展示从「查一个人」到「查一条关系链」4.1 基础查询从一个节点出发查多条关系Neo4j 查询从一个节点出发如何查询多条关系这是最常用的操作。比如查贾宝玉的所有直接关系// 查贾宝玉的所有一度关系 MATCH (p:Person {name: 贾宝玉})-[r]-(other) RETURN type(r) AS relationType, other.name AS relatedPerson ORDER BY relationType;MATCH (p)-[r]-(other)里的-[r]-没有箭头表示双向匹配即不管关系方向如何只要和贾宝玉相连的都查出来。如果你只想查「贾宝玉指向别人」的关系写成-[r]-只想查「别人指向贾宝玉」的写成-[r]-。再进一步查两度关系也就是「贾宝玉的亲戚的亲戚」// 查贾宝玉的两度关系限制返回数量避免爆炸 MATCH (p:Person {name: 贾宝玉})-[r1]-(mid)-[r2]-(far) WHERE far.name 贾宝玉 RETURN DISTINCT mid.name AS middle, type(r1) AS rel1, far.name AS farPerson, type(r2) AS rel2 LIMIT 50;这里加了WHERE far.name 贾宝玉是为了排除回到自己的情况DISTINCT去重LIMIT 50防止结果太多。两度关系在红楼梦里已经能覆盖大部分「间接关系」查询比如「贾宝玉和林黛玉共同认识的人」。4.2 路径查询与可视化展示把关系网络画出来Neo4j 浏览器自带图可视化查询结果可以直接看。但如果你想做更定制化的展示比如在网页里嵌入关系图常见做法是用neo4j-driver加前端图库如 vis.js 或 ECharts 的 graph 系列。先看一个查最短路径的 Cypher// 查贾宝玉和林黛玉之间的最短关系路径 MATCH (a:Person {name: 贾宝玉}), (b:Person {name: 林黛玉}) MATCH path shortestPath((a)-[*..6]-(b)) RETURN path;shortestPath是 Neo4j 内置函数[*..6]表示最多走 6 跳。红楼梦人物关系网的平均路径长度很短一般 3 到 4 跳就能连上两个主要人物。这个查询在浏览器里会直接画出路径上的节点和边非常直观。如果要在前端展示用 JavaScript 驱动 Neo4j 的典型流程是// 前端通过 Neo4j driver 查询并渲染图 const neo4j require(neo4j-driver); const driver neo4j.driver(bolt://localhost:7687, neo4j.auth.basic(neo4j, 你的密码)); async function queryGraph() { const session driver.session(); try { const result await session.run( MATCH (p:Person {name: $name})-[r]-(other) RETURN p.name AS source, type(r) AS relation, other.name AS target LIMIT 30, { name: 贾宝玉 } ); const nodes new Map(); const edges []; result.records.forEach(record { const source record.get(source); const target record.get(target); const relation record.get(relation); nodes.set(source, { id: source, label: source }); nodes.set(target, { id: target, label: target }); edges.push({ from: source, to: target, label: relation }); }); // 这里把 nodes 和 edges 传给 vis.js 或 ECharts 渲染 console.log(节点数, nodes.size, 边数, edges.length); } finally { await session.close(); } }这段代码的逻辑是用参数化查询$name避免 Cypher 注入把结果里的 source、target、relation 提取出来组装成前端图库需要的节点和边格式。参数说明bolt://localhost:7687是 Neo4j 的 Bolt 协议地址neo4j.auth.basic里填用户名和密码。注意前端直连数据库在生产环境不安全正式项目应该加一层后端 API 做代理。提示Neo4j 浏览器里查询结果超过 300 个节点会提示「结果太大」这时候要么加LIMIT要么用apoc的虚拟图做聚合。展示用的查询我一般限制在 50 个节点以内视觉效果最好。5. 避坑与排查红楼梦知识图谱落地时最容易翻车的五件事5.1 人物同名与别名导致节点重复现象导入后发现「贾宝玉」有两个节点一个是「贾宝玉」一个是「宝玉」。原因CSV 里同一人物用了不同称呼MERGE按name匹配时认为是两个人。解决在数据准备阶段建一张别名映射表导入前统一替换成标准名。比如宝玉 - 贾宝玉、黛玉 - 林黛玉、宝钗 - 薛宝钗。我一般会在 Python 脚本里加一个alias_map字典读 CSV 时先做一次name alias_map.get(name, name)。5.2 关系方向不一致导致查询漏结果现象查「贾政的子女」查不到贾宝玉但查「贾宝玉的父亲」能查到贾政。原因FATHER_OF关系有的从父亲指向子女有的从子女指向父亲。解决定死方向规则所有FATHER_OF、MOTHER_OF统一从长辈指向晚辈SERVANT_OF统一从仆人指向主人。导入前用脚本校验一遍发现反向的自动翻转。5.3 LOAD CSV 导入时中文乱码现象CSV 在 Excel 里看是正常的导入 Neo4j 后中文变成问号或乱码。原因Excel 默认保存为 GBK 编码Neo4j 按 UTF-8 读取。解决用 VS Code 或 Notepad 把 CSV 转成 UTF-8 无 BOM 格式。如果已经导入了乱码数据先MATCH (n) DETACH DELETE n清空再重新导入。5.4 两度查询结果爆炸现象查「贾宝玉的两度关系」返回几千条浏览器卡死。原因红楼梦人物关系网密度高两度关系会指数级增长。解决加LIMIT加WHERE过滤掉不关心的关系类型或者用apoc.path.expandConfig限制遍历深度和关系类型。我一般展示用一度关系分析用两度但必须加LIMIT 100。5.5 Neo4j 服务启动后浏览器打不开现象命令行显示启动成功但http://localhost:7474无法访问。原因可能是端口被占用或者配置文件里dbms.connector.http.listen_address被改成了非默认地址。解决先netstat -ano | findstr 7474查端口占用如果被占改neo4j.conf里的端口号如果是地址问题确认配置是0.0.0.0:7474或localhost:7474。另外Neo4j 4.x 和 5.x 的配置项名称有变化5.x 里 HTTP 连接器配置改成了server.http.listen_address升级版本时要注意。6. 进阶技巧用 APOC 做关系推断与图谱质量校验红楼梦知识图谱做到后面你会发现有些关系不是直接写出来的而是可以推断的。比如「贾宝玉的父亲的妻子」就是「贾宝玉的母亲」这种推断用 Cypher 路径查询就能实现。但更复杂的推断比如「同辈且同家族的人自动建立 SIBLING_OF 关系」用 APOC 会更顺手。一个我常用的技巧是用apoc.periodic.iterate批量推断关系// 批量推断同家族且辈分相同的人建立同辈关系 CALL apoc.periodic.iterate( MATCH (a:Person), (b:Person) WHERE a.family b.family AND a.rank b.rank AND a.name b.name RETURN a, b, MERGE (a)-[:SAME_GENERATION]-(b), {batchSize: 100, parallel: false} );这段代码分两部分第一个字符串是「查什么」第二个字符串是「对每条结果做什么」。batchSize: 100表示每 100 条提交一次避免大事务撑爆内存。parallel: false是因为MERGE在并行时可能产生竞争稳妥起见关掉。跑完后可以用MATCH ()-[r:SAME_GENERATION]-() RETURN count(r)看推断出多少条。另一个实用技巧是图谱质量校验。我一般会跑三组查询第一组查孤立节点MATCH (n) WHERE NOT (n)--() RETURN n孤立节点说明数据漏了关系第二组查关系类型分布MATCH ()-[r]-() RETURN type(r), count(*) ORDER BY count(*) DESC看有没有异常多的关系类型第三组查属性缺失MATCH (p:Person) WHERE p.gender IS NULL RETURN p.name缺属性的节点要补全。最后说一个我自己的习惯每次改完数据先在一个测试库上跑一遍完整导入和查询确认没问题再动正式库。Neo4j 社区版不支持多数据库但你可以用不同的数据目录启动两个实例一个当测试一个当正式。这个习惯帮我省过好几次「后悔药」——有一次我误删了所有FATHER_OF关系因为测试库先跑过正式库有备份十分钟就恢复了。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →