📖 RDF是什么
RDF(Resource Description Framework)是W3C制定的用于描述Web资源的标准数据模型。它是知识图谱最底层的表示框架,所有上层知识表示(RDFS、OWL等)都建立在RDF之上。
🎯 RDF核心概念
- 资源(Resource):用URI/IRI唯一标识的任何事物
- 属性(Property):资源的某个方面、特征或关系
- 陈述(Statement):由主体(Subject)、谓词(Predicate)、客体(Object)组成的三元组
- 空白节点(Blank Node):没有URI的匿名资源
- 字面量(Literal):数据值,如字符串、数字、日期
RDF三元组形如:<Subject> <Predicate> <Object>
例如:<ex:鲁迅> <ex:创作> <ex:呐喊>
📐 RDF数据模型
三元组的组成要素
| 组成部分 | 说明 | 示例 |
| Subject(主体) | 必须是URI或空白节点 | ex:鲁迅 |
| Predicate(谓词) | 必须是URI | ex:创作 |
| Object(客体) | 可以是URI、空白节点或字面量 | ex:呐喊 或 "1923"^^xsd:gYear |
RDF图的本质
一组RDF三元组构成一个有标记的有向图:
- 主体和客体URI → 节点
- 谓词URI → 有标记的有向边
- 字面量 → 叶子节点(没有出边)
这个图不一定是连通的,可以包含多个独立子图。
📝 RDF序列化格式
RDF是一种抽象数据模型,可以用多种格式序列化:
| 格式 | 扩展名 | 特点 | 适用场景 |
| Turtle | .ttl | 简洁易读,最流行 | 人工编写、教学 |
| N-Triples | .nt | 每行一个三元组,解析简单 | 大规模数据处理 |
| RDF/XML | .rdf | W3C最早标准,冗长 | 旧系统兼容 |
| JSON-LD | .jsonld | JSON格式,Web友好 | Web应用、API |
| N-Quads | .nq | 支持命名图的N-Triples | 多图存储 |
| TriG | .trig | 支持命名图的Turtle | 多图场景 |
Turtle格式详解
Turtle(Terse RDF Triple Language)是最常用的RDF序列化格式:
@prefix ex: <http://example.org/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:鲁迅 ex:出生地 ex:绍兴 ;
ex:创作 ex:呐喊 , ex:彷徨 ;
ex:原名 "周树人" ;
ex:出生年份 "1881"^^xsd:gYear .
ex:鲁迅 rdf:type ex:作家 .
ex:呐喊 rdf:type ex:文学作品 .
ex:作家 rdfs:label "作家" .
ex:出生地 rdfs:label "出生地" .
ex:绍兴 rdfs:label "绍兴"@zh .
💡 Turtle语法技巧
- 分号 ; 表示同一主体的新谓词-客体对
- 逗号 , 表示同一主体和谓词下的多个客体
- 句号 . 表示三元组结束
- @zh 表示语言标签
- ^^xsd:type 表示数据类型
💻 Python实现:RDF三元组处理器
from urllib.parse import quote
import json
class RDFNode:
"""RDF节点:URI、空白节点或字面量"""
def __init__(self, value, node_type="uri", datatype=None, lang=None):
self.value = value
self.node_type = node_type
self.datatype = datatype
self.lang = lang
def __repr__(self):
if self.node_type == "uri":
return f"<{self.value}>"
elif self.node_type == "bnode":
return f"_:{self.value}"
else:
suffix = ""
if self.lang:
suffix = f"@{self.lang}"
elif self.datatype:
suffix = f"^^<{self.datatype}>"
return f'"{self.value}"{suffix}'
def __eq__(self, other):
return isinstance(other, RDFNode) and self.value == other.value and self.node_type == other.node_type
def __hash__(self):
return hash((self.value, self.node_type))
class RDFTriple:
"""RDF三元组"""
def __init__(self, subject, predicate, obj):
assert subject.node_type in ("uri", "bnode"), "主体必须是URI或空白节点"
assert predicate.node_type == "uri", "谓词必须是URI"
self.subject = subject
self.predicate = predicate
self.object = obj
def __repr__(self):
return f"{self.subject} {self.predicate} {self.object} ."
def __eq__(self, other):
return (isinstance(other, RDFTriple) and
self.subject == other.subject and
self.predicate == other.predicate and
self.object == other.object)
def __hash__(self):
return hash((self.subject, self.predicate, self.object))
class RDFGraph:
"""RDF图 - 三元组集合"""
def __init__(self):
self.triples = set()
self.prefixes = {}
self.bnode_counter = 0
def add_prefix(self, short, long):
self.prefixes[short] = long
def uri(self, prefix, local):
"""用前缀创建URI节点"""
return RDFNode(self.prefixes.get(prefix, prefix) + local)
def literal(self, value, datatype=None, lang=None):
"""创建字面量节点"""
return RDFNode(value, "literal", datatype, lang)
def bnode(self):
"""创建空白节点"""
self.bnode_counter += 1
return RDFNode(f"b{self.bnode_counter}", "bnode")
def add(self, s, p, o):
"""添加三元组"""
triple = RDFTriple(s, p, o)
self.triples.add(triple)
return triple
def remove(self, s, p, o):
"""移除三元组"""
self.triples.discard(RDFTriple(s, p, o))
def match(self, s=None, p=None, o=None):
"""模式匹配查询(None为通配符)"""
results = []
for t in self.triples:
if (s is None or t.subject == s) and \
(p is None or t.predicate == p) and \
(o is None or t.object == o):
results.append(t)
return results
def to_turtle(self):
"""导出为Turtle格式"""
lines = []
for prefix, uri in self.prefixes.items():
lines.append(f"@prefix {prefix}: <{uri}> .")
lines.append("")
for t in sorted(self.triples, key=lambda x: repr(x)):
lines.append(repr(t))
return "\n".join(lines)
def to_jsonld(self):
"""导出为JSON-LD格式"""
context = {k: v for k, v in self.prefixes.items()}
graph = []
for t in self.triples:
entry = {
"@id": t.subject.value,
t.predicate.value: t.object.value
}
graph.append(entry)
return json.dumps({"@context": context, "@graph": graph}, ensure_ascii=False, indent=2)
def size(self):
return len(self.triples)
g = RDFGraph()
g.add_prefix("ex", "http://example.org/")
g.add_prefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
g.add_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#")
g.add_prefix("xsd", "http://www.w3.org/2001/XMLSchema#")
g.add(g.uri("ex", "鲁迅"), g.uri("rdf", "type"), g.uri("ex", "作家"))
g.add(g.uri("ex", "老舍"), g.uri("rdf", "type"), g.uri("ex", "作家"))
g.add(g.uri("ex", "呐喊"), g.uri("rdf", "type"), g.uri("ex", "小说"))
g.add(g.uri("ex", "鲁迅"), g.uri("ex", "创作"), g.uri("ex", "呐喊"))
g.add(g.uri("ex", "鲁迅"), g.uri("ex", "出生地"), g.uri("ex", "绍兴"))
g.add(g.uri("ex", "鲁迅"), g.uri("ex", "原名"), g.literal("周树人", lang="zh"))
g.add(g.uri("ex", "鲁迅"), g.uri("ex", "出生年份"), g.literal("1881", datatype="http://www.w3.org/2001/XMLSchema#gYear"))
g.add(g.uri("ex", "呐喊"), g.uri("ex", "出版年份"), g.literal("1923", datatype="http://www.w3.org/2001/XMLSchema#gYear"))
address = g.bnode()
g.add(g.uri("ex", "绍兴"), g.uri("ex", "位于"), address)
g.add(address, g.uri("ex", "省份"), g.literal("浙江省"))
g.add(address, g.uri("ex", "国家"), g.literal("中国"))
print("=== RDF图统计 ===")
print(f"三元组数: {g.size()}")
print("\n=== 模式匹配查询 ===")
type_triples = g.match(p=g.uri("rdf", "type"))
for t in type_triples:
print(f" {t}")
print("\n=== 鲁迅相关查询 ===")
luxun = g.uri("ex", "鲁迅")
for t in g.match(s=luxun):
print(f" {t}")
print("\n=== Turtle序列化 ===")
print(g.to_turtle()[:500])
=== RDF图统计 ===
三元组数: 9
=== 模式匹配查询 ===
.
.
.
=== 鲁迅相关查询 ===
.
.
.
"周树人"@zh .
"1881"^^ .
=== Turtle序列化 ===
@prefix ex: .
@prefix rdf: .
@prefix rdfs: .
@prefix xsd: .
"1923"^^ .
.
🔍 SPARQL查询语言入门
SPARQL是RDF的标准查询语言,类似于SQL之于关系数据库。
SPARQL基本查询模式
| 查询类型 | 关键字 | 用途 |
| SELECT | SELECT ?x WHERE {...} | 查询满足模式的变量绑定 |
| ASK | ASK WHERE {...} | 判断是否存在匹配 |
| CONSTRUCT | CONSTRUCT {...} WHERE {...} | 根据查询构造新RDF图 |
| DESCRIBE | DESCRIBE ?x | 返回描述资源的RDF图 |
class SimpleSPARQL:
"""简易SPARQL引擎 - 支持基本SELECT查询"""
def __init__(self, graph):
self.graph = graph
def _match_pattern(self, pattern, bindings=None):
"""匹配单个三元组模式,返回变量绑定列表"""
if bindings is None:
bindings = [{}]
results = []
s_pat, p_pat, o_pat = pattern
for binding in bindings:
s = binding.get(s_pat, s_pat) if isinstance(s_pat, str) and s_pat.startswith("?") else s_pat
p = binding.get(p_pat, p_pat) if isinstance(p_pat, str) and p_pat.startswith("?") else p_pat
o = binding.get(o_pat, o_pat) if isinstance(o_pat, str) and o_pat.startswith("?") else o_pat
for triple in self.graph.match(
s=None if isinstance(s, str) and s.startswith("?") else s,
p=None if isinstance(p, str) and p.startswith("?") else p,
o=None if isinstance(o, str) and o.startswith("?") else o
):
new_binding = dict(binding)
ok = True
if isinstance(s, str) and s.startswith("?"):
if s in new_binding and new_binding[s] != triple.subject:
ok = False
else:
new_binding[s] = triple.subject
if isinstance(p, str) and p.startswith("?"):
if p in new_binding and new_binding[p] != triple.predicate:
ok = False
else:
new_binding[p] = triple.predicate
if isinstance(o, str) and o.startswith("?"):
if o in new_binding and new_binding[o] != triple.object:
ok = False
else:
new_binding[o] = triple.object
if ok:
results.append(new_binding)
return results
def select(self, variables, patterns):
"""SPARQL SELECT查询
variables: 需要返回的变量列表如 ["?s", "?o"]
patterns: 三元组模式列表如 [("?s", "ex:创作", "?o")]
"""
bindings = None
for pattern in patterns:
bindings = self._match_pattern(pattern, bindings)
if not bindings:
return []
results = []
for b in bindings:
row = {v: b.get(v, "未绑定") for v in variables}
results.append(row)
return results
sparql = SimpleSPARQL(g)
print("=== 查询:所有作家 ===")
rdf_type = g.uri("rdf", "type")
ex_writer = g.uri("ex", "作家")
results = sparql.select(["?s"], [("?s", rdf_type, ex_writer)])
for r in results:
print(f" 作家: {r['?s']}")
print("\n=== 查询:鲁迅的作品 ===")
ex_create = g.uri("ex", "创作")
luxun = g.uri("ex", "鲁迅")
results = sparql.select(["?work"], [(luxun, ex_create, "?work")])
for r in results:
print(f" 作品: {r['?work']}")
print("\n=== 查询:与绍兴相关的作品(两跳) ===")
ex_birthplace = g.uri("ex", "出生地")
shaoxing = g.uri("ex", "绍兴")
results = sparql.select(["?writer", "?work"], [
("?writer", ex_birthplace, shaoxing),
("?writer", ex_create, "?work")
])
for r in results:
print(f" {r['?writer']} 创作 {r['?work']}")
=== 查询:所有作家 ===
作家:
作家:
=== 查询:鲁迅的作品 ===
作品:
=== 查询:与绍兴相关的作品(两跳) ===
创作
🔧 RDFS:RDF Schema
RDFS在RDF基础上增加了轻量级的本体描述能力:
RDFS核心词汇
| 词汇 | 含义 | 示例 |
| rdfs:Class | 定义类 | ex:作家 rdfs:Class |
| rdfs:subClassOf | 类继承 | ex:小说家 rdfs:subClassOf ex:作家 |
| rdfs:subPropertyOf | 属性继承 | ex:创作 rdfs:subPropertyOf ex:参与 |
| rdfs:domain | 属性的定义域 | ex:创作 rdfs:domain ex:作家 |
| rdfs:range | 属性的值域 | ex:创作 rdfs:range ex:作品 |
| rdfs:label | 人类可读标签 | ex:作家 rdfs:label "Writer" |
| rdfs:comment | 描述注释 | ex:作家 rdfs:comment "从事文学创作的人" |
class RDFSReasoner:
"""简单的RDFS推理机"""
def __init__(self, graph):
self.graph = graph
self.inferred = set()
def apply_domain_range(self):
"""应用domain和range推理规则"""
rdf_type = self.graph.uri("rdf", "type")
rdfs_domain = self.graph.uri("rdfs", "domain")
rdfs_range = self.graph.uri("rdfs", "range")
domains = {}
ranges = {}
for t in self.graph.match(p=rdfs_domain):
domains[t.subject.value] = t.object
for t in self.graph.match(p=rdfs_range):
ranges[t.subject.value] = t.object
for t in self.graph.triples:
if t.predicate.value in domains:
new_triple = RDFTriple(t.subject, rdf_type, domains[t.predicate.value])
if new_triple not in self.graph.triples:
self.inferred.add(new_triple)
print(f" 推理(domain): {t.subject} → rdf:type → {domains[t.predicate.value]}")
if t.predicate.value in ranges:
new_triple = RDFTriple(t.object, rdf_type, ranges[t.predicate.value])
if new_triple not in self.graph.triples:
self.inferred.add(new_triple)
print(f" 推理(range): {t.object} → rdf:type → {ranges[t.predicate.value]}")
def apply_subclass(self):
"""应用subClassOf传递推理"""
rdf_type = self.graph.uri("rdf", "type")
rdfs_subclass = self.graph.uri("rdfs", "subClassOf")
subclass_map = {}
for t in self.graph.match(p=rdfs_subclass):
subclass_map[t.subject.value] = t.object.value
for t in self.graph.match(p=rdf_type):
cls_value = t.object.value
if cls_value in subclass_map:
parent = self.graph.uri("ex", subclass_map[cls_value].split("/")[-1])
new_triple = RDFTriple(t.subject, rdf_type, parent)
if new_triple not in self.graph.triples and new_triple not in self.inferred:
self.inferred.add(new_triple)
print(f" 推理(subClassOf): {t.subject} → rdf:type → {parent}")
g.add(g.uri("ex", "创作"), g.uri("rdfs", "domain"), g.uri("ex", "作家"))
g.add(g.uri("ex", "创作"), g.uri("rdfs", "range"), g.uri("ex", "作品"))
g.add(g.uri("ex", "小说家"), g.uri("rdfs", "subClassOf"), g.uri("ex", "作家"))
g.add(g.uri("ex", "呐喊"), g.uri("ex", "类型"), g.literal("短篇小说集"))
print("=== RDFS推理 ===")
reasoner = RDFSReasoner(g)
reasoner.apply_domain_range()
reasoner.apply_subclass()
print(f"\n推理出 {len(reasoner.inferred)} 条新知识")
=== RDFS推理 ===
推理(domain): → rdf:type →
推理(range): → rdf:type →
推理出 2 条新知识
🌍 RDF生态系统
常用RDF工具与框架
| 工具 | 语言 | 用途 |
| RDFlib | Python | Python最成熟的RDF库 |
| Apache Jena | Java | 企业级RDF/SPARQL框架 |
| OWLAPI | Java | OWL本体处理 |
| Virtuoso | C | 高性能RDF存储和SPARQL端点 |
| Blazegraph | Java | Wikidata使用的RDF图数据库 |
| GraphDB | Java | 语义推理平台 |
try:
from rdflib import Graph, Namespace, Literal, RDF, RDFS, XSD
g2 = Graph()
EX = Namespace("http://example.org/")
g2.bind("ex", EX)
g2.add((EX["鲁迅"], EX["创作"], EX["呐喊"]))
g2.add((EX["鲁迅"], RDF.type, EX["作家"]))
g2.add((EX["鲁迅"], EX["原名"], Literal("周树人", lang="zh")))
g2.add((EX["鲁迅"], EX["出生年份"], Literal(1881, datatype=XSD.gYear)))
print("=== RDFlib操作 ===")
print(f"三元组数: {len(g2)}")
print("\nTurtle序列化:")
print(g2.serialize(format="turtle")[:300])
q = """
SELECT ?s ?o WHERE {
?s ex:创作 ?o .
}
"""
print("\nSPARQL查询结果:")
for row in g2.query(q):
print(f" {row.s} → {row.o}")
except ImportError:
print("RDFlib未安装,可运行: pip install rdflib")
print("本课的自定义实现已完整演示RDF核心概念")
RDFlib未安装,可运行: pip install rdflib
本课的自定义实现已完整演示RDF核心概念
📝 实战练习
练习1:构建完整的中国历史RDF图谱
使用RDFGraph类,构建一个包含以下内容的知识图谱:
- 至少5个历史人物,每个有rdf:type、生卒年、主要成就
- 至少3种关系类型(师徒、同盟、对立等)
- 使用字面量和数据类型
- 使用RDFS声明domain和range
练习2:实现CONSTRUCT查询
扩展SimpleSPARQL类,添加 construct 方法,支持从查询结果构造新的RDF图。例如:查询所有"出生地"关系,构造一个"籍贯"关系的新图。
练习3:RDFS推理传递闭包
实现subClassOf的传递闭包推理:如果A subClassOf B,B subClassOf C,则A subClassOf C。测试一个3层继承链。
💡 学习建议:RDF是知识图谱的"汇编语言"——虽然底层,但理解它能让你真正掌握知识表示的本质。尝试用Turtle手写几组三元组,你会发现它比想象中直观。
🔗
🏆 第2课成就解锁
RDF架构师
你已经掌握了RDF数据模型、三元组存储、Turtle序列化、SPARQL查询
🔗 RDF模型
📝 Turtle格式
🔍 SPARQL查询
🧠 RDFS推理