RDF 和 SPARQL 初探:以維基數據為例

維基百科有一個姐妹項目,叫做“維基數據”[1](Wikidata)。你可以從維基百科左側邊欄點進去。

RDF 和 SPARQL 初探:以维基数据为例

“維基數據”將維基百科的所有數據,整理成一個可以機器處理的數據庫,方便查詢。比如,山西省人口最多的地區是哪一個?

這種問題在維基百科查詢,非常費時,必須人工從一個個條目提取信息。但是,維基數據可以只執行一條命令,就返回答案(詳見後文)。因為它提供結構化數據,可以機器查詢。

但是,維基數據不是關係型數據庫,而是 RDF 數據庫;查詢語言不是 SQL,而是 SPARQL。我粗淺地學了一點 RDF 和 SPARQL,本文就是學習筆記,演示如何使用維基數據查詢信息。

RDF 和 SPARQL 初探:以维基数据为例

一、RDF 的含義

大家都知道,關係型數據庫是目前使用最廣泛的數據庫,將數據抽象成行和列的表格關係。

RDF 和 SPARQL 初探:以维基数据为例

但是,現實世界不像表格,更像網絡。各種事物通過錯綜複雜的關係,連接在一起,組成一張網。

RDF 和 SPARQL 初探:以维基数据为例

網絡在數學裡面稱為圖(graph),每樣事物就是圖的一個節點,節點之間的關係就是將它們連在一起的那條邊。如果數據庫以圖的方式儲存數據,就稱為圖數據庫。

RDF 就是圖數據庫的一種描述方式,或者說是一種使用協議。它以“三元組”( triple)的方式,描述事物與事物之間的直接關係。

“三元組”是 RDF 的核心概念,指的是兩個事物和它們之間的關係,在語法上呈現為“主語 + 謂語 + 賓語”。

天空是藍色的。

上面這句話,就是一個 RDF 三元組。“天空”(主語)和“藍色”(賓語)是兩種事物,它們通過顏色關係(謂語)連接在一起。

RDF 和 SPARQL 初探:以维基数据为例

RDF 要求,謂語(即事物之間的關係)必須有明確定義。大家這樣想,如果謂語是給定的,就可以用主語去查詢賓語,或者用賓語去查詢主語。比如,顏色關係是給定的,那麼就可以向數據庫進行下面的查詢。

查詢一:天空 + 顏色 = ?

查詢二:?+ 顏色 = 藍色

任何組織和個人,都可以定義自己的謂語。RDF 要求每套謂語必須有一個明確的 URL,通過 URL 區分不同的謂語。RDF 官方定義了一套常用的謂語,URL 如下。

https://www.w3.org/1999/02/22-rdf-syntax-ns[2]

使用的時候,只要引用這個 URL,別人就知道用的是哪一套謂語。

URL 比較冗長,引用不方便。RDF 允許指定一個前綴,代表 URL 地址,比如上面那個官方謂語的 URL,通常用前綴<code>rdf/<code>表示。

<code>PREFIX rdf: <http>
/<http>/<code>

每個 URL 裡面可以包含多種謂語,通過“前綴 : 謂語”的形式來區分。比如,官方定義了一個“type”謂語,說明主語的類型,就可以用<code>rdf:type/<code>表示。

小明是學生。

上面這句話,寫成 RDF 三元組,就是下面的形式。

<code>PREFIX rdf: <http>

小明 rdf:type 學生.
/<http>/<code>

由於<code>rdf:type/<code>是一個常用謂語,RDF 允許把它簡寫成<code>a/<code>,因此“小明是學生”又可以表示成<code>小明 a 學生/<code>。

<code>PREFIX rdf: <http>

小明 a 學生 .
/<http>/<code>

注意,每個 RDF 三元組的結尾是一個英文的句號,用來區分多個三元組。

二、 RDF 的語法示例

下面通過一個例子,演示 RDF 如何定義事物之間的關係。

甲殼蟲是一個樂隊,成員有 John Lennon、Paul McCartney、Ringo Starr 和George Harrison。他們都是藝術家,1963年出版過一張專輯《Please Please Me》,裡面包含《Love Me Do》這首單曲,長度125秒。

上面這段話,是自然語言的文本。我們先畫出網絡關係圖。

RDF 和 SPARQL 初探:以维基数据为例

然後,轉成 RDF 三元組。首先,給出謂語的 URL,及其對應的前綴。

<code>PREFIX : <http>
PREFIX rdf: <http>
/<http>/<code>

上面例子中,有兩個 URL,表示使用兩套謂語。其中一套是官方謂語,使用前綴<code>rdf/<code>表示;另一套是自己定義的,前綴為空,表示這是默認的前綴。

”甲殼蟲是一個樂隊,成員有 John Lennon、Paul McCartney、Ringo Starr 和George Harrison。“這句話對應的三元組如下。

<code>甲殼蟲 rdf:type Band .
甲殼蟲 :name "甲殼蟲" .
甲殼蟲 :member John_Lennon .
甲殼蟲 :member Paul_McCartney .
甲殼蟲 :member Ringo_Starr .
甲殼蟲 :member George_Harrison .
/<code>

上面例子中,<code>rdf:type/<code>、<code>:name/<code>、<code>:member/<code>都是謂語。由於這些三元組的主語相同,RDF 允許將它們合併。

<code>甲殼蟲 a 樂隊 ;
:name "甲殼蟲" ;
:member John_Lennon, Paul_McCartney, George_Harrison, Ringo_Starr .
/<code>

上面的代碼中,主語相同的三元組採用合併寫法時,每個三元組之間使用分號隔開,最後一個三元組採用句號結尾。

其餘部分對應的 RDF 三元組如下。

<code>John_Lennon a 藝術家 .
Paul_McCartney a 藝術家 .
Ringo_Starr a 藝術家 .
George_Harrison a 藝術家 .
Please_Please_Me a 專輯 ;
:name "Please Please Me" ;
:date "1963" ;
:artist "甲殼蟲" ;
:track Love_Me_Do .
Love_Me_Do a Song ;
:name "Love Me Do" ;
:length 125 .
/<code>

三、SPARQL 查詢語言

SPARQL 是 RDF 數據庫的查詢語言,跟 SQL 的語法很像。它的核心思想是,根據給定的謂語動詞,從三元組提取符合條件的主語或賓語。

SPARQL 查詢的語法如下。

<code>SELECT <variables>
WHERE {
<graph>
}
/<graph>/<variables>/<code>

上面代碼中,<code><variables>/<code>是所要提取主語或賓語,<code><graph>/<code>是所要查詢的三元組模式。

比如,查詢數據庫裡面的所有專輯。

<code>SELECT ?album
WHERE {
?album rdf:type :Album .

}
/<code>

上面代碼中,<code>?album/<code>是一個變量,名字可以隨便起,第一個字符必須是問號<code>?/<code>。查詢的條件是,<code>?album/<code>這個變量是主語,根據<code>rdf:type/<code>這個謂語,可以得到<code>:Album/<code>這個賓語。這個賓語也有前綴,表示這是當前數據庫定義的。

如果返回的是符合條件的所有記錄,變量可以用星號<code>*/<code>代替,並且<code>WHERE/<code>這個關鍵詞在<code>SELECT/<code>查詢裡面可以省略,最後一個三元組的結尾句號也可以省略,所以上面的查詢也可以寫成下面的樣子。

<code>SELECT * { ?album a :Album }
/<code>

除了專輯名稱,如果還要返回專輯的演唱者,可以增加一個變量<code>?artist/<code>。

<code>SELECT ?album ?artist
{
?album a :Album .
?album :artist ?artist .
}
/<code>

上面代碼中,<code>?artist/<code>這個變量必須是<code>?album/<code>(主語)和<code>:artist/<code>(謂語)的賓語。

四、維基數據查詢示例:山西省人口最多的地區

下面通過維基數據查詢“山西省人口最多的是哪一個地區”,進一步學習 SPARQL 語法。

首先,進入維基數據網站[3],在頁面頂部的搜索欄,搜索“山西”。或者,維基百科的“山西省”頁面,左邊欄也有跳轉到維基數據的鏈接。

RDF 和 SPARQL 初探:以维基数据为例

然後,進入山西省的頁面[4]。

RDF 和 SPARQL 初探:以维基数据为例

這時,留意一下這個頁面的 URL。

https://www.wikidata.org/wiki/Q46913

上面 URL 最後結尾的<code>Q46913/<code>,就是山西省這個條目在維基數據的編號(即主語),後面要用到。

接著,頁面向下滾動,找到“contains administrative territorial entity”(所包含的行政實體)這個部分,它列出了山西省下轄的各個地區。

RDF 和 SPARQL 初探:以维基数据为例

點擊“contains administrative territorial entity”這個標題,進入它的頁面,也留意一下 URL。

https://www.wikidata.org/wiki/Property:P150

上面 URL 的最後部分<code>P150/<code>,就是“所包含的行政實體”這個謂語動詞的編號。

現在,就可以開始查詢了。進入維基數據的在線查詢頁面 query.wikidata.org[5]

RDF 和 SPARQL 初探:以维基数据为例

在查詢框裡面,輸入下面的 SPARQL 語句。

<code>SELECT ?area
WHERE {
wd:Q46913 wdt:P150 ?area .
}
/<code>

上面代碼要求返回變量<code>?area/<code>,該變量必須滿足主語“山西省”(<code>wd:Q46913/<code>)和謂語“所包含的行政實體”(<code>wdt:P150/<code>)。前綴<code>wd/<code>表示這是維基數據的條目,而前綴<code>wdt/<code>表示這是維基數據定義的謂語關係。

點擊左側邊欄的三角形運行按鈕,就可以在頁面下方得到查詢的結果。

RDF 和 SPARQL 初探:以维基数据为例

從上圖可以看到,返回的都是條目的編號。修改一下查詢語句,增加一欄文字標籤。

<code>SELECT 
?area
?areaLabel
WHERE {
wd:Q46913 wdt:P150 ?area .
?area rdfs:label ?areaLabel .
FILTER(LANGMATCHES(LANG(?areaLabel), "zh-CN"))
}
/<code>

上面代碼中,增加了一個返回的變量<code>?areaLabel/<code>,該變量是前一個變量<code>?area/<code>的文字標籤(滿足謂語<code>rdfs:label/<code>),同時增加了一個過濾語句<code>FILTER/<code>,要求只返回中文標籤。

運行這段查詢,就可以看到每個地區的中文名字了。

RDF 和 SPARQL 初探:以维基数据为例

接著,再增加一個人口變量<code>?popTotal/<code>,返回每個地區的人口總數。

<code>SELECT 
?area
?areaLabel
?popTotal
WHERE {
wd:Q46913 wdt:P150 ?area .
?area rdfs:label ?areaLabel .
FILTER(LANGMATCHES(LANG(?areaLabel), "zh-CN"))

?area wdt:P1082 ?popTotal .
}
/<code>

運行這段代碼,就可以看到人口總數了。

RDF 和 SPARQL 初探:以维基数据为例

然後,增加一個排序子句<code>order by/<code>,按照人口的倒序排序。

<code>SELECT 
?area
?areaLabel
?popTotal
WHERE {
wd:Q46913 wdt:P150 ?area .
?area rdfs:label ?areaLabel .
FILTER(LANGMATCHES(LANG(?areaLabel), "zh-CN"))

?area wdt:P1082 ?popTotal .
}
ORDER BY desc(?popTotal)
/<code>

運行結果如下。

RDF 和 SPARQL 初探:以维基数据为例

最後,加上一個<code>limit 1/<code>子句,只返回第一條數據。

<code>SELECT 
?area
?areaLabel
?popTotal
WHERE {
wd:Q46913 wdt:P150 ?area .
?area rdfs:label ?areaLabel .
FILTER(LANGMATCHES(LANG(?areaLabel), "zh-CN"))

?area wdt:P1082 ?popTotal .
}
ORDER BY desc(?popTotal)
limit 1
/<code>
RDF 和 SPARQL 初探:以维基数据为例

這樣就得到了山西省人口最多的地區。

五、維基數據查詢示例:程序員名錄

下面再看一個例子,找出維基百科收入的所有程序員。

<code>SELECT 
?programmer
?programmerLabel
WHERE {
?programmer wdt:P106 wd:Q5482740 .
?programmer rdfs:label ?programmerLabel .
FILTER (LANGMATCHES(LANG(?programmerLabel), "zh-CN"))
}
/<code>

上面代碼中,Q5482740[6] 是程序員,P106[7] 是職業。

運行這個查詢,就可以看到程序員名單了。

RDF 和 SPARQL 初探:以维基数据为例

注意,這裡只返回有中文名的程序員。如果數據庫裡面沒有收入程序員的中文名,這裡就不會返回。

然後,查詢每個程序員的主要成就。

<code>SELECT 
?programmer
?programmerLabel
?notableworkLabel
WHERE {
?programmer wdt:P106 wd:Q5482740 .
?programmer rdfs:label ?programmerLabel .
FILTER (LANGMATCHES(LANG(?programmerLabel), "zh-CN"))

?programmer wdt:P800 ?notablework .
?notablework rdfs:label ?notableworkLabel .
FILTER(LANGMATCHES(LANG(?notableworkLabel), "zh-CN"))
}
/<code>

運行結果如下。

RDF 和 SPARQL 初探:以维基数据为例

有的程序員有多項成就,比如,約翰·卡馬克有“毀滅戰士”和“雷神之錘”兩項成就。這時可以用<code>GROUP BY/<code>子句將它們合併在一起。

<code>SELECT 
?programmer
?programmerLabel
(GROUP_CONCAT(?notableworkLabel; separator="; ") AS ?works)
WHERE {
?programmer wdt:P106 wd:Q5482740 .
?programmer rdfs:label ?programmerLabel .
FILTER(LANGMATCHES(LANG(?programmerLabel), "zh-CN"))

?programmer wdt:P800 ?notablework .
?notablework rdfs:label ?notableworkLabel .
FILTER (LANGMATCHES(LANG(?notableworkLabel), "zh-CN"))
}
GROUP BY ?programmer ?programmerLabel
/<code>

上面代碼中,<code>GROUP_CONCAT/<code>函數用來把多個<code>?notableworkLabel/<code>變量合併成新的一欄<code>works/<code>。

運行結果如下。

RDF 和 SPARQL 初探:以维基数据为例

上面圖片中,“毀滅戰士”和“雷神之錘”已經合併成一個單元格了。

接著,為每個人增加一個頭像照片。

<code>SELECT 
?programmer
?programmerLabel
(GROUP_CONCAT(?notableworkLabel; separator="; ") AS ?works)
?image
WHERE {
?programmer wdt:P106 wd:Q5482740 .
?programmer rdfs:label ?programmerLabel .
FILTER(LANGMATCHES ( LANG ( ?programmerLabel ), "zh-CN"))

?programmer wdt:P800 ?notablework .
?notablework rdfs:label ?notableworkLabel .
FILTER (LANGMATCHES ( LANG ( ?notableworkLabel ), "zh-CN"))

OPTIONAL {?programmer wdt:P18 ?image}
}
GROUP BY ?programmer ?programmerLabel ?image
/<code>

上面代碼中,返回值增加了一個照片變量<code>?image/<code>。由於不是每個人都有照片,所以把照片要求放在<code>OPTIONAL/<code>條件中,表示這一項是可選的。

得到查詢結果後,把結果的表格視圖(table)切換成圖像視圖(image grid)。

RDF 和 SPARQL 初探:以维基数据为例

這時,照片就可以顯示出來了。

RDF 和 SPARQL 初探:以维基数据为例

最後,我們想知道他們是哪個地方的人,維基數據提供他們的出生地。

<code>SELECT ?programmer
?programmerLabel
(GROUP_CONCAT(?notableworkLabel; separator="; ") AS ?works)
?image
?cood
WHERE {
?programmer wdt:P106 wd:Q5482740 .
?programmer rdfs:label ?programmerLabel .
FILTER(LANGMATCHES ( LANG ( ?programmerLabel ), "zh-CN"))

?programmer wdt:P800 ?notablework .
?notablework rdfs:label ?notableworkLabel .
FILTER (LANGMATCHES ( LANG ( ?notableworkLabel ), "zh-CN"))

OPTIONAL {?programmer wdt:P18 ?image}

OPTIONAL {
?programmer wdt:P19 ?birthplace .
?birthplace wdt:P625 ?cood .
}
}
GROUP BY ?programmer ?programmerLabel ?image ?cood
/<code>

上面代碼中,返回值增加了座標變量<code>cood/<code>,先查詢程序員的出生地,然後查詢出生地的地理座標。

運行查詢之後,默認的表格視圖就會出現座標。

RDF 和 SPARQL 初探:以维基数据为例

把視圖切換成地圖(map)。

RDF 和 SPARQL 初探:以维基数据为例

這時就能看到這些程序員在世界地圖上的位置。

RDF 和 SPARQL 初探:以维基数据为例

這篇教程就到這裡為止,維基數據的查詢方法還有很多,繼續學習可以點擊查詢頁[8]頭部的<code>Examples/<code>按鈕,看看官方提供的示例。

RDF 和 SPARQL 初探:以维基数据为例

六、參考鏈接

  • RDF[9], Wikipedia

  • RDF Graph Data Model[10], Stardog

  • Learn SPARQL[11], Stardog

  • SPARQL Nuts & Bolts[12], Cambridge Semantics

  • How to Extract Knowledge from Wikipedia, Data Science Style[13], Michael Li

(完)

[1]

“維基數據”: https://www.wikidata.org

[2]

https://www.w3.org/1999/02/22-rdf-syntax-ns: https://www.w3.org/1999/02/22-rdf-syntax-ns

[3]

維基數據網站: https://www.wikidata.org/

[4]

山西省的頁面:

https://www.wikidata.org/wiki/Q46913

[5]

query.wikidata.org: https://query.wikidata.org/

[6]

Q5482740: https://www.wikidata.org/wiki/Q5482740

[7]

P106: https://www.wikidata.org/wiki/Property:P106

[8]

查詢頁: https://query.wikidata.org/

[9]

RDF: https://en.wikipedia.org/wiki/RDF

[10]

RDF Graph Data Model: https://www.stardog.com/tutorials/data-model

[11]

Learn SPARQL: https://www.stardog.com/tutorials/sparql/

[12]

SPARQL Nuts & Bolts: https://www.cambridgesemantics.com/blog/semantic-university/learn-sparql/sparql-nuts-bolts/

[13]

How to Extract Knowledge from Wikipedia, Data Science Style: https://towardsdatascience.com/how-to-extract-knowledge-from-wikipedia-data-science-style-35f50f095d1a


分享到:


相關文章: