- あなたの誕生日から3か月以内に死亡した人を選び、勝手に生まれ変わり前認定して表示します
- 生まれ変わり前の生まれ変わり前も検索します
本題
wikidataとは
- ざっくり言うと「Wikipediaのデータベース」版です
- RDFという形式でデータが構造化されています
- SPARQLと呼ばれるクエリ言語でデータ取得できます
RDFについて
- 主語、述語、目的語の3つの組み合わせで表現されるデータ構造になります
- 例えば「織田信長」の項目については、次のような構造で情報が保存されています
主語 | 述語 | 目的語 |
---|---|---|
Oda Nobunaga | instance of | human |
Oda Nobunaga | father | Oda Nobuhide |
Oda Nobunaga | mother | Dota Gozen |
- 上記表で目的語として挙げた項目について、それを主語としたデータもまた格納されています
主語 | 述語 | 目的語 |
---|---|---|
Oda Nobuhide | instance of | human |
Oda Nobuhide | father | Oda Nobusada |
SPARQLについて
- RDFからデータ取得するためのクエリ言語です
- SQLに似た構文です
- 主語・述語・目的語の各項目について、IDが振られています。そのIDを用いてクエリを構成します
- 織田信長の父親を問い合わせる簡単なクエリのサンプルは次のようになります
織田信長の父親を問い合わせるSPARQL
SELECT ?human
WHERE
{
wd:Q171411 wdt:P22 ?human.
}
-
wd:Q171411
が織田信長のID、wdt:P22
が述語「父親」のID、 ?で始まる?human
が変数になります。- selectで変数(?human)の値を取り出してます
-
wd:Q171411
を主語、wdt:P22
を述語とするレコードを探し、そのレコードの目的語が?human
に入ります
-
Wikidata Query Serviceで実際にwikidataにクエリを投げて、その結果を見ることができます。
-
主語を変数にすることもできます
SELECT ?human WHERE { ?human wdt:P22 wd:Q171411. }
- 信長を父親に持つ人たち、つまり信長の子供達がぞろぞろ取得できます。実行リンク
-
述語も変数にできます
- 次のクエリは織田信長と織田信秀の関係は?と聞いてるのと同じです。
SELECT ?rel WHERE { wd:Q171411 ?rel wd:Q1153962. }
-
複数条件も指定できます
SELECT ?human WHERE { ?human wdt:P22 wd:Q171411. ?human wdt:P21 wd:Q6581072 }
- 信長を父親に持つ、かつ性別が女性、の項目がhumanに格納されます
- 主語が同じ場合、
?human wdt:P22 wd:Q171411; wdt:P21 wd:Q6581072
というように;
を使って主語を省略することもできます。
-
他にもたくさん指定できますが、きりがないので割愛します
アプリを作ってみる
- 例として冒頭に張り付けた、「生まれかわり検索」を作ってみます
- 死亡年と誕生年が3か月以内の人を勝手に生まれ変わり認定します。
- 自分の誕生日を入力すると生まれ変わりを一覧表示します
まずクエリをWikidata Query Serviceで組み立ててみます。
-
例:誕生日2018-01-01から遡って1年以内に死んだ人たちを探すクエリ
- 誕生日部分は後で作るアプリで埋め込む想定で、まずはクエリのひな型を試行錯誤しながら作ります
SELECT ?id ?name ?deth_date ?article #1 WHERE { BIND("2018-01-01"^^xsd:date as ?birth_date) #2 ?id wdt:P570 ?deth_date; wdt:P1559 ?name. BIND(?birth_date - ?deth_date as ?diff) FILTER( ?diff > 0 && ?diff < 365) #3 OPTIONAL { #4 ?article schema:about wd:Q171411 . ?article schema:inLanguage "ja" . FILTER (SUBSTR(str(?article), 1, 25) = "https://ja.wikipedia.org/") } } ORDER BY ?diff LIMIT 100
-
この例は次のようなクエリです
- #1: Id, 人名、没年、wikipediaの記事を取得します。
- #2:
^^xsd:date
で文字列を日付に変換しています。 結果をBIND・ASで変数に格納します - #3: 誕生日と没年の差が1年未満のものでフィルタします
- #4: wikipediaの記事を取得します。ない場合もあるのでOPTIONALとしています
-
同様にして人のid(wd:Q171411)から仕事を問い合わせるクエリも作ってみます
SELECT ?jobLabel WHERE { wd:Q171411 wdt:P106 ?job. ?job rdfs:label ?jobLabel #a SERVICE wikibase:label { bd:serviceParam wikibase:language "ja". } #b } LIMIT 1
-
- #a: IDからラベルへ変換します
- #b: ラベル名の対象として日本語のみとします
アプリを書く
- 無理にcopdepenに実装しました。
- Qiitaに埋め込んでしまいたいからですがダメでした(;w;)
- どうもQiitaのサポートしない構文のせいみたいです
- 素直にやるならgithub-pagesがいいと思います
- Qiitaに埋め込んでしまいたいからですがダメでした(;w;)
解説
-
APIエンドポイントは
https://query.wikidata.org/sparql?format=json&query=${query文字列}
です- これに対してfetch APIでデータ取得します
- 結果は次のようなjsonになります
{ "head" : { "vars" : [ "id", "name", "deth_date", "article" ] }, "results" : { "bindings" : [ { "article" : { "type" : "uri", "value" : "https://ja.wikipedia.org/wiki/%E7%B9%94%E7%94%B0%E4%BF%A1%E9%95%B7" }, "id" : { "type" : "uri", "value" : "http://www.wikidata.org/entity/Q11634158" }, "name" : { "xml:lang" : "ja", "type" : "literal", "value" : "豊田 達郎" }, "deth_date" : { "datatype" : "http://www.w3.org/2001/XMLSchema#dateTime", "type" : "literal", "value" : "2017-12-30T00:00:00Z" } }, { ...
-
Wikidataのクエリは30秒以上かかるとタイムアウトでエラーになります
- クエリを細かく分割して投げるといいと思います
- 生まれ変わり検索では人物情報を取得するクエリを投げた後に、職業を問い合わせるクエリを投げています
- 1度に問い合わせるとタイムアウトするためです
-
その他同時アクセスは3つまで等の制限があります
- 今回はクライアントで動作するので特に問題になりませんでした
- 再帰関数で1並列で呼び出しています
- 今回はクライアントで動作するので特に問題になりませんでした
-
ちょっと複雑なクエリを投げるとWikidataはかなり重いのですが、これはどうしようもなさそうです