慣性聚合 高效追蹤和閱讀你感興趣的部落格、新聞、科技資訊
閱讀原文 在慣性聚合中打開

推薦訂閱源

博客园 - 司徒正美
V
V2EX
T
Tailwind CSS Blog
有赞技术团队
有赞技术团队
aimingoo的专栏
aimingoo的专栏
Apple Machine Learning Research
Apple Machine Learning Research
IT之家
IT之家
Blog — PlanetScale
Blog — PlanetScale
A
About on SuperTechFans
月光博客
月光博客
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
Martin Fowler
Martin Fowler
博客园 - 聂微东
The GitHub Blog
The GitHub Blog
V
Visual Studio Blog
WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI

阮一峰的网络日志

科技爱好者周刊(第 396 期):互联网通信的替代方案 科技爱好者周刊(第 396 期):互联网通信的替代方案 - 阮一峰的网络日志 科技爱好者周刊(第 395 期):软件开发的第三种方式 科技爱好者周刊(第 395 期):软件开发的第三种方式 - 阮一峰的网络日志 科技爱好者周刊(第 393 期):脑腐状态 科技爱好者周刊(第 392 期):axios 投毒与好莱坞式骗术 科技爱好者周刊(第 391 期):AI 的贫富分化 科技爱好者周刊(第 390 期):没有语料,大模型就是智障 套壳中国大模型撑起500亿美元估值?扒一扒 Cursor 的"套壳"疑云 科技爱好者周刊(第 389 期):未来如何招聘程序员 科技爱好者周刊(第 388 期):测试是新的护城河 零安装的"云养虾":ArkClaw 使用指南 科技爱好者周刊(第 387 期):你是领先的 科技爱好者周刊(第 386 期):当外卖员接入 AI 字节全家桶 Seed 2.0 + TRAE 玩转 Skill 科技爱好者周刊(第 385 期):马斯克害怕中国车企吗? 智谱旗舰 GLM-5 实测:对比 Opus 4.6 和 GPT-5.3-Codex 科技爱好者周刊(第 384 期):为什么软件股下跌 科技爱好者周刊(第 383 期):你是第几级 AI 编程 Kimi 的一体化,Manus 的分层 科技爱好者周刊(第 382 期):独立软件的黄昏 AI native Workspace 也许是智能体的下一阶段 科技爱好者周刊(第 381 期):中国 AI 大模型领导者在想什么 科技爱好者周刊(第 380 期):为什么人们拥抱"不对称收益" 科技爱好者周刊(第 379 期):《硅谷钢铁侠》摘录 我如何用 AI 处理历史遗留代码:MiniMax M2.1 升级体验 科技爱好者周刊(第 378 期):预测是新的互联网热点 科技爱好者周刊(第 377 期):14万美元的贫困线 科技爱好者周刊(第 376 期):太空数据中心的争议 科技爱好者周刊(第 375 期):一扇门的 Bug 终于有人做了 Subagent,TRAE 国内版 SOLO 模式来了 科技爱好者周刊(第 374 期):6GHz 的问题 VS Code 使用国产大模型 MiniMax M2 教程 科技爱好者周刊(第 373 期):数据模型是新产品的核心 国产大模型接入 Claude Code 教程:以 Doubao-Seed-Code 为例 科技爱好者周刊(第 372 期):软件界面如何设计 大模型比拼:MiniMax M2 vs GLM 4.6 vs Claude Sonnet 4.5 科技爱好者周刊(第 371 期):一个乐观主义者的专访 科技爱好者周刊(第 370 期):正确的代码高亮 错误处理:异常好于状态码 科技爱好者周刊(第 369 期):Tim 与罗永浩的对谈 科技爱好者周刊(第 368 期):不要这样管理软件团队 一天之内,智谱和 Anthropic 都发了最强编程模型 科技爱好者周刊(第 367 期):Nano Banana 的几个妙用 科技爱好者周刊(第 366 期):旧金山疯狂的 AI 广告 科技爱好者周刊(第 365 期):流量变现正在崩塌 科技爱好者周刊(第 364 期):最难还原的魔方 科技爱好者周刊(第 363 期):最好懂的神经网络解释 科技爱好者周刊(第 362 期):GitHub 工程师谈系统设计 科技爱好者周刊(第 361 期):暗网 Tor 安全吗?
RDF 和 SPARQL 初探:以維基數據為例
阮一峰 · 2020-02-23 · via 阮一峰的网络日志

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

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

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

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

一、RDF 的含義

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

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

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

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

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

天空是藍色的。

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

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

查詢一:天空 + 顏色 = ?

查詢二:? + 顏色 = 藍色

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

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

使用的時候,只要引用這個 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按鈕,看看官方提供的示例。

六、參考鏈接

(完)