☰
基于Neo4j的红楼梦知识图谱构建与查询实战
2026/10/2 3:50:28 网站建设 项目流程

简介:这份资源围绕《红楼梦》知识图谱的构建与可视化展示,面向知识图谱入门学习者、自然语言处理方向的学生以及需要Neo4j实战案例的开发者。它解决的是从文本到结构化知识网络的落地问题,涵盖实体识别、关系抽取与图数据库存储的完整链路,适合作为课程设计或自学练手项目。压缩包共7个文件,约1.62MB,包含csv三元组数据、Python脚本、Neo4j数据库备份文件、图谱展示图片及说明文档,分别对应数据源、构建代码、数据库还原与效果预览等环节。目前已有160人学习下载。读者可借助三元组数据与脚本理解知识图谱的构建流程,通过数据库备份快速还原红楼梦人物关系网络,并参考图谱截图与说明文档完成展示与验证,从而掌握Neo4j图数据库的基本操作与知识图谱应用思路。

1. 红楼梦知识图谱展示与Neo4j数据库:从文本到可查询关系网络

把《红楼梦》里的人物关系、家族谱系、诗词归属、事件因果整理成一张能点、能查、能扩展的关系网络,这件事听起来像数字人文的论文题目,但落到工程上,它其实是一个标准的图数据库建模与查询问题。我最初接触这个方向,是因为一个做语文教育产品的朋友提了个需求:学生读《红楼梦》时,最头疼的是人物关系——贾宝玉和林黛玉是什么关系?薛宝钗和王熙凤有没有直接交集?贾母到底有几个孙辈?这些问题用传统关系型数据库做,需要大量 JOIN,查询一深就慢得离谱。而 Neo4j 这类图数据库,天生就是为「关系」设计的,节点存实体,边存关系,查「某人的所有亲戚」就是一次图遍历,直观且高效。

这个方案适合谁?如果你正在做知识图谱构建、课程设计、或者想找一个有真实数据、有展示价值、又能练手 Neo4j 的项目,红楼梦知识图谱是一个非常好的切入点。它数据量适中(几百个人物、上千条关系),语义清晰,而且有大量公开文本可以抽取。更重要的是,它能让你完整走一遍「本体建模 → 数据抽取 → 图库导入 → 查询展示」的链路,这套链路放到工业场景下的知识图谱设计里,骨架是一样的。下面我就按实际做过的顺序,把每一步拆开讲。

2. 本体建模与数据准备:先想清楚「节点」和「边」到底存什么

2.1 红楼梦知识图谱的本体设计:五类节点与八种关系

动手写代码之前,必须先定本体。本体建模不是学术游戏,它直接决定你后面查询能不能写、展示能不能看。我一般会先问三个问题:谁?做了什么?和谁有关?对应到红楼梦,我最终定下来的节点类型有五种:

节点标签含义示例关键属性
Person人物贾宝玉、林黛玉name, gender, rank
Family家族/府邸贾府、荣国府、宁国府name, type
Event事件元妃省亲、黛玉葬花name, chapter
Poem诗词葬花吟、芙蓉女儿诔title, content
Place地点大观园、潇湘馆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, rank;relations.csv字段是from, relation, to, chapter。这种方式最笨但最准,适合课程设计或原型验证。

第二种,半自动抽取。用 jieba 分词加正则匹配,从原文里抓「某某是某某的父亲」这类句式。但红楼梦的语言是古白话,句式变化多,召回率大概只有 60%,剩下 40% 还得人工补。我试过用 HanLP 做依存句法分析,效果比正则好一些,但依然需要人工校验。

第三种,用现成的开源人物关系数据集。网上有一些红楼梦人物关系 CSV,但质量参差不齐,字段定义也不统一。我一般会拿它做底稿,然后对照原文逐条核对。注意,不要直接拿来就用,错误的关系比没有关系更误导人。

下面是一个最小可用的数据准备脚本,把 CSV 转成 Neo4j 能导入的格式:

import csv import json # 读取人物表,生成节点导入文件 persons = [] with open('persons.csv', 'r', encoding='utf-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', encoding='utf-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', encoding='utf-8') as f: json.dump(persons, f, ensure_ascii=False, indent=2) with open('relations.json', 'w', encoding='utf-8') as f: json.dump(relations, f, ensure_ascii=False, indent=2) 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 Desktop,Linux 用 tar.gz 包直接跑。Neo4j Desktop 的好处是自带浏览器界面,不用额外配。但要注意,Neo4j Desktop 默认给的内存比较保守,导入几千条数据没问题,但如果你后面想加全文索引或者跑复杂查询,需要手动调内存。

配置文件在conf/neo4j.conf,关键参数有三个:

# 堆内存初始值和最大值,建议设为物理内存的 25% 左右 dbms.memory.heap.initial_size=1G dbms.memory.heap.max_size=2G # 页面缓存,用于缓存图数据和索引,建议设为物理内存的 50% 左右 dbms.memory.pagecache.size=2G # 允许从文件导入 CSV,默认是关闭的 apoc.import.file.enabled=true

这里有个常见坑: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关系,因为测试库先跑过,正式库有备份,十分钟就恢复了。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询