语义Web:手把手教你用Apache Jena驾驭RDF标准

在本文中,我们简要介绍了 RDF 格式以及用于处理该格式的 Apache Jena 库。我们将研究资源描述框架 (RDF) 标准和Apache Jena如何在应用程序中使用 RDF。

什么是RDF?
资源描述框架 (Resource Description Framework) 是 W3C 的一项建议,旨在存储和交换图数据。这使得它在需要描述各种相互关联的数据及其关系时尤为有用。例如,我们可以将它用于博客系统,描述帖子、评论、作者以及它们之间的关系。

RDF 规范本身定义了如何描述数据模型,以及如何将这些模型序列化以供实际使用。这是 W3C语义网工作的基础之一。

RDF及其核心应用(如Schema.org结构化数据)对SEO的意义极其重大,它是将您的网站内容从“人类可读”升级为“机器(搜索引擎)可理解”的关键技术,是实现下一代SEO——语义搜索和知识图谱优化的基石。

传统的搜索引擎主要依靠分析网页上的文字(字符串)来猜测网页的主题。例如,它看到“苹果”、“甜”、“红色”等词,可能会猜测这个页面在讨论水果。
而RDF和结构化数据的作用是直接告诉搜索引擎:“这个页面上的这个元素,是一个特定类型的事物(Thing),它拥有这些明确的属性,并与其他事物有明确的关系。”

  • 没有RDF/结构化数据: 一段文字:“我最喜欢的电影是《肖申克的救赎》,它由弗兰克·德拉邦特执导,蒂姆·罗宾斯主演。”
  • 有RDF/结构化数据(使用Schema.org词汇): 你明确地标记出:
    • 这里有一个 Movie(事物类型)
    • 它的 name(属性)是 "肖申克的救赎"
    • 它由 director(关系)指向另一个事物:Person(弗兰克·德拉邦特)
    • 它由 actor(关系)指向另一个事物:Person(蒂姆·罗宾斯)

这样一来,搜索引擎不再需要“猜测”,而是确知了这些信息。这大大提高了信息理解的准确性和深度。

语音助手(如Google Assistant, Siri)和AI聊天机器人严重依赖结构化的数据来提供直接、准确的答案。当用户问:“OK Google,我需要一个巧克力蛋糕的食谱,要做法简单的”,搜索引擎会优先从那些用 Recipe 结构化数据标记、并且 preparationTime 较短的页面中寻找答案。

W3C和搜索引擎共同推出了一个名为 Schema.org 的词汇表,它基于RDF模型。实践中最常用的实现方式是:

  1. 选择类型: 在 Schema.org 上找到最适合你内容类型的词汇(如 Article, Product, Recipe, Person)。
  2. 实现格式: 使用 JSON-LD(推荐方式,Google首选)将结构化数据代码嵌入到网页的  或  中。JSON-LD干净、易读,且与HTML代码分离。
  3. 测试: 使用 Google的富媒体搜索结果测试工具 来验证你的代码是否正确。
  4. 部署和监控: 将代码部署到网站后,可以在 Google Search Console 中监控富媒体搜索结果的展示和点击情况。

和LLM.txt让大模型可读有啥区别?
一句话概括核心区别:

  • RDF:是精确、无歧义的数据标准化,旨在让机器像处理数据库一样可靠地处理信息。它追求的是“机器可读”的终极形式。
  • LLM.txt:是灵活、充满上下文的指令和背景信息,旨在让大模型像人类一样利用知识和常识进行推理。它追求的是“机器可理解”的友好形式。

什么是 Apache Jena?
Apache Jena是一个免费的开源 Java 框架,旨在构建语义网和关联数据应用程序。它提供了处理 RDF 模型的工具,既可以描述模型本身,也可以从各种格式对其进行序列化和反序列化。它还包含一些用于构建应用程序的其他工具,例如用于通过 HTTP 公开 RDF 模型的Fuseki 服务器。

依赖项
首先,我们需要在pom.xml文件中包含apache-jena-libs依赖项:

<dependency>
    <groupId>org.apache.jena</groupId>
    <artifactId>apache-jena-libs</artifactId>
    <type>pom</type>
    <version>5.5.0</version>
</dependency>

此时,我们已准备好开始在我们的应用程序中使用它。

RDF 模型
RDF 的核心在于我们用来表示数据的实际模型。这些模型由各种资源组成,每种资源都具有定义自身的属性以及这些资源之间的关系。

这里,我们有四种不同的资源:一篇博文、一条评论和两个人。每种资源都有一些定义自身的属性,并且它们之间有关系。

身份 
我们模型中的每个资源都必须具有一个唯一标识符。在 RDF 中,这些标识符以 URI 的形式提供,例如 /blog/posts/123或 /users/JoeBloggs。这些 URI 可能可以解析为资源本身,但并非必须如此。

使用 URI 不仅确保了我们的身份在全球范围内的唯一性,还允许不同服务控制的资源相互引用。这是关联数据在应用程序中运作的关键方面。

我们的属性和关系也具有由 URI 描述的不同标识。这些标识通常以类似于 XML 命名空间工作的简写形式书写。例如,资源类型schema:Person实际上是https://schema.org/Person,而属性 schema:name 实际上是https://schema.org/name。

虽然我们可以为资源、属性和关系使用任何身份,但为此目的,存在标准词汇表。例如,Schema.org或Friend Of A Friend (FOAF)是标准公共词汇表。使用这些词汇表,消费者可以通过其公共定义来理解我们的对象。

语句
在 RDF 中,我们使用一系列 RDF 语句(也称为 RDF 三元组)来描述资源。每个 RDF 语句由三个部分组成:

主题——声明适用的资源的身份。
谓词——我们所作陈述的身份。
对象——语句的原始值,或语句所引用的其他资源的标识。

例如,为我们的博客文章提供标题的 RDF 语句将是:

主题 –  /blog/posts/123
谓词 –  https://schema.org/headline
对象——“RDF 和 Apache Jena 简介”

我们可以根据需要添加任意数量的语句来描述我们的资源。所有资源中这些语句的完整集合定义了我们的模型。

使用 Apache Jena 构建 RDF 模型
现在我们知道了什么是 RDF 模型,我们需要能够在我们的代码中构建它们。

我们可以使用 ModelFactory创建一个空白的 RDF 模型:

Model model = ModelFactory.createDefaultModel();

然后,我们使用createResource()将资源添加到我们的模型中:

Resource blogPost = model.createResource("/blog/posts/123");

一旦我们有了资源,我们就使用addProperty()向其添加属性:


blogPost.addProperty(SchemaDO.headline, "Introduction to RDF and Apache Jena");
blogPost.addProperty(SchemaDO.wordCount, "835");
blogPost.addProperty(SchemaDO.author, model.createResource("/users/jdon"));
blogPost.addProperty(SchemaDO.comment, model.createResource("/blog/posts/123/comments/1"));

第一个参数是定义属性的谓词。Jena 为许多标准本体提供了常量,或者我们可以根据需要编写自己的本体。

在本例中,SchemaDO代表 Schema.org 词汇表。第二个参数是属性的值——可以是文字值,也可以是另一个Resource实例。

提取模型值
我们还可以从模型中提取值。如果我们知道资源的标识,我们可以使用getResource()方法检索它:

Resource blogPost = model.getResource("/blog/posts/123");

此外,我们可以使用getProperty() 方法从资源中获取单个属性:

Statement headline = blogPost.getProperty(SchemaDO.headline);

这将返回一个 表示 RDF 语句的Statement实例。

最后,一旦我们得到了Statement实例,我们就可以查询它的不同方面:


Resource subject = headline.getSubject()
Property predicate = headline.getPredicate();
RDFNode object = headline.getObject()

getObject()返回的 RDFNode 使我们能够在 RDF 模型中更好地了解该节点。例如,我们可以检查它是一个文字值还是其他资源:


assertTrue(headline.getObject().isLiteral());
assertFalse(headline.getObject().isResource());
我们可以使用getString()获取文字值,或使用 getResource()获取资源引用:

String headline = blogPost.getProperty(SchemaDO.headline).getString();
Resource author = blogPost.getProperty(SchemaDO.author).getResource();

序列化和反序列化 RDF
在代码中表示 RDF 模型很有用。但是,我们还需要能够序列化和反序列化它们。这样我们才能将它们持久化到磁盘上或在服务之间传输它们。

N-三元组
我们经常会看到以N-Triple 形式编写的 RDF 模型。此标准在 RDF 测试用例规范中定义。它要求将每个语句写在单独的行上。这样做时,我们将语句中的主语、谓语和宾语用空格分隔,然后以一个句点结束整个语句。在编写值时,URI 标识用 <> 括起来,文字值用 “” 括起来。

例如,我们的整个模型可以写成:


"Introduction to RDF and Apache Jena" .
"835" .
.
.
"What a great article!" .
.
"jdon" .
"https://jdon.com" .
"Joe Bloggs" .
"joe.bloggs@example.com" .

这组语句以简洁的形式直接表示了我们的模型。

我们可以使用 Jena 通过RDFDataMgr.write()方法编写我们的模型:

RDFDataMgr.write(System.out, model, Lang.NTRIPLES);

这将获取要写入的输出流、要写入的模型和格式,并输出模型的全部内容。

RDF-XML
除了 N-Triple 格式外,我们还有RDF-XML 格式。这种格式的可读性较差,但却是机器使用的首选格式。W3C 维护着一个描述此格式工作原理的架构,方便我们在应用程序中轻松生成和使用它。

例如,我们在 RDF-XML 中的整个模型将是:


    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:schema="https://schema.org/">
 
    835
    Introduction to RDF and Apache Jena
   
     
        https://jdon.com
        jdon
     

   

   
     
        What a great article!
       
         
            joe.bloggs@example.com
            Joe Bloggs
         

       

     

   

 


和以前一样,我们可以使用 RDFDataMgr.write() 来编写此代码,并传入 Lang.RDFXML作为格式:

RDFDataMgr.write(System.out, model, Lang.RDFXML);

这会将整个模型写入给定的输出流,但这次采用的是 RDF-XML 格式。请注意,对于此格式的所有标识符,我们必须使用绝对 URI,但我们可以选择任何 URI 方案。

最后,因为 XML 是标准交换格式,我们也可以只使用Model.write()来实现相同的结果:

model.write(System.out);

解析 RDF-XML
除了将我们的模型序列化为 RDF-XML 之外,我们还可以使用 Model.read()方法将其解析回我们的Model对象:


Model model = ModelFactory.createDefaultModel();
model.read(new StringReader(rdfxml), null);

我们必须将 XML 以Reader或InputStream实例的形式提供。我们还需要提供一个基 URI,用于转换 XML 中可能存在的相对 URI。如果没有基 URI,则可以为null 。

这将使用来自该 RDF-XML 文档的数据填充我们的模型实例。