維基百科有一個姐妹專案,叫做"維基資料"(Wikidata)。你可以從維基百科左側邊欄點進去。
"維基資料"將維基百科的所有資料,整理成一個可以機器處理的資料庫,方便查詢。比如,山西省人口最多的地區是哪一個?
這種問題在維基百科查詢,非常費時,必須人工從一個個條目提取資訊。但是,維基資料可以只執行一條命令,就返回答案(詳見後文)。因為它提供結構化資料,可以機器查詢。
但是,維基資料不是關係型資料庫,而是 RDF 資料庫;查詢語言不是 SQL,而是 SPARQL。我粗淺地學了一點 RDF 和 SPARQL,本文就是學習筆記,演示如何使用維基資料查詢資訊。
一、RDF 的含義
大家都知道,關係型資料庫是目前使用最廣泛的資料庫,將資料抽象成行和列的表格關係。
但是,現實世界不像表格,更像網路。各種事物通過錯綜複雜的關係,連線在一起,組成一張網。
網路在數學裡面稱為圖(graph),每樣事物就是圖的一個節點,節點之間的關係就是將它們連在一起的那條邊。如果資料庫以圖的方式儲存資料,就稱為圖資料庫。
RDF 就是圖資料庫的一種描述方式,或者說是一種使用協議。它以"三元組"( triple)的方式,描述事物與事物之間的直接關係。
"三元組"是 RDF 的核心概念,指的是兩個事物和它們之間的關係,在語法上呈現為"主語 + 謂語 + 賓語"。
天空是藍色的。
上面這句話,就是一個 RDF 三元組。"天空"(主語)和"藍色"(賓語)是兩種事物,它們通過顏色關係(謂語)連線在一起。
RDF 要求,謂語(即事物之間的關係)必須有明確定義。大家這樣想,如果謂語是給定的,就可以用主語去查詢賓語,或者用賓語去查詢主語。比如,顏色關係是給定的,那麼就可以向資料庫進行下面的查詢。
查詢一:天空 + 顏色 = ?
查詢二:? + 顏色 = 藍色
任何組織和個人,都可以定義自己的謂語。RDF 要求每套謂語必須有一個明確的 URL,通過 URL 區分不同的謂語。RDF 官方定義了一套常用的謂語,URL 如下。
使用的時候,只要引用這個 URL,別人就知道用的是哪一套謂語。
URL 比較冗長,引用不方便。RDF 允許指定一個字首,代表 URL 地址,比如上面那個官方謂語的 URL,通常用字首rdf
表示。
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns>
每個 URL 裡面可以包含多種謂語,通過"字首 : 謂語"的形式來區分。比如,官方定義了一個"type"謂語,說明主語的型別,就可以用rdf:type
表示。
小明是學生。
上面這句話,寫成 RDF 三元組,就是下面的形式。
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns> 小明 rdf:type 學生.
由於rdf:type
是一個常用謂語,RDF 允許把它簡寫成a
,因此"小明是學生"又可以表示成小明 a 學生
。
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns> 小明 a 學生 .
注意,每個 RDF 三元組的結尾是一個英文的句號,用來區分多個三元組。
二、 RDF 的語法示例
下面通過一個例子,演示 RDF 如何定義事物之間的關係。
甲殼蟲是一個樂隊,成員有 John Lennon、Paul McCartney、Ringo Starr 和George Harrison。他們都是藝術家,1963年出版過一張專輯《Please Please Me》,裡面包含《Love Me Do》這首單曲,長度125秒。
上面這段話,是自然語言的文字。我們先畫出網路關係圖。
然後,轉成 RDF 三元組。首先,給出謂語的 URL,及其對應的字首。
PREFIX : <http://foo.com/tutorial/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns>
上面例子中,有兩個 URL,表示使用兩套謂語。其中一套是官方謂語,使用字首rdf
表示;另一套是自己定義的,字首為空,表示這是預設的字首。
"甲殼蟲是一個樂隊,成員有 John Lennon、Paul McCartney、Ringo Starr 和George Harrison。"這句話對應的三元組如下。
甲殼蟲 rdf:type Band . 甲殼蟲 :name "甲殼蟲" . 甲殼蟲 :member John_Lennon . 甲殼蟲 :member Paul_McCartney . 甲殼蟲 :member Ringo_Starr . 甲殼蟲 :member George_Harrison .
上面例子中,rdf:type
、:name
、:member
都是謂語。由於這些三元組的主語相同,RDF 允許將它們合併。
甲殼蟲 a 樂隊 ; :name "甲殼蟲" ; :member John_Lennon, Paul_McCartney, George_Harrison, Ringo_Starr .
上面的程式碼中,主語相同的三元組採用合併寫法時,每個三元組之間使用分號隔開,最後一個三元組採用句號結尾。
其餘部分對應的 RDF 三元組如下。
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 .
三、SPARQL 查詢語言
SPARQL 是 RDF 資料庫的查詢語言,跟 SQL 的語法很像。它的核心思想是,根據給定的謂語動詞,從三元組提取符合條件的主語或賓語。
SPARQL 查詢的語法如下。
SELECT <variables> WHERE { <graph pattern> }
上面程式碼中,<variables>
是所要提取主語或賓語,<graph pattern>
是所要查詢的三元組模式。
比如,查詢資料庫裡面的所有專輯。
SELECT ?album WHERE { ?album rdf:type :Album . }
上面程式碼中,?album
是一個變數,名字可以隨便起,第一個字元必須是問號?
。查詢的條件是,?album
這個變數是主語,根據rdf:type
這個謂語,可以得到:Album
這個賓語。這個賓語也有字首,表示這是當前資料庫定義的。
如果返回的是符合條件的所有記錄,變數可以用星號*
代替,並且WHERE
這個關鍵詞在SELECT
查詢裡面可以省略,最後一個三元組的結尾句號也可以省略,所以上面的查詢也可以寫成下面的樣子。
SELECT * { ?album a :Album }
除了專輯名稱,如果還要返回專輯的演唱者,可以增加一個變數?artist
。
SELECT ?album ?artist { ?album a :Album . ?album :artist ?artist . }
上面程式碼中,?artist
這個變數必須是?album
(主語)和:artist
(謂語)的賓語。
四、維基資料查詢示例:山西省人口最多的地區
下面通過維基資料查詢"山西省人口最多的是哪一個地區",進一步學習 SPARQL 語法。
首先,進入維基資料網站,在頁面頂部的搜尋欄,搜尋"山西"。或者,維基百科的"山西省"頁面,左邊欄也有跳轉到維基資料的連結。
然後,進入山西省的頁面。
這時,留意一下這個頁面的 URL。
https://www.wikidata.org/wiki/Q46913
上面 URL 最後結尾的Q46913
,就是山西省這個條目在維基資料的編號(即主語),後面要用到。
接著,頁面向下滾動,找到"contains administrative territorial entity"(所包含的行政實體)這個部分,它列出了山西省下轄的各個地區。
點選"contains administrative territorial entity"這個標題,進入它的頁面,也留意一下 URL。
https://www.wikidata.org/wiki/Property:P150
上面 URL 的最後部分P150
,就是"所包含的行政實體"這個謂語動詞的編號。
現在,就可以開始查詢了。進入維基資料的線上查詢頁面 query.wikidata.org
在查詢框裡面,輸入下面的 SPARQL 語句。
SELECT ?area WHERE { wd:Q46913 wdt:P150 ?area . }
上面程式碼要求返回變數?area
,該變數必須滿足主語"山西省"(wd:Q46913
)和謂語"所包含的行政實體"(wdt:P150
)。字首wd
表示這是維基資料的條目,而字首wdt
表示這是維基資料定義的謂語關係。
點選左側邊欄的三角形執行按鈕,就可以在頁面下方得到查詢的結果。
從上圖可以看到,返回的都是條目的編號。修改一下查詢語句,增加一欄文字標籤。
SELECT ?area ?areaLabel WHERE { wd:Q46913 wdt:P150 ?area . ?area rdfs:label ?areaLabel . FILTER(LANGMATCHES(LANG(?areaLabel), "zh-CN")) }
上面程式碼中,增加了一個返回的變數?areaLabel
,該變數是前一個變數?area
的文字標籤(滿足謂語rdfs:label
),同時增加了一個過濾語句FILTER
,要求只返回中文標籤。
執行這段查詢,就可以看到每個地區的中文名字了。
接著,再增加一個人口變數?popTotal
,返回每個地區的人口總數。
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
,按照人口的倒序排序。
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
子句,只返回第一條資料。
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
這樣就得到了山西省人口最多的地區。
五、維基資料查詢示例:程式設計師名錄
下面再看一個例子,找出維基百科收入的所有程式設計師。
SELECT ?programmer ?programmerLabel WHERE { ?programmer wdt:P106 wd:Q5482740 . ?programmer rdfs:label ?programmerLabel . FILTER (LANGMATCHES(LANG(?programmerLabel), "zh-CN")) }
上面程式碼中,Q5482740 是程式設計師,P106 是職業。
執行這個查詢,就可以看到程式設計師名單了。
注意,這裡只返回有中文名的程式設計師。如果資料庫裡面沒有收入程式設計師的中文名,這裡就不會返回。
然後,查詢每個程式設計師的主要成就。
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")) }
執行結果如下。
有的程式設計師有多項成就,比如,約翰·卡馬克有"毀滅戰士"和"雷神之錘"兩項成就。這時可以用GROUP BY
子句將它們合併在一起。
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
上面程式碼中,GROUP_CONCAT
函式用來把多個?notableworkLabel
變數合併成新的一欄works
。
執行結果如下。
上面圖片中,"毀滅戰士"和"雷神之錘"已經合併成一個單元格了。
接著,為每個人增加一個頭像照片。
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
上面程式碼中,返回值增加了一個照片變數?image
。由於不是每個人都有照片,所以把照片要求放在OPTIONAL
條件中,表示這一項是可選的。
得到查詢結果後,把結果的表格檢視(table)切換成影象檢視(image grid)。
這時,照片就可以顯示出來了。
最後,我們想知道他們是哪個地方的人,維基資料提供他們的出生地。
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
上面程式碼中,返回值增加了座標變數cood
,先查詢程式設計師的出生地,然後查詢出生地的地理座標。
執行查詢之後,預設的表格檢視就會出現座標。
把檢視切換成地圖(map)。
這時就能看到這些程式設計師在世界地圖上的位置。
這篇教程就到這裡為止,維基資料的查詢方法還有很多,繼續學習可以點選查詢頁頭部的Examples
按鈕,看看官方提供的示例。
六、參考連結
- RDF, Wikipedia
- RDF Graph Data Model, Stardog
- Learn SPARQL, Stardog
- SPARQL Nuts & Bolts, Cambridge Semantics
- How to Extract Knowledge from Wikipedia, Data Science Style, Michael Li
(完)