用Neo4j撈取資料的一些小技巧:以SWAPI為例

在網路上已經有許多neo4j匯入外部資料的教學影片,這裡嘗試分享一些Cypher的撰寫技巧,用以解決幾個匯入時會遇到的小問題。

SWAPI 是 (:Starwars Fan :ProgrammingGeek) 應該都熟知的API,它提供不少星戰電影中的資訊(雖然不是最多...只有正史、而且只到第七部曲):例如Planets(行星)、Spaceships(太空船)、Vehicles(太空船之外的交通工具)、People(人物)、Films(影片)、以及Species(種族)等等。

常見的撈取資料寫法大致像是這樣:
WITH 'https://swapi.co/api/people/?format=json' AS url
CALL apoc.load.json(url) YIELD value
UNWIND value.results AS p
CREATE (n:StarWarsPerson)
SET n.name=p.name, n.height=p.height, n.mass=p.mass...
RETURN n;

問題一、API一次只給一部份資料

不過這裡遇到一個問題,許多API都不會一次給你所有的資料,以SWAPI為例,資料庫明明有87個人,但若不指定號碼的話,它只會吐給你前十個;除非你指定人物編號、一個一個撈出來,例如
WITH 'https://swapi.co/api/people/88/?format=json' AS url
CALL apoc.load.json(url) YIELD value AS p
CREATE (n:StarWarsPerson)
SET n.name=p.name, n.height=p.height, n.mass=p.mass
RETURN n;
指定88號,我們得到了Captain Phasma

除非運用programming language API(例如Java API),否則要一個一個撈還挺煩的。不過畢竟Neo4j還是有一點迴圈的能力,所以如果我們用下面這個小技巧,以動態產生網址的方式就可以解決掉第一個問題:
UNWIND range(1,88) AS i
CALL apoc.load.json('https://swapi.co/api/people/'+i+'/?format=json')
YIELD value AS p
CREATE (n:StarWarsPerson)
SET n.name=p.name, n.height=p.height, n.mass=p.mass
RETURN n;

問題二、資料跳號的問題

不過大家有沒有發現,資料庫有87個人,為什麼我這裡寫88呢?若你執行上面的程式碼應該會發現,會出現錯誤訊息,原來第17號被跳掉了,若你指定:
https://swapi.co/api/people/17/?format=json
將會得到
{"detail":"Not found"}
這也是撈取資料時常見的問題,因此必須加上一行來過濾:
UNWIND range(1,88) AS i
WITH i WHERE i<>17
CALL apoc.load.json('https://swapi.co/api/people/'+i+'/?format=json')
YIELD value AS p
CREATE (n:StarWarsPerson)
SET n.name=p.name, n.height=p.height, n.mass=p.mass
RETURN n;
如果跳掉的不只一個號碼,可能就要改用NOT...IN的語法比較方便。

問題三、殺手技—自動撈全部欄位

最後一個技巧,也是最重要的一個,就是撈取的資料內容欄位很豐富,但是如果我得一個一個寫成SET指定不是很麻煩?像是SWAPI的人物,提供的欄位就有很多,例如["films", "homeworld", "gender", "skin_color", "edited", "created", "mass", "vehicles", "url", "hair_color", "birth_year", "eye_color", "species", "starships", "name", "height"]等等(上面我舉例時,寫了三個就累了😅)。能不能不管三七二十一,先自動全撈進來再說?

這裡有個簡單的寫法可以辦到這件事,就是使用apoc.create.node
UNWIND range(1,88) AS i
WITH i WHERE i<>17
CALL apoc.load.json('https://swapi.co/api/people/'+i+'/?format=json')
YIELD value
CALL apoc.create.node(['StarWarsPerson'], value) YIELD node
RETURN node;

這樣一來,只要不是太複雜的資料型態(例如map,因為neo4j的屬性不允許放一個map進去),都可以用這種方式來撈取。

經過以上的討論,我們順利把SWAPI中87個星際大戰的人物(Well,有些其實是robot)一口氣撈進Neo4j資料庫中,是不是很簡單呢?

註:原先的API位置已經失效,新的位置為 https://swapi.dev,不過有一點很奇怪的是,資料庫裡的人數減少了,變為82人,但不變的是17號依舊跳號。因此以上程式碼中的88須改為83才能正確執行。(04/25/2020)


留言

這個網誌中的熱門文章

病毒肆虐下、資料集間國名比對的聯想

關於「複合形式」(摘錄自作品首演文件)

以圖形資料建構Star Wars星戰宇宙中的行星們