跳转到内容

XQuery/Freebase

来自维基教科书,开放世界中的开放书籍

您希望访问一个使用 REST 作为查询参数获取 JSON 文档并返回 JSON 文档作为结果的 Web 服务。此示例使用 Freebase,一个流行的开放数据库,被 Google 收购。

基本示例

[编辑 | 编辑源代码]

我们将创建一个 Freebase 查询,该查询将返回艺术家 ID 为“/en/bob_dylan”的前三张专辑的名称。

我们将使用 EXPath http 客户端库并使用 URI 发送 HTTP GET 请求到 Freebase 服务器以对我们的查询进行编码。

示例代码

[编辑 | 编辑源代码]
xquery version "3.0";

let $freebase:="https://www.googleapis.com/freebase/v1/mqlread?query="

let $queries:= 
   (
     '[{
        "type": "/music/album",
        "name":null,
        "artist":{"id":"/en/bob_dylan"},
        "limit":3
      }]','cursor'
   )
 
let $responses := http:send-request(<http:request
     href="{$freebase || string-join(for $q in $queries return encode-for-uri($q),'&amp;')}"
     method="get"/>)
                           
return
<results>
  {if ($responses[1]/@status ne '200')
     then
         <failure>{$responses[1]}</failure>
     else
       <success>
         {util:base64-decode($responses[2])}
         {'' (: todo - use string to JSON serializer lib here :) }
       </success>
  }
</results>

示例结果

[编辑 | 编辑源代码]

返回的结果是 JSON 格式的字符串。我已经添加了空格以显示结果的结构

<results>
   <success>
     {"cursor": "eNp9js1qxDAMhB-ml5hiVrKUWBJl6XuYHNz80EBJQtIuyz59nW7p3joXgUajb7qvbV82ix2wpcWoTbMhREIAZWrz3NsT-LIBFmkILVha99txuFwM2mqcrkNv9-GnPRtJYCmJhjU2wsE-Xnx1mVZTFWTS8PgWbHvWACFLwQOUCI4Cv0LMg_SDPzdYK2oQl96n-bMwnasO0E-toygz_UFQaqbA9PD4P8hdnT8fOaiFnEvrthTICZGCgp5e45tGZB6ZQKJmBCSAbyilTh0=",
   "result": 
   [
      {
         "artist": {"id": "/en/bob_dylan"}, 
         "name": "Blood on the Tracks", 
         "type": "/music/album"
       },
       {
         "artist": {"id": "/en/bob_dylan"},
         "name": "Love and Theft",
         "type": "/music/album"
       },
       {
           "artist": {"id": "/en/bob_dylan"}, 
           "name": "Highway 61 Revisited",
           "type": "/music/album"
       }
   ]}
   </success>
</results>

Freebase API 密钥

[编辑 | 编辑源代码]

以下是描述获取 Freebase API 密钥过程的博客链接

http://anchetawern.github.io/blog/2013/02/11/getting-started-with-freebase-api/

使用游标获取更多数据

[编辑 | 编辑源代码]

默认的 Freebase 查询限制了返回的结果数量。Freebase 提供数据库游标以方便检索整个查询结果集。您要求在查询结果中返回一个游标(参见下面的示例 API 调用以获取初始请求的形式),它充当指向下一组查询结果的链接。

https://developers.google.com/freebase/v1/mql-overview#querying-with-cursor-paging-results

通过提供从先前调用返回的游标值,可以获得下一组结果。除此之外,您还将获得另一个指向下一组的游标。当检索到最后一组结果时,游标将设置为字符串“false”。

Freebase 查询概述网页上的示例包含 Python 代码示例,该示例调用库来为您处理所有游标处理工作。

https://developers.google.com/freebase/v1/mql-overview#looping-through-cursor-results

但是,通过少量尾递归,可以在 XQuery 中轻松实现相同的功能。我们将使用以下 MQL 查询作为示例,该查询返回所有电影及其 Netflix ID。

[{
  "type": "/film/film",
  "name": null,
  "netflix_id": []
}]

关于 MQL 的一些简要评论。您通过提供字段名称和 null 值来要求某事。Null 将被实际值替换。但是,如果该字段可以具有多个值,MQL 将返回一个数组并导致您的 null 查询出错。即使您期望一个单一值,这也会发生,因此您可以使用空数组的符号而不是 null 来避免此问题,如上面的查询所示。

您可以将上面的查询粘贴到 http://www.freebase.com/query 以查看结果(我们将处理代码示例中的游标)。现在进入代码,它假设 XQuery 3.0

xquery version "3.0";
import module namespace xqjson="http://xqilla.sourceforge.net/lib/xqjson";

Freebase 返回 JSON,因此我们使用上述包将其转换为 XML。从 eXist 中,您可以通过在 eXist 包管理器中单击它来安装该包,您可以从 eXist 仪表板访问该包管理器。

我们为查询声明一个变量。

declare variable $mqlQuery {'[{
   "type": "/film/film",
  "name": null,
  "netflix_id": []
}]'};

declare variable $freebase {'https://www.googleapis.com/freebase/v1/mqlread'};
declare variable $key {obtain an API key from freebase and puts it's value here'};

由于我们将进行尾递归,因此我们需要将 API 调用放入一个函数中,该函数将进行 API 调用并将结果存储在数据库中。

  declare function local:freebaseCall($cursor as xs:string,$i as xs:integer)

此函数将采用两个参数。第一个是游标,第二个是为自动递增的唯一文件名提供配置的整数。

declare function local:freebaseCall($cursor as xs:string, $i as xs:integer)
{
  if ($cursor eq 'false')

    (: termination condition :)
    then $i || ' pages loaded'
    else

     let $params :=  ('query=' || encode-for-uri($mqlQuery), 'key=' ||
       $key, 'cursor=' || encode-for-uri($cursor))

     (: Above uri encodes the parameters to the API call - we have three the
       MQL query, the API key and the cursor :)

      let $href := $freebase || '?' || string-join($params, '&amp;')

     (: This constructs the API call - again thanks to Michael Westbay for
      showing the correct way to do this by string joining the parameters
      with a separator of &amp; :)

     let $responses :=
       http:send-request(<http:request href="{$href}" method="get"/>)
      (: Make the API call. :)

    return
        if ($responses[1]/@status ne '200')
            then <failure
                   href="{xmldb:decode-uri(xs:anyURI($href))}">{$responses[1]}
                </failure>
            else

        let $jsonResponse:= util:base64-decode($responses[2])
        (: Standard EXPATH http error checking - don't forget to base64 decode
           the body of the response.
         :)

         let $freebaseXML:= xqjson:parse-json($jsonResponse)

    (: Convert the returned JSON to XML because we are going to construct an
       http PUT to store it in our xml db. :)

      let $movieData := http:send-request(
         <http:request
            href="{concat(path to store the data in your repostiory,$i,'.xml')}"
               username="username"
               password="password"
               auth-method="basic"
               send-authorization="true"
               method="put">
              <http:header name="Connection" value="close"/>
              <http:body media-type="application/xml"/>              
          </http:request>,
          (),
          <batch cursor="{$cursor}">{transform:transform($freebaseXML,doc(identity.xsl'),())}
          </batch>)

         (: Standard EXPATH PUT request. On the last line we are wrapping the
            returned XML with an element that carries the value of the cursor that
            was used to obtain the page. Identity.xsl is of course the standard
            XSLT identity transform, you can use it as a placeholder for the
            insertion of your own custom transform.
         :)

          return local:freebaseCall($freebaseXML//data(pair[@name="cursor"]), $i + 1)

         (: Finally the tail recursive call. We extract the cursor from the
            returned JSON for parameter 1 and increment $i to give us a unique
            document name for the next page to store.

};

要启动一切,请将空字符串作为初始游标值传递并初始化您的计数器

   local:freebaseCall('',1)

或者,您可以使用游标对函数调用进行预处理,以从您停止的地方重新开始检索。

   local:freebaseCall($freebaseXML//data(pair[@name="cursor"]), $i + 1)};

原始示例由 Ihe Onwuka 和 Michael Westbay 于 2014 年 3 月在 eXist 列表中提供

参考文献

[编辑 | 编辑源代码]
华夏公益教科书