news 2026/9/25 20:10:01

知识图谱也会有脏数据?用 pySHACL 给 RDF 加一道数据校验

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
知识图谱也会有脏数据?用 pySHACL 给 RDF 加一道数据校验

做 MySQL 的时候,我们对“数据校验”其实很熟悉。

比如用户表:

CREATETABLEuser(idBIGINTPRIMARYKEY,usernameVARCHAR(64)NOTNULL,ageINT);

这里已经偷偷规定了很多规则:

id 必须有 username 不能为空 username 最长 64 age 必须是整数

前端还可能继续限制:

年龄必须大于 0 手机号必须 11 位 邮箱必须符合格式

所以数据库世界里,我们很自然地知道:

数据不是能存进去就行,还必须符合规则。

但当我们开始学习:

RDF OWL Ontology Knowledge Graph

以后,很容易忽略一个问题:

知识图谱里的数据写错了怎么办?

比如我们定义:

Employee

表示员工。

员工应该至少有:

姓名 工号 所属部门

结果某一天进来一条数据:

王小明 类型:Employee 年龄:"二十八岁"

甚至:

王小明 email:123456

或者:

员工 A 同时属于 8 个主部门

从 RDF 的角度来看,这些数据可能依然能够组成三元组。

但从我们的业务角度看:

明显有问题。

这时候就需要今天要讲的东西:

SHACL。

以及一个非常实用的 Python 开源项目:

RDFLib/pySHACL

GitHub:

https://github.com/RDFLib/pySHACL

pySHACL 做的事情可以用一句话概括:

拿一套 SHACL 规则,检查你的 RDF 数据到底合不合格。


一、先用人话理解 SHACL

假设现在有一个员工:

王小明

我们希望所有员工满足这些条件:

必须有姓名 姓名只能有一个 必须有邮箱 年龄必须是整数 年龄不能小于 18 必须属于一个部门

这其实就是一套:

数据规则

在普通程序里,我们可能写:

ifuser.nameisNone:raiseValueError("姓名不能为空")ifuser.age<18:raiseValueError("年龄不能小于 18")

在 Java 里可能写:

@NotNullprivateStringname;@Min(18)privateIntegerage;

那知识图谱呢?

也需要类似的机制。

SHACL 就可以理解成:

RDF / Knowledge Graph 世界里的数据校验规则。

SHACL 全称:

Shapes Constraint Language

可以简单理解成:

Shape = 一个数据应该长什么样

比如:

EmployeeShape

规定:

Employee 应该长什么样。

二、Ontology 不是已经定义 Class 和 Property 了吗?

这是很多小白第一次接触 SHACL 时最疑惑的问题。

我们不是已经有:

OWL Ontology

了吗?

例如:

Employee

有:

name age email belongsToDepartment

为什么还需要 SHACL?

因为:

“描述知识”与“校验数据”不是完全一回事。

Ontology 更关注:

这个世界有哪些概念? 它们之间是什么关系? 哪些类存在继承? 可以推理出什么新知识?

而 SHACL 更关注:

这条数据合不合格?

可以简单记:

OWL = 知识是什么意思
SHACL = 数据写得对不对

举个例子。

Ontology 告诉我们:

BackendEngineer 是 Employee 的一种

这属于:

语义建模

而 SHACL 告诉我们:

Employee 必须有一个 name age 必须是整数 email 最少有一个

这是:

数据约束

两者完全可以一起使用。


三、我们先准备一份 RDF 数据

假设公司知识图谱里有两个员工。

新建:

data.ttl

内容:

@prefix ex: <http://example.com/> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . ex:zhangsan a ex:Employee ; ex:name "张三" ; ex:age 28 ; ex:email "zhangsan@example.com" . ex:lisi a ex:Employee ; ex:name "李四" ; ex:age "十八岁" .

第一位员工:

张三

数据比较正常:

name = 张三 age = 28 email = zhangsan@example.com

第二位:

李四

有两个问题。

第一个:

age = "十八岁"

这是字符串。

我们希望它是:

整数

第二个:

没有 email

假设业务规定:

员工必须有邮箱

那么这条数据就应该被检查出来。


四、没有 SHACL 时会发生什么?

从 RDF 角度来看:

ex:lisi ex:age "十八岁" .

依然是一条正常的三元组。

结构依旧是:

Subject Predicate Object

也就是:

李四 年龄 十八岁

RDF 本身并不会像 MySQL:

INT

字段那样直接阻止你保存。

这也是知识图谱和传统关系数据库一个非常明显的区别。

知识图谱非常灵活。

但是:

灵活的代价就是数据质量更难控制。

于是 SHACL 出场了。


五、给 Employee 定义一个 Shape

新建:

shapes.ttl

我们先写一套非常简单的规则:

@prefix sh: <http://www.w3.org/ns/shacl#> . @prefix ex: <http://example.com/> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . ex:EmployeeShape a sh:NodeShape ; sh:targetClass ex:Employee ; sh:property [ sh:path ex:name ; sh:minCount 1 ; sh:maxCount 1 ; ] ; sh:property [ sh:path ex:age ; sh:datatype xsd:integer ; sh:minCount 1 ; ] ; sh:property [ sh:path ex:email ; sh:minCount 1 ; ] .

第一次看可能有点乱。

我们拆开。


六、sh:targetClass 是什么意思?

这里:

sh:targetClass ex:Employee ;

意思就是:

这套规则检查所有 Employee。

也就是说,只要数据里出现:

ex:zhangsan a ex:Employee .

那么:

zhangsan

就需要接受:

EmployeeShape

的检查。

可以理解成:

EmployeeShape ↓ 检查所有 ↓ Employee

七、姓名必须存在怎么写?

看这一段:

sh:property [ sh:path ex:name ; sh:minCount 1 ; sh:maxCount 1 ; ] ;

这里:

sh:path ex:name

表示:

我要检查 name 这个属性。

然后:

sh:minCount 1

表示:

至少出现一次。

再:

sh:maxCount 1

表示:

最多出现一次。

合起来就是:

一个员工必须有且只能有一个姓名。

如果:

ex:user001 a ex:Employee .

完全没有:

name

那么:

校验失败

如果:

ex:user001 ex:name "张三" ; ex:name "李四" .

两个姓名。

同样:

校验失败

八、这其实很像数据库约束

你可以这样对照。

数据库:

nameVARCHAR(64)NOTNULL

SHACL:

sh:path ex:name ; sh:minCount 1 ;

数据库:

必须唯一只有一个值

SHACL:

sh:maxCount 1 ;

虽然两者机制完全不同,但对于刚入门的人,这样理解非常直观。


九、怎么规定 age 必须是整数?

我们写:

sh:property [ sh:path ex:age ; sh:datatype xsd:integer ; sh:minCount 1 ; ] ;

这里最关键的是:

sh:datatype xsd:integer ;

意思:

age 必须是整数类型

所以:

ex:age 28 ;

可以通过。

但:

ex:age "十八岁" ;

就不符合规则。

是不是已经开始有点像:

类型系统

了?

这也是为什么我很喜欢把 SHACL 给程序员解释成:

给 RDF 加上一层“类型 + 校验规则”。


十、甚至还能限制年龄范围

比如业务规定:

员工年龄不能低于 18 岁。

可以继续增加:

sh:minInclusive 18 ;

完整:

sh:property [ sh:path ex:age ; sh:datatype xsd:integer ; sh:minInclusive 18 ; sh:minCount 1 ; ] ;

现在:

ex:age 28 ;

通过。

但是:

ex:age 16 ;

失败。

是不是已经非常接近:

ifage<18:raiseValueError(...)

了?


十一、SHACL 规则有了,谁来执行?

这就轮到:

pySHACL

出场了。

pySHACL 是一个纯 Python 的 SHACL 验证器,可以拿:

RDF Data Graph

去对照:

SHACL Shapes Graph

执行检查。

整个过程:

data.ttl ↓ 真实 RDF 数据 shapes.ttl ↓ SHACL 校验规则 ↓ pySHACL ↓ Validation Report

最终告诉你:

通过 或者 哪里没通过

十二、安装 pySHACL

安装很简单:

pipinstallpyshacl

检查:

pyshacl--help

如果能正常看到参数说明:

pyshacl

基本安装完成。


十三、最简单的一条命令

现在目录:

demo/ ├── data.ttl └── shapes.ttl

执行:

pyshacl\-sshapes.ttl\data.ttl

这里:

-s

表示:

SHACL Shapes Graph

也就是我们的规则文件。

而:

data.ttl

就是:

待检查数据

完整流程:

data.ttl + shapes.ttl ↓ pyshacl ↓ 检查

十四、如果数据全部正常

假设:

ex:zhangsan a ex:Employee ; ex:name "张三" ; ex:age 28 ; ex:email "zhangsan@example.com" .

满足:

name 至少一个 name 最多一个 age 是整数 email 至少一个

那么结果类似:

Conforms: True

这就表示:

数据符合规则。


十五、如果数据不符合呢?

我们的:

李四

存在:

age 类型错误 email 缺失

那么验证结果会告诉你:

Conforms: False

同时还会生成:

Validation Report

指出:

哪个节点有问题 哪个属性有问题 违反了什么 Constraint

这就比程序里简单打印:

数据错误

有用太多了。


十六、让输出更适合人看

pySHACL 的命令行可以指定:

-fhuman

例如:

pyshacl\-sshapes.ttl\-fhuman\data.ttl

这样输出更偏:

人类可读

特别适合本地调试。

如果你后面想把结果:

交给程序处理

还可以选择其他 RDF / JSON-LD 等输出格式。


十七、也可以直接用 Python 调用

如果你本身就在做:

Python AI Knowledge Graph GraphRAG

那其实没必要每次跑命令行。

可以直接:

frompyshaclimportvalidate

然后:

frompyshaclimportvalidate conforms,results_graph,results_text=validate(data_graph="data.ttl",shacl_graph="shapes.ttl",)print("是否合法:",conforms)print(results_text)

运行:

python validate_graph.py

如果:

conforms = True

说明:

验证通过

如果:

False

就可以查看:

results_text

定位问题。


十八、validate 为什么返回三个东西?

pySHACL 的:

validate()

通常返回:

conforms,results_graph,results_text

分别可以理解为:

conforms = 最终通过没通过
results_graph = 结构化的验证报告 RDF Graph
results_text = 方便人阅读的文本报告

所以最简单:

ifconforms:print("数据合法")else:print("数据有问题")print(results_text)

已经可以用于很多场景。


十九、实际项目里怎么用?

假设你正在做:

企业知识库

数据来源很多:

MySQL Excel 接口 人工录入 大模型抽取 PDF 爬虫

最后全部转成:

RDF

问题来了。

不同来源的数据质量可能完全不同。

比如 AI 从文档抽取出:

张三 职位:Java

但是你的知识模型规定:

职位

应该是:

BackendEngineer FrontendEngineer ProductManager

而:

Java

应该属于:

Skill

如果不做检查:

脏数据 ↓ 直接进入知识图谱 ↓ 越来越多 ↓ 查询结果开始奇怪 ↓ GraphRAG 最后也被污染

这就是非常现实的问题。


二十、所以可以在入库前增加一道校验

架构可以变成:

PDF / Excel / MySQL / API ↓ 数据抽取 ↓ RDF 数据 ↓ pySHACL ↓ 是否符合 Shape? ↙ ↘ 否 是 ↓ ↓ 错误队列 写入图谱

这样:

SHACL 就变成了知识图谱的数据质量防火墙。

这个说法对于程序员来说特别容易理解。


二十一、例如 AI 自动抽取知识

假设大模型从简历里提取:

{"name":"王小明","age":"28岁","skill":["Go","Redis"]}

AI 看起来抽对了。

但:

age = "28岁"

如果我们的 Ontology 要求:

age = integer

那么它实际上是不符合规则的。

我们把数据转成 RDF:

ex:wangxiaoming a ex:Employee ; ex:name "王小明" ; ex:age "28岁" .

pySHACL 马上就可以发现:

DatatypeConstraintComponent

没有通过。

然后我们就可以:

拦下来 ↓ 修复 ↓ 重新校验 ↓ 再进入知识图谱

这在:

LLM + Knowledge Graph

场景里其实特别有价值。


二十二、还能校验字符串长度

比如:

员工编号

不能太短。

可以:

sh:property [ sh:path ex:employeeCode ; sh:minLength 5 ; ] ;

如果:

E1

太短。

就会验证失败。


二十三、还能使用正则表达式

比如邮箱:

sh:property [ sh:path ex:email ; sh:pattern "^[^@]+@[^@]+\\.[^@]+$" ; ] ;

那么:

zhangsan@example.com

可以通过。

而:

123456

不能通过。

这就特别像后端经常写的:

regex

校验。


二十四、还能限制值只能来自某几个选项

例如员工状态只能:

active disabled left

可以使用:

sh:in ( "active" "disabled" "left" ) ;

于是:

active

合法。

但是:

abc

不合法。

和 Java 里的:

Enum

是不是很像?


二十五、还可以检查对象类型

假设:

Employee

有一个:

belongsToDepartment

我们希望它指向的必须是:

Department

可以:

sh:property [ sh:path ex:belongsToDepartment ; sh:class ex:Department ; ] ;

正确:

王小明 ↓ belongsToDepartment ↓ 研发部

其中:

研发部 a Department

但是如果:

王小明 ↓ belongsToDepartment ↓ Go

那显然就奇怪了。

SHACL 可以把这类问题找出来。


二十六、一个比较完整的 EmployeeShape

最后可以写成:

@prefix sh: <http://www.w3.org/ns/shacl#> . @prefix ex: <http://example.com/> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . ex:EmployeeShape a sh:NodeShape ; sh:targetClass ex:Employee ; sh:property [ sh:path ex:name ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ; ] ; sh:property [ sh:path ex:age ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:integer ; sh:minInclusive 18 ; ] ; sh:property [ sh:path ex:email ; sh:minCount 1 ; sh:pattern "^[^@]+@[^@]+\\.[^@]+$" ; ] ; sh:property [ sh:path ex:belongsToDepartment ; sh:minCount 1 ; sh:maxCount 1 ; sh:class ex:Department ; ] .

现在我们的员工就有了比较明确的数据规则:

姓名 必须存在 只能有一个 必须是字符串 年龄 必须存在 必须是整数 必须 >= 18 邮箱 必须存在 必须符合格式 部门 必须存在 只能一个 必须指向 Department

这已经非常接近真实项目了。


二十七、pySHACL 还可以配合推理

pySHACL 不仅仅能:

读取 RDF ↓ 直接验证

还可以在验证前选择:

RDFS OWL RL

等推理方式。

Python:

conforms,graph,text=validate("data.ttl",shacl_graph="shapes.ttl",inference="rdfs",)

或者命令行:

pyshacl\-sshapes.ttl\-irdfs\data.ttl

为什么有用?

例如 Ontology 定义:

BackendEngineer ↓ subClassOf ↓ Employee

然后:

王小明 a BackendEngineer

虽然数据没有直接写:

王小明 a Employee

但是经过 RDFS 推理后,可以得到:

王小明 也是 Employee

再进行:

EmployeeShape

验证。

这样:

推理 + 数据校验

就串起来了。


二十八、这和 Protégé 又是什么关系?

如果你看过前面 Protégé 那篇,可以这样理解。

Protégé:

设计 Ontology

例如:

Employee Department Skill Employee belongsToDepartment Department

然后:

pySHACL

负责:

检查实际数据是不是符合规则

可以形成:

Protégé ↓ 设计知识模型 SHACL ↓ 设计数据约束 pySHACL ↓ 执行数据校验 RDF Graph ↓ 合格的数据

这就是比较完整的知识工程工作流。


二十九、SHACL 和 Java Bean Validation 其实很像

如果你是 Java 程序员,可以直接这样类比。

Java:

publicclassUser{@NotNullprivateStringusername;@Min(18)privateIntegerage;}

SHACL:

sh:property [ sh:path ex:username ; sh:minCount 1 ; ] ; sh:property [ sh:path ex:age ; sh:minInclusive 18 ; ] ;

本质思路差不多:

Java Object + Validation Annotation

对应:

RDF Graph + SHACL Shape

而:

Hibernate Validator

有点类似:

pySHACL

虽然实现机制不同,但作为入门理解非常好用。


三十、为什么我觉得 pySHACL 很适合知识图谱新手?

因为很多知识图谱教程一开始就在讲:

RDF RDFS OWL SPARQL Reasoner Description Logic

非常抽象。

但 pySHACL 解决的是一个极其具体的问题:

我的数据到底对不对?

你只需要:

一份 RDF 数据 + 一份规则

然后:

pyshacl-sshapes.ttl data.ttl

马上就可以看到结果。

学习反馈非常直接。


三十一、它特别适合哪些项目?

第一种:

Knowledge Graph

知识图谱入库前:

SHACL Validation

保证数据质量。

第二种:

GraphRAG

大模型抽取实体关系后:

LLM ↓ Triples ↓ SHACL ↓ Knowledge Graph

减少错误实体和关系进入图谱。

第三种:

企业数据治理

不同部门:

Excel MySQL API CSV

统一映射成 RDF 后:

SHACL

作为统一质量规则。

第四种:

Ontology 项目

Protégé 建模以后:

真正的数据

需要一套独立验证机制。


三十二、如果数据量非常大怎么办?

简单项目可以直接:

pySHACL + RDFLib

验证。

但 pySHACL 现在也支持:

SPARQL Remote Graph Mode

也就是数据不一定非得:

全部下载成一个本地 ttl

还可以针对远程 SPARQL Graph 做验证。

另外项目也提供:

HTTP REST Service

模式。

也就是说,后面完全可以把:

pySHACL

包装成一个:

知识图谱质量校验服务

架构变成:

数据系统 ↓ POST /validate ↓ pySHACL Service ↓ Validation Report

对于企业系统反而更加实用。


三十三、CI/CD 里甚至也可以用

这是我觉得非常有意思的一个玩法。

假设你的 GitHub 仓库里:

ontology/ shapes/ data/

每次有人提交 RDF 数据:

git push

GitHub Actions 自动执行:

pyshacl\-sshapes.ttl\data.ttl

pySHACL 的 CLI 会通过不同退出码告诉我们:

0 = 数据符合规则 1 = 数据不符合规则

于是 CI:

RDF 合法 ↓ ✅ Pass

或者:

RDF 不合法 ↓ ❌ Build Failed

是不是很像:

代码提交之前跑单元测试

?

所以你甚至可以把 SHACL 理解成:

Knowledge Graph 的自动化测试。


三十四、这可能才是 SHACL 最好理解的方式

我们平时写代码有:

Unit Test

验证代码。

数据库有:

Constraint

验证数据。

API 有:

JSON Schema

验证请求。

Java 有:

Bean Validation

验证对象。

而 RDF / Knowledge Graph:

SHACL

验证图数据。

所以:

JSON → JSON Schema Java Object → Bean Validation RDF Graph → SHACL

这样记,基本就不会忘。


三十五、最后总结

如果你正在学习:

Ontology RDF OWL Knowledge Graph GraphRAG

我非常建议顺手把:

SHACL

也学一下。

因为真实项目里,一个知识图谱的问题通常不是:

能不能存数据?

而是:

存进去的数据到底靠不靠谱?

完整的数据链路应该更像:

原始数据 ↓ 解析 / LLM 抽取 ↓ RDF ↓ SHACL Rules ↓ pySHACL ↓ Validation ↓ 修复错误 ↓ 进入 Knowledge Graph ↓ SPARQL / GraphRAG / Agent

其中 pySHACL 做的事情看起来很简单:

检查数据

但它解决的是知识工程里一个非常重要的问题:

数据质量。

如果前面的知识图谱里已经塞满了:

错误类型 错误关系 缺失属性 非法值

那么后面不管:

SPARQL

还是:

GraphRAG

甚至:

AI Agent

最终得到的结果都会受到影响。

所以 Ontology 告诉系统:

这个世界应该是什么样。

SHACL 告诉系统:

这批数据有没有按照这个世界的规则来。

而 pySHACL,就是负责真正执行这场“知识图谱体检”的工具。

项目地址:

https://github.com/RDFLib/pySHACL

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 20:07:36

堆排序图解:厘清算法堆与内存堆的区别

1. 为什么堆排序总被说“难懂”&#xff1f;先拆掉那个“堆”的心理门槛很多人第一次看到“堆排序”三个字&#xff0c;脑子里立刻浮现出编译器报错里那行刺眼的“java.lang.OutOfMemoryError: Java heap space”&#xff0c;或者调试时在IDE里点开“Variables”面板却找不到某…

作者头像 李华
网站建设 2026/9/25 20:07:34

剪贴板工作原理与复制粘贴失效排查全指南

复制粘贴这事儿&#xff0c;看着简单&#xff0c;翻车的时候能把人逼疯。Excel里明明选中了就是粘贴不了&#xff0c;Ubuntu虚拟机里从Windows复制文本过来变成乱码&#xff0c;Illustrator里复制了半天没反应&#xff0c;Jupyter里键盘都快按烂了代码还是过不来。这些场景我全…

作者头像 李华
网站建设 2026/9/25 20:05:39

运维日常用什么远程工具?工具链收敛实战

做 IT 支持和运维的&#xff0c;大概率都有过这种经历&#xff1a;电脑里装了四五个远程相关的工具&#xff0c;远控一个、传文件一个、内网穿透又一个&#xff0c;账号分别登&#xff0c;连接分别配&#xff0c;出门在外还要想半天"我现在该开哪个"。 这篇聊聊我怎么…

作者头像 李华
网站建设 2026/9/25 20:05:28

运维2-电商业务

1.MySQL 主从配置主从复制的原理 &#xff1a;主服务器开启bin-log&#xff08;记录了写操作&#xff09; 从服务器获取到主服务器的bin-log 记录到relay-log中。从服务器在通过异步的线程方式&#xff0c;对于relay-log进行重放操作。 IO线程去主服务器binlog日志拷贝 > 写…

作者头像 李华