1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【初心者向け】国立国会図書館のLinked Open Dataからデータを抽出してみた

Last updated at Posted at 2025-04-03

先日SPARQLで国立国会図書館のLinked Open Data (LOP)からデータを抽出する機会がありました。
個人的に面白いと思ったのですが、SPARQL初心者で今後使わないと忘れてしまいそうなので、自分の備忘録用、また初めて国立国会図書館LOPを利用する方向けに書きました。

本記事は以下の前提で書いています。

  • SQLの基本的な書き方は抑えている
  • Linked Dataについてはすでに知っている

国立国会図書館のLinked Open Dataとは

  • 国立国会図書館が提供する書籍や記事論文などのメタデータをもとに提供している
  • 1つのリソースに対して↓のような情報が与えられている
    (例)カルボン酸:https://id.ndl.go.jp/auth/ndlsh/00565061

実行したクエリ

<抽出したかった条件>
skos:inScheme=Topical Termsuri, xl:prefLabel, xl:altLabel, skos:broaderの一覧を抽出したい

sqarql
PREFIX xl: <http://www.w3.org/2008/05/skos-xl#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?uri ?label ?altlabel ?broaderlabel WHERE {
      ?uri xl:prefLabel [ xl:literalForm ?label ].
      ?uri xl:altLabel [ xl:literalForm ?altlabel ].
      OPTIONAL {
        ?uri skos:broader [ rdfs:label ?broaderlabel].
      }
      ?uri skos:inScheme <http://id.ndl.go.jp/auth#topicalTerms> .
}

混乱したこと

私はこちらのサイト上でクエリ実行をしていたのですが
https://id.ndl.go.jp/information/sparql/
COUNTやサブクエリが使えず「クエリが間違ってるのかな」と悪戦苦闘していました。
しかしこれはSPARQL1.0を使用していたことが原因でした(汗)
SPARQL1.0: https://id.ndl.go.jp/information/sparql/
SPARQL1.1: https://id.ndl.go.jp/information/sparql-11/

SPARQL1.1では実行できるので、正しい方のリンクになっているか確認してから実行しましょう。

クエリの詳細

PREFIX: 省略形を定義するところ

sqarql
PREFIX xl: <http://www.w3.org/2008/05/skos-xl#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
  • xl=http://www.w3.org/2008/05/skos-xl#ということ。
    SELECTの中でURLを記載してもいいが、PREFIXに記載しておくことでxlと記載するだけで呼び出すことができる。
  • リンクはNamespaceドキュメントで、例えばリソースのページに記載のあるxl:prefLabelとは何か?が定義されているところ。

SELECT文の基本的なルール

sqarql
SELECT ?uri ?label ?altlabel ?broaderlabel WHERE {
      ?uri xl:prefLabel [ xl:literalForm ?label ].
      ?uri xl:altLabel [ xl:literalForm ?altlabel ].
      OPTIONAL {
        ?uri skos:broader [ rdfs:label ?broaderlabel].
      }
      ?uri skos:inScheme <http://id.ndl.go.jp/auth#topicalTerms> .
}
  • SPARQLでは?で始まるものが変数と呼ばれる
  • SELECTの後はSQL同様、どんな項目を表示したいかを記載する
  • SELECT * とした場合は定義した変数全てが表示される
  • WHERE以降は主語-述語-目的語(トリプルという)の順番で記載する
    例)?uri skos:inScheme <http://id.ndl.go.jp/auth#topicalTerms>
    主語: ?uri
    述語: skos:inScheme
    目的語: http://id.ndl.go.jp/auth#topicalTerms
    (=?uriskos:inSchemeというプロパティを持ち、その値がtopicalTerms)と解釈できる
  • トリプルの終わりには.をつける(OPTIONALなど}で終わる場合はつけなくてもエラーにはならない)
  • []はブランクノードといい省略形(例でみた方がわかりやすいです)
    例)?uri skos:broader [ rdfs:label ?broaderlabel]は以下と同義
    sparql
    ?uri skos:broader ?broader
    ?broader rdfs:label ?broaderlabel
    
  • OPTIONALはあれば表示する、なければ表示しなくてもいい条件を記載する際に用いる

参考にした資料・動画

【番外編】SPARQLWrapperを使った場合

私はpythonを主に使うので、SPARQLWrapperというライブラリを使ってjupyterでもデータを抽出しました。一応その時に使ったコードも記載しておきます。

python
from SPARQLWrapper import SPARQLWrapper, JSON
import pandas as pd

# SPARQLエンドポイント
sparql = SPARQLWrapper("http://id.ndl.go.jp/auth/ndla/sparql")  # NDLのエンドポイント
sparql.setReturnFormat(JSON) # 今回はJSONからデータフレームに整形

# クエリテンプレート
query_template = """
PREFIX xl: <http://www.w3.org/2008/05/skos-xl#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?uri ?label ?altlabel ?broaderlabel WHERE {
      ?uri xl:prefLabel [ xl:literalForm ?label ].
      ?uri xl:altLabel [ xl:literalForm ?altlabel ].
      OPTIONAL {
        ?uri skos:broader [ rdfs:label ?broaderlabel].
      }
      ?uri skos:inScheme <http://id.ndl.go.jp/auth#topicalTerms> .
}
"""

# データ取得
offset = 0
all_results = []

while True:
    # クエリを設定
    sparql.setQuery(query_template + f" OFFSET {offset} LIMIT 1000")  # 1回で1000件まで取得できるので1000件ずつ取得する

    # クエリ実行
    results = sparql.query().convert()

    bindings = results["results"]["bindings"]
    
    if len(bindings) == 0:
        break  # これ以上データがない場合、ループを終了
    
    all_results.extend(bindings)
    offset += 1000  # 次の1000件を取得
    print(f'offset: {offset}')

# データをPandasデータフレームに変換
df = pd.DataFrame([
    {
        "uri": res["uri"]["value"],
        "label": res["label"]["value"],
        "altlabel": res.get("altlabel", {}).get("value", None),
        "broaderlabel": res.get("broaderlabel", {}).get("value", None) if "broaderlabel" in res else None,
    }
    for res in all_results
])

# 取得件数を表示
print(f"取得件数: {len(df)}")
df.head()
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?