2012年12月1日星期六

【转自IBM】语义 Web,Linked Data 和 Drupal,第 1 部分: 使用 PDF 公开您的数据


语义 Web,Linked Data 和 Drupal,第 1 部分: 使用 PDF 公开您的数据

Lin Clark, Drupal 开发人员, Digital Enterprise Research Institute, NUI Galway
简介: Drupal 7 是第一个在其内核中支持语义 Web 技术的主流内容管理系统。语义 Web 技术随时可用,允许您将 Web 应用从一个文档型应用转换为一个数据型应用。在本文中,学习使您的 web 数据更具互操作性,使您的数据共享更为高效的技术。一个示例显示了如何通过使用 RDF 发布内容来让 Drupal 7 实现发布 Linked Data。
发布日期: 2012 年 5 月 02 日
级别: 中级 原创语言: 英文
访问情况 : 945 次浏览
评论: 0 (查看 | 添加评论 - 登录)
平均分 5 星 共 1 个评分 平均分 (1个评分)
为本文评分

在 2001 年,Tim Berners-Lee 先生的一篇传奇文章 “The Semantic Web”,勾勒出了这样一个世界,您的手持代理可以和其他代理交换数据并制定决策使您的生活更加简便。从那时起到现在,语义 Web 已经有了相当大的改进,在最近几年也有了一个更加务实的转变。随着 Linked Data Initiative 的出现,研究和开发都关注 Berners-Lee 所谓的最主要的困难:数据互操作性。
无需创建大量自定义代码就可以从 Linked Data 技术获益。本文将介绍 Drupal 7 如何提供广泛的数据互操作性能。学习如何使用 Resource Description Framework (RDF) 公开您的 web 数据。您可以 下载 本文所使用的源代码。
为了更好地理解本文的示例,安装一个 Drupal 7 。Drupal 需要 PHP 和一个数据库服务器(比如 MySQL)才可运行。
目前,web 上的很多数据都不能互操作。例如,如果您想将数据从一个网站取出,然后与其他网站上的数据组合,您需要编写一个自定义爬虫(crawler),从页面中抓取您所需要的信息。如果您想使用那些预算较少的网站(比如个人、政府和教育机构运行的网站)上的信息,这一点尤为重要。如果开发人员有权访问网站上的结构化数据,那通常是通过专有 API 进行的,这些 API 各个网站都不相同。
Linked Data Initiative 使用很小一部分语义 Web 技术和概念(比如 RDF)来尝试解决互操作性问题,并使重利用和合并 web 上的数据更为容易。
随着对数据互操作性的关注,开发和创新的步伐不断加快,因为企业看到了语义 web 的力量。例如 Google 使用 RDF in attributes (RDFa) 来支持 Rich Snippets,这呈现一个片段,应用 Google 算法来突出显示 web 页面中嵌入的结构化数据。它们通过重点显示页面内容的某一部分来提供较为有用的搜索结果,如 图 1 所示。不同的账户显示增强的搜索结果在点击率上有 15-30% 的提高。

图 1. 一个菜谱的 Google Rich Snippet 
Google Rich Snippet 突出显示页面的某个元素
2010 年,Facebook 开始使用 RDFa 来驱动 Like 按钮,开发人员可以将其放在自己的站点上。一个 Web 页面上仅包含一点点 RDFa,使它和一个 Facebook 页面大小差不多。当访问者单击这个 Like 按钮时,访问者和外部页面在 Facebook 中建立一个连接。这类互操作性的动力来自遵守一些简单原则和少数语义 web 技术。
对于 Linked Data,最重要的准则是为 web 上的所有内容使用不同的名称,而不是使用一系列 ID 或者其他标识符。创建完全不同的名称的一个简单方法是使用域命名系统。例如,如果您在自己的网站上提供关于 jane-doe 的信息,这很难区分不同的名为 Jane Doe 的人的信息。使用标识符 http://example.com/people/jane-doe,就很容易区分所指的 Jane Doe。标识符样式被称为 HTTP URI
如果您使用了 HTTP URI 来进行识别,那么您也可以使用 web 架构来提供使关于 Jane Doe 的更多信息。您可以给用户提供关于 Jane 的更多信息,当用户访问 http://example.com/people/jane-doe 时,就可以看到她的姓名、上网号、所处位置以及她所撰写的刊物。
如果您之前使用数据库或者面向对象语言进行工作,那么对 RDF 可能比较熟悉。它只是一个 “实体-属性-值”(entity-attribute-value)模型(在 RDF 中,attributes 通常称为 properties),如 表 1 所示。

表 1. “实体-属性-值” 模型 
实体属性
http://example.com/people/jane-doenameJane Doe
为了确保互操作性,您必须使用大家都可以理解的属性名,因此,也使用 URI 作为属性。人们通常在词汇表(或者,更正式的是,本体论) 中发布一些属性 URI 包。
以下是词汇表示例。
Friend of a Friend (FOAF)
提供描述人的属性: namehomepagembox (email)、accountbased_near
Dublin Core (DC)
提供描述发表作品的属性 :abstractcreateddateCopyrightedpublisher
Semantically-Interlinked Online Communities (SIOC)
提供描述在线社交网络和其他用户的属性:followshas_replylast_reply_datemoderator_ofsubscriber_of
参阅 参考资料 获取更多关于词汇表的信息。
使用 URI 作为属性,上述 entity-attribute-value 语句看起来像这样:
<http://example.com/people/jane-doe> <http://xmlns.com/foaf/0.1/name> "Jane Doe"

尖括号中的是完整的 URI 值,引号中的是文本值。
通过使用紧凑的 URIs(CURIEs)和定义这些 CURIEs 方法的前缀使内容易于阅读。清单 1 显示了一个示例。

清单 1. 使用 CURIEs 的 RDF 语句 
    
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX person: <http://example.com/people/>

person:jane-doe foaf:name "Jane Doe"

这样,web 架构就变成了一种数据库结构,但不是所有的数据都位于一个数据存储库中,数据可位于不同的独立网站中,根据需要轻松地合并和集成。
您可以使用 SPARQL 查询语句来从这个 RDF 数据集中检索数据
很多数据集和信息中心(比如 Wikipedia)能够通过 Linked Data 打开。尽管 Wikipedia 中的很多内容都是免费文本,且很难作为 Linked Data 公开,但很多结构化信息可在 Wikipedia 页面右侧的 infoboxes 中找到。这个 infoboxes 可能包含诸如城市人口或作者风格和笔名这类信息。
DBpedia 是一个社区,致力于从 Wikipedia 中抽象结构化信息,并通过使用 RDF 将其公开使得此信息在 web 中可用。整个 RDF 文件可以从转储中下载,供应用程序所用。也提供 示范查询接口
在 DBpedia 中挖掘数据的一个简单方法是寻找与一个主题相关联的 URI。例如,假设您想寻找匹兹堡的城市人口或其他信息,首先,找到 Wikipedia 页面的 URL,然后使用 清单 2 中的 SPARQL 查询寻找与该页面相关的 DBpedia URI。因为 Snorql 界面包括示例中的前缀,您可以使用 CURIEs,无需声明您的前缀。

清单 2. 查找匹兹堡的 DBpedia URI 的查询语句
    
SELECT ?uri WHERE {
  ?uri foaf:page <http://en.wikipedia.org/wiki/Pittsburgh>
}

URI 是 <http://dbpedia.org/resource/Pittsburgh>(尽管 Snorql 接口只为您提供到另一个查询的链接,如 清单 3 所示)。

清单 3. 在 DBpedia 中查找关于匹兹堡的所有信息的查询语句
    
SELECT ?property ?hasValue ?isValueOf
WHERE {
  { <http://dbpedia.org/resource/Pittsburgh> ?property ?hasValue }
  UNION
  { ?isValueOf ?property <http://dbpedia.org/resource/Pittsburgh> }
}

这个查询语句好像很难理解,不用担心。重要的是理解在运行这个查询时,可以看到 DBpedia 中包含的关于匹兹堡的所有信息,比如全年的平均温度、经度/纬度坐标,城市标志图像和该城市诞生的著名人物。
如果您只想从 DBpedia 中提取一个事实 — 比如城市人口 — 可以使用 清单 4 中的这个查询语句。

清单 4. 查找匹兹堡人口的查询语句
    
SELECT ?population WHERE {
<http://dbpedia.org/resource/Pittsburgh> dbpedia2:populationMetro ?population
}

通过遍历不同事物之间的链接可以找到更多事实。进一步查找,获取匹兹堡出生的人名清单。添加 dbo 前缀,这样您可以编写dbo:birthPlace 而不是 dbpedia:ontology/birthPlace,如 清单 5 所示。

清单 5. 获取在匹兹堡出生的人名清单的查询语句
    
PREFIX dbo: <http://dbpedia.org/ontology/>

SELECT ?person WHERE {
?person dbo:birthPlace <http://dbpedia.org/resource/Pittsburgh> .
}

现在,您有了一个在匹兹堡出生的人名清单。为了使其更加有趣,以某一标准对此列表进行筛选。例如,仅需要属于 “美国博客作者” 这类人。要这样做,使用一个称为 rdf:type 的属性,如 清单 6 所示。

清单 6. 获取出生在匹兹堡的美国博客作者的查询语句
    
PREFIX dbo: <http://dbpedia.org/ontology/>

SELECT ?person WHERE {
?person dbo:birthPlace <http://dbpedia.org/resource/Pittsburgh> ;
        rdf:type <http://dbpedia.org/class/yago/AmericanBloggers> .
}

您可以获取关于在匹兹堡出生的博客作者列表的更多信息。清单 7 显示了如何获取文章提要,可以作为简要传记。

清单 7. 获取在匹兹堡出生的美国博客作者简短摘要的查询语句
    
PREFIX dbo: <http://dbpedia.org/ontology/>

SELECT ?person ?bio WHERE {
?person dbo:birthPlace <http://dbpedia.org/resource/Pittsburgh> ;
        rdf:type <http://dbpedia.org/class/yago/AmericanBloggers> ;
        dbo:abstract ?bio .
}

您可能注意到了,这个查询得到的比您所需要的更多,包括英语和每种现存译文的摘要。如果只要英文版,使用 FILTER 并选中语言,如 清单 8 所示。

清单 8. 英语摘要查询
    
PREFIX dbo: <http://dbpedia.org/ontology/>

SELECT ?person ?bio WHERE {
?person dbo:birthPlace <http://dbpedia.org/resource/Pittsburgh> ;
        rdf:type <http://dbpedia.org/class/yago/AmericanBloggers> ;
        dbo:abstract ?bio .
FILTER langMatches( lang(?bio), "en" )
}

上述查询只返回查询的英文版本。
SPARQL 使得从一个 RDF 数据集中获取您想要的目标信息比较容易,从而可以轻松地访问数据和跨网络重用。
Drupal 为小型数据发布者提供更为广泛的数据互操作性功能。它是一个开源内容管理系统,使开发人员和终端用户可以轻松地创建健壮的数据输入表单。该表单适合捕获结构化数据,以各种方式灵活地格式化数据。
有了 Drupal 7,甚至更小的网站都能够使用 RDF 公开它们的数据。在新 Drupal 7 网站中,基本的页面和文章内容是默认公开的。本产品的关键是 RDF Mapping API。有了这些 API,任何格式的字段都可以映射到一个 RDF 属性,任何内容类型都可以被映射到一个 RDF 类型。例如,如果该网站有一个唱片艺术家清单,则内容可能有类型 mo:MusicArtist。字段可能被映射到mo:fanpagemo:discographymo:biography 等等。
任何定义了映射的内容类型都将使用 RDFa(HTML 属性中的 RDF)自动公开内容。例如,之前的 Jane Doe 场景在 RDFa 中看起来如 清单 9 所示。

清单 9. 清单 1 中的语句在 RDFa 中的表示
    
<div about="http://example.com/people/jane-doe">
  <h1 property="foaf:name">Jane Doe</h1>
</div>

Drupal 处理标记的格式化,使其更易于发布有效 RFDa,网站所有者现在可以和其他人共享自己的数据,也可以很容易地利用各种机会,比如 Google 的 Rich Snippets。
在本小节中,您将开始发布 Linked Data。示例创建了一个 Recipe 内容类型,用于发布烹饪菜谱,可作为 Google Rich Snippets 显示。
您可以 下载 本文所用源代码。
为了跟随示例逐步学习,需要安装一个 Drupal 7 。要运行 Drupal,需要 PHP 和一个数据库服务器,比如 MySQ。如果您已经在您的开发环境中安装了 PHP 和一个数据库服务器,查看安装指南中的说明。如果没有安装,Acquia 提供易于使用的 Acquia Stack Installer,为您安装 Apache Web Server、PHP、MySQL 服务器和 Drupal 7 网站。参阅 参考资料 获取更多信息。
建立了网站之后,试着使用第二个工具栏中的 Add content 按钮添加一个 Article。您的新内容将显示在首页列表中。看看源代码,注意 RDF 已经包含在 HTML 中了。例如,文章作者是通过 sioc:has_creator 标记的,因为 Page 和 Article 内容类型都是默认 RDF 映射的。
本小节余下部分将使用其本身的自定义映射(您通过代码定义的)创建一个新内容类型。(内容类型及其映射也可以通过一个用户界面定义,这不在本文范围之内。)
Drupal 模块的第一个要求是 .info 文件,如 清单 10 所示。它向系统提供关于模块中的文件以及其他您依赖的模块的信息。

清单 10. rdf_example.info 文件
    
name = RDF Example
description = Demonstrates an RDF mapping using the RDF Mapping API.
core = 7.x
dependencies[] = richsnippets 
dependencies[] = field_collection

依赖项矩阵告知 Drupal 哪个模块需要在该模块安装之前安装。注意 field_collection 本身依赖 Entity API。见 参考资料 获取下载模块的链接。
该示例在 Rich Snippets 模块上添加了一个依赖项,因为您需要对 RDFa 进行一些较小的调整。依赖项被添加在 field_collection上,因为您将使用它来创建字段组,比如营养信息或配料。
下一步是创建内容类型,以及在 .install 文件中附加相关字段。这个 .install 文件由 3 个函数组成,如下所示。
函数作用
rdf_example_install实现 hook_install,在模块安装时被调用。
_rdf_example_installed_fields一个私有函数,被 rdf_example_install 调用;为我们需要的字段提供字段定义。
_rdf_example_installed_instances另一个私有函数,被 rdf_example_install 调用;定义附加到内容类型的字段。

在 rdf_example_install 中,首先创建 Recipe 节点类型,如 清单 11 所示。

清单 11. 在 rdf_example_install 中定义一个节点类型
    
  // Define the node type.
  $rdf_example = array(
    'type' => 'recipe',
    'name' => $t('Recipe'),
    'base' => 'node_content',
    'description' => $t('The recipe node is defined to demonstrate RDF mapping.'),
  );

  // Set additional defaults and save the content type.
  $content_type = node_type_set_defaults($rdf_example);
  node_type_save($content_type);
  

创建可以附加到 Recipe 节点类型的字段,如 清单 12 所示。这些字段可以通过 Fields UI 被其他模块和其他用户所重用。字段定义保存在另一个函数中(_rdf_example_installed_fields),用于进行代码清理。

清单 12. 创建可以添加到节点类型的字段
    
  foreach (_rdf_example_installed_fields() as $field) {
    field_create_field($field);
  }

上述代码调用 _rdf_example_installed_fields 获取需要安装的字段列表。该字段定义包含字段名和类型,以及基数(这是该字段可拥有的值的数量)。此外,设备被配置在某些字段上。对于字段定义,该示例只创建将要使用的所有字段。您不需要担心哪一个内容类型将被附加。
在 清单 13 中注意 recipe_nutrition 是一个 field_collection 字段,是一个特殊类型,连接到一个 field_collection 实体,该实体有其自己的 RDF 类型并含有自己的字段。该实体类型动态创建为 recipe_nutrition。下一步将向其中附加字段,如 清单 14 所示。

清单 13. 在 _rdf_example_installed_fields() 中定义字段
    
array(
    'recipe_photo' => array(
      'field_name' => 'recipe_photo',
      'cardinality' => 1,
      'type'        => 'image',
    ),
    'recipe_summary' => array(
      'field_name'  => 'recipe_summary',
      'cardinality' => 1,
      'type'        => 'text',
      'settings'    => array(
        'max_length' => 500,
      ),
    ),
    'recipe_nutrition' => array(
      'field_name'  => 'recipe_nutrition',
      'cardinality' => 1,
      'type'        => 'field_collection',
    ),
    'recipe_serving_size' => array(
      'field_name'  => 'recipe_serving_size',
      'cardinality' => 1,
      'type'        => 'text',
    ),
    'recipe_calories' => array(
      'field_name'  => 'recipe_calories',
      'cardinality' => 1,
      'type'        => 'number_integer',
    ),
  );

现在,字段已经在系统中创建了,使用 field_create_instance 将该字段的实例附加到 Recipe 内容类型和 Recipe Nutrition 字段集合(在 Drupal 中,这些类型被称为 bundles)。再一次,实例定义放置在一个分离函数中(_rdf_example_installed_instances)进行代码清理,清单 14 显示了一个示例。

清单 14. 附加字段示例到 rdf_example_install 中的内容类型
    
  foreach (_rdf_example_installed_instances() as $bundle_name => $bundle) {
    foreach ($bundle as $instance) {
      $instance['entity_type'] = $bundle_name == 'recipe' ? 'node' : 
'field_collection_item';
      $instance['bundle'] = $bundle_name;
      field_create_instance($instance);
    }
  }

上述代码调用 _rdf_example_installed_instances 获取字段实例定义。在 _rdf_example_installed_instances 中,您可以创建一个附加该实例的类型确定的数组。在本例中,它是 Recipe 内容类型或者 Recipe Nutrition 字段集合,如 清单 15 所示。

清单 15. 在 _rdf_example_installed_instances() 中定义实例
    
  $instances = array();
  $instances['recipe'] = array(
    'recipe_photo' => array(
      'field_name'  => 'recipe_photo',
      'label'       => $t('Photo of the prepared dish'),
    ),
    'recipe_summary' => array(
      'field_name' => 'recipe_summary',
      'label'       => $t('Short summary describing the dish'),
      'widget'      => array(
        'type'    => 'text_textarea',
      ),
    ),
    'recipe_nutrition' => array(
      'field_name' => 'recipe_nutrition',
      'label'      => $t('Recipe Nutrition Information'),
    ),
  );
  $instances['recipe_nutrition'] = array(
    'recipe_serving_size' => array(
      'field_name' => 'recipe_serving_size',
      'label'       => $t('Serving size'),
    ),
    'recipe_calories' => array(
      'field_name' => 'recipe_calories',
      'label'       => $t('Calories'),
    )
  );

在 .module 文件中,为您所定义的字段和内容类型创建 RDF 映射。因为内容类型是在这个模块中定义的,使用 hook_rdf_mapping 为内容类型创建 RDF 映射。更改其他模块创建的内容类型的映射在这里不作说明。要更改现有映射,在安装函数中您可以使用 RDF Mapping API 提供的 rdf_mapping_load 和 rdf_mapping_save 函数。
正如您在创建实例中一样,通过 bundle 类型结构化该数组。为 Recipe 内容类型定义的映射如 清单 16 所示,为 Recipe Nutrition 字段集合定义的映射如 清单 17 所示。

清单 16. 来自 rdf_example.module 的菜谱 RDF 映射声明
    
    'recipe' => array(
      'type' => 'node',
      'bundle' => 'recipe',
      'mapping' => array(
        'rdftype' => array('v:Recipe'),
        // We don't use the default bundle mapping for title. Instead, we add
        // the v:name property. We still want to use dc:title as well, though,
        // so we include it in the array.
        'title' => array(
          'predicates' => array('dc:title', 'v:name'),
        ),
        'recipe_summary' => array(
          'predicates' => array('v:summary'),
        ),
        // The photo URI isn't a string but instead points to a resource, so we
        // indicate that the attribute type is rel. If type isn't specified, it
        // defaults to property, which is used for string values.
        'recipe_photo' => array(
          'predicates' => array('v:photo'),
          'type' => 'rel',
        ),
        'recipe_nutrition' => array(
          'predicates' => array('v:nutrition'),
          'type' => 'rel',
        ),
      ),
    ),


清单 17. 来自 rdf_example.module 的菜谱营养映射声明
    
    'nutrition' => array(
      'type' => 'field_collection_item',
      'bundle' => 'recipe_nutrition',
      'mapping' => array(
        'rdftype' => array('v:Nutrition'),
        'recipe_serving_size' => array(
          'predicates' => array('v:servingSize'),
        ),
        'recipe_calories' => array(
          'predicates' => array('v:calories'),
        ),
      ),
    ),

最后,实现 hook_rdf_namespaces,这样前缀定义就被包含在 HTML 文档的顶部,如 清单 18 所示。

清单 18. 来自 rdf_example.module 的 RDF 名称空间声明 
    
function rdf_example_rdf_namespaces() {
  return array(
    // Google's namespace for their custom vocabularies.
    'v' => 'http://rdf.data-vocabulary.org/#', 
  );
}

模块完成,试着安装一下。Recipe 内容类型应该显示在 Add 内容页面。
在 Recipe 节点表单上提供标题、图像和摘要字段。为了在测试工具中查看 Rich Snippet ,您需要添加一个图像。您提交了一个节点之后,就可以选择添加营养信息了。确保至少添加一卡路里。
现在您已经有了自己的菜谱了,使用 Rich Snippets Testing Tool 测试。您可以看到一个显示图像和卡路里数的预览视图。
Linked Data 技术使网络数据更好地互操作、可重复使用、也更容易使用。像 Google 和 Facebook 这类企业已经开始构建这些技术了,因为它们使数据共享更为简单。有了 Drupal,小型数据发布者和使用者也可以从这些技术获益,不需要创建一些自定义代码。在本文中,您已经学习了如何使用 RDF 公开您网站的数据。
请继续关注本系列第 2 部分,了解如何使用由他人提供的数据,以及如何在您的 Drupal 网站中合并他人的数据和您自己的数据。

下载
描述名字大小下载方法
本文源代码rdf_example.zip3KBHTTP

学习
获得产品和技术
讨论
Lin Clark 的照片
Lin Clark 是 Drupal 的一名开发人员,专门从事 Linked Data 方面的工作。她是多个 Drupal 模块(比如 Microdata 和 SPARQL Views)的维护人员,还积极参与了 W3C 的 HTML Data Task Force 和 Drupal 的 HTML 5 计划。她曾就读于卡耐基梅隆大学,现正在攻读 NUI Galway 的 Digital Enterprise Research Institute 的研究硕士学位。您可以在 lin-clark.com 上了找到关于她的更多信息。

没有评论:

发表评论