内容が古くなっている可能性がありますのでご注意下さい。
せっかくWikibaseの環境を構築しても中にデータが入っていなければ何の意味もありません。そこで、PHPのスクリプトを作成して、CSV形式の月待塔オープンデータをAPI経由でインポートすることにします。
ボット用パスワードの作成
APIを使用してWikibaseにアクセスする場合、通常のログインに使用するアカウントとパスワードではなく、アカウントに紐付いたボット名とボット用パスワードを使用します。
そこでまず、特別ページの「利用者と権限」にある「ボット用パスワード」を開きます。
ボット名を入力して[作成]ボタンをクリックします。
編集関係の権限を付与して[作成]ボタンをクリックすると、作成されたボット用パスワードが表示されます。
インポートプログラムの作成
メインのプログラムは以下のとおりです。
<?php require('login.php'); require('util.php'); mb_internal_encoding("UTF-8"); setlocale(LC_CTYPE, 'C'); $endPoint = "https://wiki.midoriit.com/api.php"; $login_Token = getLoginToken(); $login = loginRequest( $login_Token ); echo "Login: ".$login."\n"; if( $login == "Success" ) { if (($csv = fopen("https://raw.githubusercontent.com/midoriit/tsukimachito/master/tsukimachito.csv", "r")) !== FALSE) { $row = fgetcsv($csv); //ヘッダ行読み捨て $token = getEditToken(); // 編集用トークン取得 while ($row = fgetcsv($csv)) { if( $id = wbSearchEntities($row[0]) ) { echo $row[0]." skip( ".$id." )\n"; } else { $id = wbEditEntity( $row, $token ); if( $id < 0 ) { exit(1); } echo $row[0]." create( ".$id." )\n"; } } fclose($csv); } else { echo "Failed to open 'tsukimachito.csv'\n"; } } unlink( "cookie.txt" ); ?>
requireで読み込む login.php と util.php については後で解説します。
setlocale(LC_CTYPE, 'C'); は、Windows環境において fgetcsv() が日本語を正しく扱えるようにするためのものです。詳しくはこちらを参照して下さい。
login.php で定義された以下の2つの関数を呼び出してWikibaseにログインします。APIの種類によっては、操作をする前にトークンを取得をする必要があります。
$login_Token = getLoginToken(); $login = loginRequest( $login_Token );
ログインに成功したら、GitHub上にある月待塔オープンデータのCSVファイルを開き、util.php で定義した getEditToken() 関数で編集用トークンを取得しておきます。
CSVファイルを1行ずつ読み込みながら、wbSearchEntities() で既に項目が存在するかどうかを確認し、存在しなければ wbEditEntity() で項目を登録します。CSVファイルの1行がWikibaseにおける1つの項目となります。また、CSVファイルの1カラム目のIDは、Wikibaseの項目のラベルになります。
最後の unlink() では、トークンの取得時に作成したクッキーを削除しています。
login.php は、こちらにあるMITライセンスのサンプルコードを修正して利用します。
<?php /* login.php MediaWiki API Demos Demo of `Login` module: Sending post request to login MIT license */ // Step 1: GET Request to fetch login token function getLoginToken() { global $endPoint; $params1 = [ "action" => "query", "meta" => "tokens", "type" => "login", "format" => "json" ]; $url = $endPoint . "?" . http_build_query( $params1 ); $ch = curl_init( $url ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); curl_setopt( $ch, CURLOPT_COOKIEJAR, "cookie.txt" ); curl_setopt( $ch, CURLOPT_COOKIEFILE, "cookie.txt" ); $output = curl_exec( $ch ); curl_close( $ch ); $result = json_decode( $output, true ); return $result["query"]["tokens"]["logintoken"]; } // Step 2: POST Request to log in. Use of main account for login is not // supported. Obtain credentials via Special:BotPasswords // (https://www.mediawiki.org/wiki/Special:BotPasswords) for lgname & lgpassword function loginRequest( $logintoken ) { global $endPoint; $params2 = [ "action" => "login", "lgname" => "ボット名", "lgpassword" => "ボット用パスワード", "lgtoken" => $logintoken, "format" => "json" ]; $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, $endPoint ); curl_setopt( $ch, CURLOPT_POST, true ); curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query( $params2 ) ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); curl_setopt( $ch, CURLOPT_COOKIEJAR, "cookie.txt" ); curl_setopt( $ch, CURLOPT_COOKIEFILE, "cookie.txt" ); $output = curl_exec( $ch ); curl_close( $ch ); $result = json_decode( $output, true ); return $result["login"]["result"]; } ?>
lgname と lgpassword はボット名とボット用パスワードを設定します。loginRequest() は処理結果を表す文字列を返すように変更しました。
トークン取得APIではHTTPのGETメソッドを使用し、ログインAPIではPOSTメソッドを使用しています。
util.php では、編集用トークンを取得する getEditToken()、データを検索する wbSearchEntities()、データを登録する wbEditEntity() とその補助関数を定義しています。
<?php // getEditToken function getEditToken() { global $endPoint; $params = [ "action" => "query", "meta" => "tokens", "type" => "csrf", "format" => "json" ]; $url = $endPoint . "?" . http_build_query( $params ); $ch = curl_init( $url ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); curl_setopt( $ch, CURLOPT_COOKIEJAR, "cookie.txt" ); curl_setopt( $ch, CURLOPT_COOKIEFILE, "cookie.txt" ); $output = curl_exec( $ch ); curl_close( $ch ); $result = json_decode( $output, true ); return $result["query"]["tokens"]["csrftoken"]; } // wbSearchEntities function wbSearchEntities($id) { global $endPoint; $params = [ "action" => "wbsearchentities", "language" => "ja", "search" => $id, "format" => "json" ]; $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, $endPoint ); curl_setopt( $ch, CURLOPT_POST, true ); curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query( $params ) ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); curl_setopt( $ch, CURLOPT_COOKIEJAR, "cookie.txt" ); curl_setopt( $ch, CURLOPT_COOKIEFILE, "cookie.txt" ); $output = curl_exec( $ch ); curl_close( $ch ); $result = json_decode( $output, true ); if( count($result["search"]) > 0 ) { return $result["search"][0]["id"]; } } // wbEditEntity function wbEditEntity( $row, $token ) { global $endPoint; $claims = buildClaims( $row ); $data = '{"labels":{"ja":{"language":"ja","value":"'.$row[0].'"}},"claims":'.$claims.'}'; $params = [ "action" => "wbeditentity", "new" => "item", "data" => $data, "token" => $token, "format" => "json" ]; $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, $endPoint ); curl_setopt( $ch, CURLOPT_POST, true ); curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query( $params ) ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); curl_setopt( $ch, CURLOPT_COOKIEJAR, "cookie.txt" ); curl_setopt( $ch, CURLOPT_COOKIEFILE, "cookie.txt" ); $output = curl_exec( $ch ); curl_close( $ch ); $result = json_decode( $output, true ); if(array_key_exists("entity", $result)) { return $result["entity"]["id"]; } else { var_dump($result); return -1; } } // buildClaims function buildClaims( $row ) { // $row[0] -> ツイートID (P4) $claims = buildClaimSub( "P4", substr($row[0], 1) ); // $row[1] -> ツイート日 (P5) $date = new DateTime($row[1]); $iso8601 = substr($date->format(DateTime::ATOM), 0, 19); $claims = $claims.','.buildClaimSub2( "P5", "+".$iso8601."Z", 11 ); // $row[2] -> Twitterアカウント (P6) $claims = $claims.','.buildClaimSub( "P6", $row[2] ); // $row[3],$row[4] -> 緯度経度 (P7) $claims = $claims.','.buildClaimSub3( "P7", $row[3], $row[4] ); // $row[5] -> ツイートURL (P8) $claims = $claims.','.buildClaimSub( "P8", $row[5] ); // $row[6]~$row[9] -> 画像URL (P9) for( $i=0 ; $i<4 ; $i++ ) { if( $row[6+$i] ) { $claims = $claims.','.buildClaimSub( "P9", $row[6+$i] ); } else { break; } } // $row[10] -> 分類 (P1) $types = explode(" ", $row[10]); foreach ($types as $type) { switch( $type ) { case '三日月塔': $q = 3; break; case '(二十)三夜塔': $q = 23; break; case '七夜待塔': $q = 7; break; case '十三夜塔': $q = 13; break; case '十四夜塔': $q = 14; break; case '十五夜塔': $q = 15; break; case '十六夜塔': $q = 16; break; case '十七夜塔': $q = 17; break; case '十八夜塔': $q = 18; break; case '十九夜塔': $q = 19; break; case '二十夜塔': $q = 20; break; case '二十一夜塔': $q = 21; break; case '二十二夜塔': $q = 22; break; case '二十三夜塔': $q = 23; break; case '二十四夜塔': $q = 24; break; case '二十五夜塔': $q = 25; break; case '二十六夜塔': $q = 26; break; case '二十七夜塔': $q = 27; break; case '二十八夜塔': $q = 28; break; default : $q = 0; } if( $q != 0 ) { $claims = $claims.','.buildClaimSub4( "P1", $q ); } } // $row[11] -> 造立年(和暦)(P17) if( $row[11] ) { $claims = $claims.','.buildClaimSub( "P17", $row[11] ); } // $row[12] -> 造立年(西暦)(P18) if( $row[12] ) { $claims = $claims.','.buildClaimSub( "P18", $row[12] ); } // $row[13] -> 所在地 (P15) if( $row[13] ) { $claims = $claims.','.buildClaimSub( "P15", $row[13] ); } // $row[14] -> 場所 (P16) if( $row[14] ) { $claims = $claims.','.buildClaimSub( "P16", $row[14] ); } return "[".$claims."]"; } // buildClaimSub(プロパティ, 値) function buildClaimSub( $prop, $value ) { return '{"mainsnak":{"snaktype":"value","property":"'.$prop.'","datavalue":{"value":"'.$value.'","type":"string"}},"type":"statement","rank":"normal"}'; } // buildClaimSub2(プロパティ, 日付, 精度) function buildClaimSub2( $prop, $date, $precision ) { return '{"mainsnak":{"snaktype":"value","property":"'.$prop.'","datavalue":{"value":{"time":"'.$date.'","timezone": 0,"before": 0,"after": 0,"precision":'.$precision.',"calendarmodel": "http:\/\/www.wikidata.org\/entity\/Q1985727"},"type":"time"}},"type":"statement","rank":"normal"}'; } // buildClaimSub3(プロパティ, 緯度, 経度) function buildClaimSub3( $prop, $lat, $lon ) { return '{"mainsnak":{"snaktype":"value","property":"'.$prop.'","datavalue":{"value":{"latitude":'.$lat.',"longitude":'.$lon.',"precision":0.000001},"type":"globecoordinate"}},"type":"statement","rank":"normal"}'; } // buildClaimSub4(プロパティ, 項目) function buildClaimSub4( $prop, $item ) { return '{"mainsnak":{"snaktype":"value","property":"'.$prop.'","datavalue":{"value":{"entity-type":"item","numeric-id":'.$item.'},"type":"wikibase-entityid"}},"type":"statement","rank":"normal"}'; } ?>
getEditToken() の内容は getLoginToken() とほぼ同様ですが、取得するトークンはCSRFトークンになります。
wbSearchEntities() も同様にAPIを呼び出し、結果を取得して返します。
データを登録する wbEditeEntity() 関数は、Wikibaseに登録する内容を指定するJSON形式のデータを作成し、APIを呼び出します。
登録内容はラベルと文から成ります。ラベルは
{"labels":{"ja":{"language":"ja","value":"ラベル"}}
と指定します。
文を登録するためのJSONはやや複雑なため、buildClaims() 関数とその補助関数を用いて作成します。
補助関数は、プロパティに対する値の型によって、文字列用の buildClaimSub()、日付用の buildClaimSub2()、緯度経度用の buildClaimSub3()、項目用の buildClaimSub4() を用意しました。型ごとのJSONフォーマットの仕様についてはこちらが参考になります。
インポートを実行すると、以下のような項目が作成されます。
項目には「説明」を設定していないため、特別ページの説明がないエンティティを表示するとインポートした項目を一覧表示することができます。