Wikidata:Tools/OpenRefine/Editing/Tutorials/Working with APIs/ja
このチュートリアルでは、私たちはウェブAPIから情報を抽出してウィキデータにアップロードします。APIはウェブサイトの機械可読なバージョンで、このウェブサイトからの情報の抽出を容易にします。
始めてみよう
ORCIDは学術論文の著者に一意の識別子を提供しているイニシアチブです。ORCID上には誰でも自分のプロファイルを作って、オーサリングリサーチ記事やその他の人工物に使うことができる一意の識別子を取得することができます。ウィキデータでは、ORCID iD (P496)プロパティはこれらのプロファイルにリンクするのに使え、これは研究者の雇用者についての情報をしばしば含んでいます。例えば、本校執筆時点でAndreas Küttel (Q289574)には所属がありませんが、 https://orcid.org/0000-0003-0235-3590 にリンクしていて、Employmentの記録をリストしています。私たちのゴールはこの情報をウィキデータにインポートすることです。
最初に、小さなSPARQLクエリを書いて、ウィキデータ項目に所属は無いが、ORCID iD (P496)のある研究者を探してみましょう。
SELECT ?researcher ?orcid WHERE {
?researcher wdt:P31 wd:Q5;
wdt:P496 ?orcid.
FILTER NOT EXISTS { ?researcher wdt:P108 ?institution }
}
LIMIT 200
このクエリを実行してその結果を表としてダウンロードしてください。それを元にOpenRefineのプロジェクトを作成します(インポートツール用の標準的な設定が適切です)。最初のカラムにはウィキデータ項目のURIが含まれています。現在のところ、 OpenRefineはこれを任意のその他URLとして扱います: これらは技術的にはウィキデータには照合されません。これらのセルに照合済みのステータスを与えるには、このカラムで照合を実施してください。きっと通常より顕著に早く終わるでしょう(検索は行われないため - QIDは単に検証されて返却されるだけです)。[1]
APIにクエリを投げる
ORCIDは一般向けにAPIを提供していて、登録なしにORCIDプロファイルの一般向けコンテンツを参照できます。クイック・チュートリアルでAPIの動作が説明されています。APIに関するORCIDの説明を全て理解する必要はありません - ここでは必要のない高度な機能もカバーしているため、そのガイドはとても複雑です。私たちが知るべきことは、所属情報を構造化された形式で取得するためにはどのURLを参照すれば良いか、ということだけです。Employmentセクションのコンテンツを参照するには、私たちは次のアドレスを呼び出す必要があり: "https://pub.orcid.org/v2.1/"+value+"/employments"
、そこでは+value+
がその人物のORCID識別子で置き換えられています。デフォルトで、これはXML形式のレスポンスを返しますが、HTTPヘッダAccept: application/json
を追加して、JSONを返すように依頼することができます。これはOpenRefineで行えます。ORCIDカラムでEdit column(カラム編集) → Add column by fetching URLs(URLでカラムを追加)と操作します:
JSONレスポンスからの情報抽出
操作が終わると、新しいカラムにJSONレスポンスが取得されています。私たちはここから情報を取得する必要があります。各レスポンスは雇用者のリストを含んでいることがあるので、最初のステップはOpenRefineのレコードモデルに従って、別々のライン上の各雇用者用のサブ-オブジェクトを抽出することです。残念ながら、これはややエレガントでないプロセスです:
- 私たちは最初に、Add column based on this column(このカラムに基づいてカラムを追加)の操作(employmentsカラムのEdit column(カラム編集)メニュー内)で次の表現を使って、JSONペイロードをパースして雇用者のリストを抽出する必要があります:
value.parseJson()["employment-summary"].join('###')
- 次に、私たちはSplit multi-valued cells(多値のセルを分割)操作 (新しいカラムのEdit cells(セル編集)メニュー内)を使って、各行にあるオブジェクトを取得するために、同じセパレータ
###
を指定します。
これを入手したら、再度parseJson()
を使ってこのオブジェクトから情報を抽出できます。次のフィールドから (再度Add column based on this column(このカラムに基づいてカラムを追加)操作を使って)抽出します:[2]
- Organization nameは
value.parseJson()["organization"]["name"]
で
- Organization countryは
value.parseJson()["organization"]["address"]["country"]
で
- Organization identifierは
value.parseJson()["organization"]["disambiguated-organization"]["disambiguated-organization-identifier"]
で
- Start date of the positionは
value.parseJson()["start-date"]["year"]["value"]
で
- End date of the positionは
value.parseJson()["end-date"]["year"]["value"]
で
- (練習: 地位のタイトルも抽出できます。position held (P39)で修飾子として追加できます。)。
これで私たちはJSONカラムを破棄してその他を並び替えることができます。これは、ルートカラムにあるAll(全て)メニューのEdit columns(カラム編集)の下で使えるRe-order / remove columns(カラムの並び替え・削除)操作を使って簡単に行えます。こんな感じで取得できます:
組織の照合
私たちの研究者は既に照合済みです。というのも私たちはSPARQLクエリで取得したからです。しかしながら、組織については曖昧さを回避する必要があります。私たちはこれらを名前で照合できますが、これらの名前はしばしば曖昧なので、それだけでは不十分です。2つのパラメータでより精度を上げることができます: 国と識別子。
- ORCIDはそのISO 3166-1 alpha-2 code (P297)で国を表します。しかしながら、このプロパティはその組織の項目ではなく、そのcountry (P17)で見つけられるので、照合ダイアログ内でISO 3166-1 alpha-2 code (P297)だけを使うことはできません。項目から国コードを取得するには2ホップ必要です。ウィキデータの照合サービスはそのための文法を提供しています(SPARQLインスパイア):
P17/P297
.
- JSONペイロードから取り出した識別子は必ずしも同じ種類の識別子ではありません。たいていはRinggold ID (P3500)ですが、GRID ID (P2427)や DOI (P356) (ウィキデータでは実際はOpen Funder Registry funder ID (P3153)としてストア)の場合もあります。ORCIDはその公開する識別子の型を提供しているので、私たちはそれを使ってそれらを別のカラムに分離し、各カラムを照合ダイアログ内で使えるでしょう。今回、私たちは代わりにトリックを使います: これらの識別子は非互換だと分かっているので、直接照合インタフェースに問い合わせて、識別子を取り出します。これは論理和を取り込む
|
を使って行えます:P3500|P2427
は両プロパティの結合に一致するでしょう。[3] 次のような照合ダイアログになります:
スキーマの作成
いったん照合が終わると、私たちはスキーマを作成することができます。そのような主張の期待される構造について知りたければemployer (P108)に相談してください。次のようにするとできるようです:
これでもう終わったわけではありません。私たちの表にはレコード構造があります: researcherカラムに空のセルがいくつかあります(研究者がプロファイル上に複数の雇用者を持っている場合)。なぜならスキーマの評価は行型で機能し、対応する文は現在スキップされています (その主題が空なので)。これを修正するには、Fill down(下方向の空白を埋める)コマンド(researcherカラムのEdit cells(セル編集)メニューより)が使えます。これは値をレコードの縦方向に広げてくれます。これは"orcid"カラムでも行うべきです。なぜならこれらの値は情報源で使われているからです。これが終わったら、Previewタブで編集結果をプレビューして、右上隅のWikidataメニューのUpload edits to Wikidataオプションでアップロードすることができます。あなたはこちら や あちら のような編集を得るでしょう。これらの編集は同時に複数の文を取り込むので、その自動編集のサマリーには情報が少ないため、それを補うための意味のあるサマリーを指定するのは重要だ、ということに注意してください。
何をやったのか?
私たちはわずか200名の研究者用にかなりうんざりするようなプロセスを進めてきました。しかし、良いお知らせは、次回はこれを全てやる必要は無いということです。 OpenRefineの undo/redo タブは他のソフトウェアより少し高度です。あなたのプロジェクトを任意の中間状態にロールバックできるだけでなく、操作をJSONオブジェクトとして抽出し、これらの操作を新しいプロジェクトに再度適用することができます。このため、SPARQLクエリを(おそらくはより上限を上げて)再実行する場合は、その新しいプロジェクトに自分の編集履歴を適用することができます。あなたがOpenRefineを自分の編集を直接実行するのに使っている場合はそれも操作に含まれるので、OpenRefineはそれも同様に実行します。 おめでとうございます!あなたはコードを1行も書かずに高度なウィキデータbotを仕上げました。
下記はOpenRefine編集履歴のサンプルで、SPARQLクエリの結果に適用して同じ結果を得ることができます:
Extended content |
---|
[
{
"op": "core/recon",
"description": "Reconcile cells in column researcher to type Q5",
"columnName": "researcher",
"config": {
"mode": "standard-service",
"service": "https://wikidata.reconci.link/en/api",
"identifierSpace": "http://www.wikidata.org/entity/",
"schemaSpace": "http://www.wikidata.org/prop/direct/",
"type": {
"id": "Q5",
"name": "human"
},
"autoMatch": true,
"columnDetails": [],
"limit": 0
},
"engineConfig": {
"mode": "row-based",
"facets": []
}
},
{
"op": "core/column-addition-by-fetching-urls",
"description": "Create column employments at index 2 by fetching URLs based on column orcid using expression grel:\"https://pub.orcid.org/\"+value+\"/employments\"",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "employments",
"columnInsertIndex": 2,
"baseColumnName": "orcid",
"urlExpression": "grel:\"https://pub.orcid.org/v2.1/\"+value+\"/employments\"",
"onError": "set-to-blank",
"delay": 100,
"cacheResponses": true,
"httpHeadersJson": [
{
"name": "authorization",
"value": ""
},
{
"name": "user-agent",
"value": "OpenRefine 2.8 [TRUNK]"
},
{
"name": "accept",
"value": "application/json"
}
]
},
{
"op": "core/column-addition",
"description": "Create column employment_record at index 3 based on column employments using expression grel:value.parseJson()[\"employment-summary\"].join('###')",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "employment_record",
"columnInsertIndex": 3,
"baseColumnName": "employments",
"expression": "grel:value.parseJson()[\"employment-summary\"].join('###')",
"onError": "set-to-blank"
},
{
"op": "core/column-removal",
"description": "Remove column employments",
"columnName": "employments"
},
{
"op": "core/multivalued-cell-split",
"description": "Split multi-valued cells in column employment_record",
"columnName": "employment_record",
"keyColumnName": "researcher",
"mode": "separator",
"separator": "###",
"regex": false
},
{
"op": "core/column-addition",
"description": "Create column end_date at index 3 based on column employment_record using expression grel:value.parseJson()[\"end-date\"][\"year\"][\"value\"]+if(value.parseJson()[\"end-date\"][\"month\"] != null, \"-\"+value.parseJson()[\"end-date\"][\"month\"][\"value\"], \"\")",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "end_date",
"columnInsertIndex": 3,
"baseColumnName": "employment_record",
"expression": "grel:value.parseJson()[\"end-date\"][\"year\"][\"value\"]+if(value.parseJson()[\"end-date\"][\"month\"] != null, \"-\"+value.parseJson()[\"end-date\"][\"month\"][\"value\"], \"\")",
"onError": "set-to-blank"
},
{
"op": "core/column-addition",
"description": "Create column start_date at index 3 based on column employment_record using expression grel:value.parseJson()[\"start-date\"][\"year\"][\"value\"]+if(value.parseJson()[\"start-date\"][\"month\"] != null, \"-\"+value.parseJson()[\"start-date\"][\"month\"][\"value\"], \"\")",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "start_date",
"columnInsertIndex": 3,
"baseColumnName": "employment_record",
"expression": "grel:value.parseJson()[\"start-date\"][\"year\"][\"value\"]+if(value.parseJson()[\"start-date\"][\"month\"] != null, \"-\"+value.parseJson()[\"start-date\"][\"month\"][\"value\"], \"\")",
"onError": "set-to-blank"
},
{
"op": "core/column-addition",
"description": "Create column organization_id at index 3 based on column employment_record using expression grel:value.parseJson()[\"organization\"][\"disambiguated-organization\"][\"disambiguated-organization-identifier\"]",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "organization_id",
"columnInsertIndex": 3,
"baseColumnName": "employment_record",
"expression": "grel:value.parseJson()[\"organization\"][\"disambiguated-organization\"][\"disambiguated-organization-identifier\"]",
"onError": "set-to-blank"
},
{
"op": "core/column-addition",
"description": "Create column organization_country at index 3 based on column employment_record using expression grel:value.parseJson()[\"organization\"][\"address\"][\"country\"]",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "organization_country",
"columnInsertIndex": 3,
"baseColumnName": "employment_record",
"expression": "grel:value.parseJson()[\"organization\"][\"address\"][\"country\"]",
"onError": "set-to-blank"
},
{
"op": "core/column-addition",
"description": "Create column organization_name at index 3 based on column employment_record using expression grel:value.parseJson()[\"organization\"][\"name\"]",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"newColumnName": "organization_name",
"columnInsertIndex": 3,
"baseColumnName": "employment_record",
"expression": "grel:value.parseJson()[\"organization\"][\"name\"]",
"onError": "set-to-blank"
},
{
"op": "core/column-removal",
"description": "Remove column employment_record",
"columnName": "employment_record"
},
{
"op": "core/recon",
"description": "Reconcile cells in column organization_name to type Q43229",
"columnName": "organization_name",
"config": {
"mode": "standard-service",
"service": "https://wikidata.reconci.link/en/api",
"identifierSpace": "http://www.wikidata.org/entity/",
"schemaSpace": "http://www.wikidata.org/prop/direct/",
"type": {
"id": "Q43229",
"name": "organization"
},
"autoMatch": true,
"columnDetails": [
{
"column": "organization_country",
"propertyName": "SPARQL: P17/P297",
"propertyID": "P17/P297"
},
{
"column": "organization_id",
"propertyName": "SPARQL: P3500|P2427",
"propertyID": "P3500|P2427"
}
],
"limit": 0
},
"engineConfig": {
"mode": "row-based",
"facets": []
}
},
{
"op": "core/fill-down",
"description": "Fill down cells in column researcher",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"columnName": "researcher"
},
{
"op": "wikidata/save-wikibase-schema",
"description": "Save Wikibase schema skeleton",
"schema": {
"itemDocuments": [
{
"subject": {
"type": "wbitemvariable",
"columnName": "researcher"
},
"statementGroups": [
{
"property": {
"datatype": "wikibase-item",
"pid": "P108",
"label": "employer",
"type": "wbpropconstant"
},
"statements": [
{
"references": [
{
"snaks": [
{
"prop": {
"datatype": "external-id",
"pid": "P496",
"label": "ORCID iD",
"type": "wbpropconstant"
},
"value": {
"type": "wbstringvariable",
"columnName": "orcid"
}
},
{
"prop": {
"datatype": "time",
"pid": "P813",
"label": "retrieved",
"type": "wbpropconstant"
},
"value": {
"type": "wbdateconstant",
"value": "TODAY"
}
}
]
}
],
"qualifiers": [
{
"prop": {
"datatype": "time",
"pid": "P580",
"label": "start time",
"type": "wbpropconstant"
},
"value": {
"type": "wbdatevariable",
"columnName": "start_date"
}
},
{
"prop": {
"datatype": "time",
"pid": "P582",
"label": "end time",
"type": "wbpropconstant"
},
"value": {
"type": "wbdatevariable",
"columnName": "end_date"
}
}
],
"value": {
"type": "wbitemvariable",
"columnName": "organization_name"
}
}
]
}
],
"nameDescs": []
}
]
}
},
{
"op": "core/fill-down",
"description": "Fill down cells in column orcid",
"engineConfig": {
"mode": "row-based",
"facets": []
},
"columnName": "orcid"
}
]
|
脚注
- ↑ これは一見時間の無駄のように思えます: この照合操作は些細なことに思えるでしょう。実際は、OpenRefineは各項目の情報をフェッチします: そのラベル、内部のストア型。そして
cell.recon.match.type
という表現でアクセスできます。 - ↑ 中間カラム(
value.parseJson()["organization"]
のような)を作成して、より短い表現を取得することができます。 - ↑ 単純化するために私たちはDOI (P356)は無視します。 なぜならその形式はウィキデータが使っているものとは一致しないからです。練習: 照合でも使えるようにこれらの値からDOIプレフィックスを除去しなさい。