内容が古くなっている可能性がありますのでご注意下さい。
せっかく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フォーマットの仕様についてはこちらが参考になります。
インポートを実行すると、以下のような項目が作成されます。

項目には「説明」を設定していないため、特別ページの説明がないエンティティを表示するとインポートした項目を一覧表示することができます。
