ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从Excel到知识图谱:Neo4j图数据库入门实战指南

从Excel到知识图谱:Neo4j图数据库入门实战指南 1. 为什么我开始构建知识图谱从一张杂乱Excel说起1.1 知识图谱不是什么高深概念我第一次接触知识图谱这个概念是在处理一批企业客户关系数据的时候。当时手头有一张持续维护了三年的Excel表里面记录了客户、项目、合同、联系人、供应商之间的关系。表越来越胖字段越加越多关联查询越写越痛苦。有一次为了理清某位联系人历史上参与过哪些项目这些项目又关联了哪些客户客户之间是否存在共同投资人这条链用了三条SQL加两段Python脚本最后结果还是靠人工比对出来的。后来我意识到这类问题本质上不是计算问题而是关系问题。传统表格擅长存储行和列却不擅长表达任意两个实体之间的任意深度关系。知识图谱说白了就是把实体当作点把关系当作边用一种天然贴合人类认知的方式去组织和查询数据。它不是什么学术界才用的神秘技术电商推荐、风控反欺诈、搜索引擎的实体链接、企业内部的知识库背后都有它的影子。Neo4j是目前最主流的图数据库之一它用Cypher查询语言操作数据学习曲线比想象中平缓——前提是你愿意切换一下思维方式。1.2 图数据库与关系型数据库的分水岭有人问我MySQL也能存关系数据为什么非要引入Neo4j这个问题问到点子上了。关系型数据库表达关系的方式是靠外键和JOIN。查询A到B再到C再到D这样的多跳关系时JOIN的次数会随着查询深度线性增长数据量一大性能直线下降。更麻烦的是如果关系本身就带属性比如张三和李四之间的关系是同事从2020年持续至今SQL要额外建关联表查询时表联结变得极其复杂。图数据库把关系提升为一等公民。关系可以有自己的属性查询多跳关系时不需要反复JOIN而是沿着指针走边性能和表达力都远超表格模型。关于什么场景该用Neo4j、什么场景老实待MySQL我给一个非常务实的判断标准场景特征适合关系型数据库适合Neo4j实体之间关系固定且层级浅是不必要关系维度多变、查询深度不定痛苦天然适合关系本身需要记录属性麻烦天然支持大量聚合统计报表擅长不擅长需要沿关系链做路径分析极难擅长我做的客户关系图谱正好命中关系维度多变、查询深度不定和路径分析这两条所以Neo4j是明确的候选方案。2. Neo4j环境搭建版本选择、安装步骤与第一个坑2.1 社区版还是企业版先想清楚再动手Neo4j分为社区版Community和企业版Enterprise。社区版免费支持单实例部署没有集群和高可用能力最多开一个数据库。对于学习、做原型、个人项目、甚至是中小型企业的内部知识图谱社区版完全够用。企业版需要商业授权提供集群、多数据库、备份增强、安全增强等功能。如果你是在公司正式环境使用先问清楚公司有没有许可证预算。如果没有预算社区版跑单机是合法合规的选择。我建议从社区版开始先把知识图谱的建模和查询逻辑跑通。等真到了需要高可用、需要水平扩展的那一天再评估企业版不迟。初期复杂度控制是最重要的。2.2 安装实操JDK版本与Neo4j版本必须锁死Neo4j本身是Java写的底层依赖JDK。这里有一个非常容易踩的坑不同版本的Neo4j对JDK版本要求不同。以我用的Neo4j 5.x为例5.x要求JDK 17而Neo4j 4.4支持JDK 11和JDK 17。如果你机器的默认JDK是8或者11直接启动Neo4j 5.x会报错说Unsupported Java version。我的建议是先确认要安装的Neo4j版本再去对应版本查它要求的JDK版本。安装JDK时不要把版本搞混配置好JAVA_HOME环境变量。检查JDK版本的命令是java -version确认之后再启动Neo4j。在Windows上Neo4j的安装包是zip格式解压即用。在Linux上通常下载tar.gz包解压到/opt目录下。macOS用户可以用Homebrew安装但版本可能不是最新的谨慎使用。如果你在Windows上遇到共享文件违规之类的启动错误大概率是杀毒软件在后台扫描文件把Neo4j的数据目录加入白名单即可。以下是我在Linux服务器上安装Neo4j 5.26.0社区版的完整步骤# 下载并解压 wget https://dist.neo4j.org/neo4j-community-5.26.0-unix.tar.gz tar -xzf neo4j-community-5.26.0-unix.tar.gz mv neo4j-community-5.26.0 /opt/neo4j # 设置环境变量写入 ~/.bashrc echo export NEO4J_HOME/opt/neo4j ~/.bashrc echo export PATH\$PATH:\$NEO4J_HOME/bin ~/.bashrc source ~/.bashrc # 启动前确保JDK 17 java -version # 进入bin目录执行安装为服务 neo4j install-service neo4j start启动后用neo4j status查看状态如果看到 Neo4j is running就说明内核已经起来了。2.3 启动与登录浏览器访问搞定一切Neo4j安装完成后默认会启动一个HTTP服务浏览器访问http://localhost:7474就能打开Neo4j Browser——这是一个Web版的查询控制台也是我日常使用最多的界面。第一次进入需要登录默认用户名是neo4j初始密码是neo4j。系统会强制要求修改初始密码。这里有个小细节如果你在非本机远程访问默认配置server.default_listen_address是localhost外部机器访问不了。需要修改neo4j.conf配置文件把监听地址改成0.0.0.0并开启HTTP和Bolt端口的防火墙。Bolt端口默认是7687是Neo4j驱动连接数据库用的二进制协议端口。你在Python或Java代码里访问Neo4j时连接地址通常是bolt://localhost:7687。在浏览器里输入:help可以查看所有客户端命令比如:play打开交互式教程:history查看历史命令。这些命令会极大提升上手效率。2.4 初次见面必踩的坑内存配置与密码策略Neo4j默认的内存配置比较保守运行大一点的图谱时容易出现内存溢出。neo4j.conf里有两个关键参数# 堆内存大小 server.memory.heap.initial_size512m server.memory.heap.max_size1G # 页缓存 server.memory.pagecache.size512m如果你的服务器内存有8G可以把堆内存设为2G页缓存设为1G。这个配置不是越大越好堆内存过大会触发垃圾回收停顿页缓存过大可能挤占系统内存。一个稳妥的估算是页缓存设为数据库文件大小的80%再加上操作系统本身需要的内存余量。还有密码策略。Neo4j 5.x默认启用了密码复杂度策略要求至少8位且包含大写字母、小写字母、数字和符号。如果你在自动化脚本里用初始密码连接会被拒绝。3. Cypher查询语言用SQL思维做一次平滑迁移3.1 节点、关系、属性三个核心概念Cypher是Neo4j的查询语言语法看起来有点像SQL和ASCII艺术的结合体。如果你有SQL基础理解起来会很快。节点Node相当于数据库中的实体比如人、公司、书籍、项目。关系Relationship连接两个节点的边必须有类型和方向。属性Property节点或关系上的键值对相当于表中的字段。在Cypher里节点用一对小括号表示(n)。关系用方括号加两条连字符表示-[r]-。箭头方向表示关系的方向。整句看起来像画图(m:Person {name: 张三}) -[:WORKS_AT]- (c:Company {name: 某某科技})这行代码的含义是创建一个名为张三的人物节点创建一个名为某某科技的公司节点建立一条从人到公司的任职于关系。注意m、c是变量名:Person、:Company是节点标签{name: 张三}是属性字典。3.2 从CREATE到MATCH最常用的六条Cypher语句入门阶段以下六条语句覆盖了你日常80%的需求。创建节点CREATE (n:Person {name: 李四, age: 30})创建带关系的节点CREATE (a:Person {name: 王五})-[r:FRIEND_OF {since: 2020}]-(b:Person {name: 赵六})查询节点MATCH (n:Person) WHERE n.age 25 RETURN n.name, n.age查询多跳关系MATCH (a:Person {name: 王五})-[:FRIEND_OF*1..3]-(b) RETURN b.name*1..3表示沿关系走1到3跳这是图查询最迷人的功能——你不需要知道路径有多长只要指定范围图数据库会自动找出来。删除MATCH (n:Person {name: 李四}) DETACH DELETE n注意DETACH DELETE和DELETE的区别前者会先切断该节点上的所有关系再删除节点后者在节点仍有关系时会报错。更新属性MATCH (n:Person {name: 王五}) SET n.age 313.3 为什么Cypher比SQL更适合这类查询用一个真实场景来对比。假设你要查张三认识的人中哪些人曾经在张三所在公司工作过。SQL写法假设有三个表person, knows, works_atSELECT DISTINCT p2.name FROM person p1 JOIN knows k ON k.person_id p1.id JOIN person p2 ON p2.id k.friend_id JOIN works_at w1 ON w1.person_id p1.id JOIN works_at w2 ON w2.person_id p2.id JOIN company c ON c.id w1.company_id AND c.id w2.company_id WHERE p1.name 张三;Cypher写法MATCH (p1:Person {name: 张三})-[:KNOWS]-(p2:Person)-[:WORKS_AT]-(c:Company)-[:WORKS_AT]-(p1) RETURN DISTINCT p2.name关键是可读性和维护性。SQL的JOIN链一旦超过四层理解起来就很吃力Cypher的语法结构就是图本身的样子查多深的关系画多长的链就够了。这就是我为什么愿意花时间迁移到这个技术栈。4. 亲手搭建第一个知识图谱以技术书籍推荐为例4.1 实体与关系设计先画出来再写代码学了语法之后我强烈建议你不要直接上手公司数据先拿一个自己熟悉的领域练手。我第一个完整的知识图谱做的是技术书籍推荐选它是因为实体类型简单、关系清晰、数据量可控。我设计的实体和关系如下实体类型节点标签Book书属性包括书名、作者、出版年份、豆瓣评分Author作者属性包括姓名、国籍Topic主题/领域属性包括名称Publisher出版社属性包括名称关系类型WRITTEN_BY书到作者表示该书由该作者创作COVER_TOPIC书到主题表示该书涉及某个主题PUBLISHED_BY书到出版社表示该书由该出版社出版RECOMMENDED_FOR书到主题或书到读者水平表示推荐给某类人群我在项目里简化了推荐给某类人群用属性表示更省事所以最终只用三条关系。动手写Cypher之前我把整个图谱的实体关系画在一张白纸上——这一步比代码重要得多。知识图谱的质量百分之八十取决于建模阶段画图阶段发现关系设计不合理改起来只是擦掉一条线写进代码后发现不合理改的就是一堆数据了。4.2 写入数据用Cypher逐条创建我选了六本经典编程书籍做数据样本CREATE (b1:Book {title: 代码整洁之道, author: Robert C. Martin, year: 2008, rating: 8.5}), (b2:Book {title: 重构改善既有代码的设计, author: Martin Fowler, year: 1999, rating: 9.0}), (b3:Book {title: 深入理解计算机系统, author: Randal E. Bryant, year: 2016, rating: 9.8}), (b4:Book {title: 算法导论, author: Thomas H. Cormen, year: 2009, rating: 9.3}), (b5:Book {title: 设计模式可复用面向对象软件的基础, author: Erich Gamma, year: 1994, rating: 9.1}), (b6:Book {title: 计算机网络自顶向下方法, author: James F. Kurose, year: 2012, rating: 8.8}); CREATE (a1:Author {name: Robert C. Martin, country: 美国}), (a2:Author {name: Martin Fowler, country: 英国}), (a3:Author {name: Randal E. Bryant, country: 美国}), (a4:Author {name: Thomas H. Cormen, country: 美国}), (a5:Author {name: Erich Gamma, country: 瑞士}), (a6:Author {name: James F. Kurose, country: 美国}); CREATE (t1:Topic {name: 编码规范}), (t2:Topic {name: 设计模式}), (t3:Topic {name: 计算机组成原理}), (t4:Topic {name: 算法}), (t5:Topic {name: 计算机网络}); CREATE (p1:Publisher {name: 人民邮电出版社}), (p2:Publisher {name: 机械工业出版社}), (p3:Publisher {name: 电子工业出版社}); CREATE (b1)-[:WRITTEN_BY]-(a1), (b1)-[:COVER_TOPIC]-(t1), (b2)-[:WRITTEN_BY]-(a2), (b2)-[:COVER_TOPIC]-(t1), (b2)-[:COVER_TOPIC]-(t2), (b3)-[:WRITTEN_BY]-(a3), (b3)-[:COVER_TOPIC]-(t3), (b4)-[:WRITTEN_BY]-(a4), (b4)-[:COVER_TOPIC]-(t4), (b5)-[:WRITTEN_BY]-(a5), (b5)-[:COVER_TOPIC]-(t2), (b6)-[:WRITTEN_BY]-(a6), (b6)-[:COVER_TOPIC]-(t5), (b1)-[:PUBLISHED_BY]-(p1), (b2)-[:PUBLISHED_BY]-(p1), (b3)-[:PUBLISHED_BY]-(p2), (b4)-[:PUBLISHED_BY]-(p2), (b5)-[:PUBLISHED_BY]-(p1), (b6)-[:PUBLISHED_BY]-(p3);注意我在这里把author作为属性放进了Book节点同时又在Author节点中记录了作者信息。这样做会让数据产生冗余。实际项目中同一作者的多本书会导致作者既写在了Book属性里又有独立的Author节点这种不一致。如果要做严格的图建模应该统一采用节点加关系的方式让book.author属性消失只保留(book)-[:WRITTEN_BY]-(author)。4.3 查询与可视化看到图谱那一刻才算入门数据写进去之后第一件事是看一眼全局的图长什么样。在Neo4j Browser里执行MATCH (n) RETURN n LIMIT 50会看到散布的节点和连线出现在界面上。默认的节点颜色和标签可能分不清可以点击节点类型单独设置颜色。当你第一次看到这些点线交织成网时你会理解为什么图数据库叫图数据库——它不是存图表的但结果展示天然就是一张图。接下来做几个查询练手查某本书涉及的主题和作者MATCH (b:Book {title: 设计模式可复用面向对象软件的基础})-[:COVER_TOPIC]-(t:Topic), (b)-[:WRITTEN_BY]-(a:Author) RETURN t.name, a.name找包含设计模式主题的所有书籍以及这些书的作者MATCH (t:Topic {name: 设计模式})-[:COVER_TOPIC]-(b:Book)-[:WRITTEN_BY]-(a:Author) RETURN b.title, a.name找与《代码整洁之道》同一作者的其他书MATCH (b1:Book {title: 代码整洁之道})-[:WRITTEN_BY]-(a:Author)-[:WRITTEN_BY]-(b2:Book) RETURN b2.title到这里你已经完成了从存数据到用数据的跨越。查询语句写起来就像说人话从主题节点出发往反方向找覆盖它的书再顺着书写关系找到作者。5. 进阶路线从Protege本体建模到Neo4j导入5.1 为什么需要本体建模直接拿Cypher写CREATE语句建图适合小数据量和探索阶段。数据量大了、多人协作了、业务逻辑复杂了就会出现一个问题每个人对关系的理解不一致。有人把员工和部门的关系命名为WORKS_IN有人命名为EMPLOYED_BY还有人直接把这个关系做成了属性——图谱变得不可控。这时候就需要本体Ontology层面的约束。本体定义了领域里面的概念、概念的属性、概念之间的关系、以及约束规则。它是图谱的元模型或 schema。Protege是一款开源的本体编辑工具桌面版有图形化界面用起来跟画类图差不多。它支持OWLWeb Ontology Language标准可以定义类、属性、关系、实例。从学习路径上看我建议的顺序是先用Cypher手动建图感受图数据库的无模式灵活性。再用Protege做一次正式的本体建模体会先建模后建图的工程规范。最后回到Neo4j把本体里的类、属性、关系映射成标签、属性、关系类型。5.2 Protege构建本体并导出在Protege里我以人物-组织-项目领域为例建立本体的基本流程定义类Classes在Active Ontology界面设置IRI然后在Classes标签页新建Person、Organization、Project三个类。类可以继承比如把Person的子类设为Employee、Manager。定义对象属性Object Properties对象属性描述的是实例之间的关系比如worksFor定义域是Person、值域是OrganizationparticipatesIn定义域是Person、值域是Project。定义数据属性Data Properties数据属性是节点本身的属性比如Person有fullName字符串、birthYear整数。添加个体Individuals可以手动添加具体的人物和组织实例也可以先不添加只把模式定义好。完成建模后文件另存为OWL格式RDF/XML序列化后缀通常是.owl。Protege和Neo4j之间没有直接的官方迁移工具工程上常用的方式有两种使用Neosemantics现名n10s插件直接在Neo4j里读取RDF数据。n10s安装后可以用Cypher调用过程把OWL文件导入图数据库。用Python的rdflib库将OWL解析成三元组再通过py2neo写入Neo4j。5.3 导入Neo4j的常见方式与注意事项目前最主流的方式是安装Neosemantics插件。具体操作# 下载对应Neo4j版本的neosemantics jar包 # 放入 /opt/neo4j/plugins 目录 # 重启Neo4j配置完成后用Cypher创建约束并导入CREATE CONSTRAINT n10s_unique_uri FOR (r:Resource) REQUIRE r.uri IS UNIQUE; CALL n10s.graphconfig.init(); CALL n10s.rdf.import.fetch(file:///path/to/ontology.owl, RDF/XML);这里容易踩的坑有三个版本兼容性n10s的版本必须和Neo4j版本严格对应。装错版本插件加载报错Neo4j甚至可能起不来。装之前去GitHub的neosemantics仓库看Release说明里面写清楚了支持的和Neo4j版本。URI约束n10s要求每个节点必须有URI且唯一。如果你的OWL文件里面没有给每个个体定义URI导入会失败。命名空间导入前先检查OWL文件的命名空间配置确保在graphconfig里设置正确的命名空间否则类别名称会被解析成全URI字符串导致标签命名混乱。如果用Python路线核心逻辑不复杂from rdflib import Graph from neo4j import GraphDatabase graph Graph() graph.parse(ontology.owl, formatxml) driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, password)) with driver.session() as session: for s, p, o in graph: # 三元组写入s是主体p是谓词o是客体 # 简单策略s和o都创建为节点p作为关系类型 session.run( MERGE (a:Resource {uri: $uri}) MERGE (b:Resource {uri: $uri2}) MERGE (a)-[r:RELATION {type: $pred}]-(b), uristr(s), uri2str(o), predstr(p) )注意上面的代码是极简演示没有处理字面量literal、多类型谓词、属性过滤真实场景至少要对o做一次是否URI还是字面量的判断再决定是当作节点还是属性。从我的实操经验看如果只是想快速试试Protege到Neo4j的链路优先用n10s如果想深度定制转换逻辑比如把某些谓词映射为节点属性把某些类映射为特定标签Python方案更灵活。不要两条路同时上手先选一条走通再考虑优化。6. 我踩过的坑和给你的实操建议6.1 节点合并与去重MERGE比CREATE稳我第一次建图时有个惨痛教训。用CREATE语句录入书籍数据因为脚本中途断掉了重跑了一次结果节点全部重复了。查询《代码整洁之道》的作者结果显示两个同名的Robert节点各自连了一本书。数据脏了清理起来要手动匹配合并极其痛苦。后来我把所有创建语句中的CREATE一律换成MERGE。MERGE的语义是查找存在就不创建不存在才创建。它是按模式匹配的所以必须在节点里带上唯一性属性。MERGE (b:Book {title: 代码整洁之道}) ON CREATE SET b.year 2008, b.rating 8.5 ON MATCH SET b.year 2008, b.rating 8.5ON CREATE SET和ON MATCH SET用来处理新建和已存在两种场景。这样即使脚本重复执行也不会产生重复节点。为了进一步防止重复我还给关键属性加了唯一约束CREATE CONSTRAINT book_title_unique IF NOT EXISTS FOR (b:Book) REQUIRE b.title IS UNIQUE;加了约束之后如果再试图创建相同标题的书数据库会直接报错而不是静默写入这对数据质量的保障是决定性的。6.2 中文数据编码问题Neo4j对中文的支持总体没有太大问题但有两个场景容易踩坑一是Windows环境下导入CSV文件时如果CSV不是UTF-8编码读出来的中文全是乱码。解决方法是导入前用编辑器强制转成UTF-8无BOM或者用Python预处理统一转换。二是Cypher脚本文件本身的编码。我在Windows写好的.cypher脚本放到Linux服务器上执行只要文件不是UTF-8中文就乱。建议统一用VS Code保存为UTF-8编码并在脚本开头加上排序等无关操作来间接验证中文是否正常。中文属性对Cypher语法本身没有影响不需要加引号转义但要注意节点标签Label尽量不要用中文。标签作为代码层面的标识符用英文让代码更可读。数据层面的名称属性用中文没有任何问题。6.3 大数据量导入别用逐条CREATE如果你的知识图谱只有几十上百个节点手动写Cypher没问题。一旦数据量到数万级别逐条CREATE的性能会让人崩溃。Neo4j针对大批量导入提供了三种主要方案按推荐度排序方案适用场景大致性能neo4j-admin import初次全量导入数据在CSV文件每秒数十万条LOAD CSVCypher命令结构化数据中等数据量每秒数千条批量事务APIPython/Java驱动需要预处理或在线增量导入每秒数万条生产环境中最常用的是LOAD CSVLOAD CSV WITH HEADERS FROM file:///books.csv AS row MERGE (b:Book {title: row.title}) SET b.author row.author, b.year toInteger(row.year);注意CSV文件要放在Neo4j的import目录下路径用的是file:///协议而不是操作系统的绝对路径。另外LOAD CSV自带事务语义每10万行会生成一个检查点中途失败可以从头重跑但前面已提交的数据不会回滚所以MERGE在这里几乎是必须的用CREATE会再次产生重复数据。6.4 思维转变从表到图的本质是放弃规范化执念这两个月持续用Neo4j做项目之后我最深的感触是一个思维层面的转变。关系型数据库的建模是规范化驱动的把所有信息拆成最小的不可再分的单元通过外键组合恢复原貌。这种建模方式在数据一致性上极有优势但查询时要把碎片拼回去代价是JOIN。图数据库的建模是查询驱动的先想清楚你将来会问什么问题再决定哪些信息建模为节点、哪些建模为关系、哪些建模为属性。它允许冗余允许重复因为在图数据库里一条查询的便利性远远重要于消除存储冗余。举个具体的例子在关系型数据库里一本书的主题通常放到主题表里通过关联表连接。而在Neo4j里一个节点可以有多个COVER_TOPIC关系也可以在节点属性里直接写一串主题标签。图数据库的灵活性给了你选择空间也给了你犯错的自由。建模时唯一的标准就是你的核心查询模式是什么就按怎么查来建。我个人的建议是入门阶段不需要刻意追求完美本体。你完全可以先快后慢——先用宽松的标签和属性把数据塞进去跑起来跑通了之后再根据实际查询中出现的问题比如某个关系类型歧义大、某个属性总是被重复查询逐步收紧约束和抽象级别。Neo4j这种无模式起步、后续逐步规范化的方式才是它相比传统数据库最具优势的工程特性。从Excel表格走到Neo4j图谱过程比想象中顺路上遇到的坑也都不算深。只要把本文第一部分的环境搭建跑通再照着第四部分建完你的第一个迷你图谱你就会真切感受到所有关系一目了然带来的效率提升。下一篇我准备聊聊如何把真实业务数据从MySQL迁移进Neo4j以及两个系统之间如何做数据同步这部分是很多人在生产落地时绕不开的坎。
返回列表