目的
Wikipediaから情報をスクレイピングする際の代替手段として、"DBpedia Japanese"があります。
このデータベースへのアクセスには、SQLライクな言語**"SPARQL(スパークル)"**を用いなければならず、記法について学習が必要でした。
本記事の目的は、SPARQLを用いたRDFデータベースからの情報取得について手法をまとめ、自分への備忘録作成および情報共有をすることです。
結論
指定した歴史上の人物の、「子、親、兄弟」を、それぞれ検索表示してくれるNode.jsアプリケーションを作成しました。
動作は以下の通り。
「日本史人物」家系図検索アプリケーション pic.twitter.com/6HFPF1Zgt2
— すいばり@'16年度9試合2勝2敗 (@Suibari_cha) 2017年3月4日
内容
コード
function getFamily(person) {
var uri = "http://ja.dbpedia.org/sparql";
var children = [];
var parents = [];
var siblings = [];
console.log("YUKI.N > " + person + " の親族を調査中...");
async.series([
function(callback) {
// 子供を検索
client.fetch(uri, getParam(person, "child"), function(err, $, res, body){
children = getJson(body);
callback(err, children);
});
},
function(callback) {
// 親を検索
client.fetch(uri, getParam(person, "parent"), function(err, $, res, body){
parents = getJson(body)
callback(err, parents);
});
},
function(callback) {
// 兄弟を検索
siblings = compareChildren(parents);
callback(null, siblings);
}
], function(err, results) {
if (err) throw err;
results.forEach(function(v, i){
switch (i) {
case 0: console.log("YUKI.N > 子は、"); break;
case 1: console.log("YUKI.N > 親は、"); break;
case 2: console.log("YUKI.N > 兄弟は、"); break;
}
display(v);
});
});
// ----------------------------
// SPARQLクエリを生成する
function getParam(name, cp) {
var obj = {};
var q;
var f = "application/json";
if (cp == "child") {
// nameの子供を検索するSPARQLを返す
q = "SELECT DISTINCT ?name ?abstract WHERE {{ ?w dbpedia-owl:parent <http://ja.dbpedia.org/resource/"+name+"> . ";
q += "} UNION { <http://ja.dbpedia.org/resource/"+name+"> prop-ja:子 ?w . } ";
} else if (cp == "parent") {
// nameの親を検索するSPARQLを返す
q = "SELECT DISTINCT ?name ?abstract WHERE { <http://ja.dbpedia.org/resource/"+name+"> dbpedia-owl:parent ?w . ";
}
q += "?w rdfs:label ?name . ";
q += "?w dbpedia-owl:abstract ?abstract . }";
obj.query = q;
obj.format = f;
return obj;
}
// SPARQLendpointからの応答である4バイト文字JSONを変換する
function getJson(body) {
return JSON.parse(decodeUnicode(body)).results.bindings;
function decodeUnicode(unicode) {
return unicode.replace(/(\\u)([0-9A-F]{4})/g, function(match,p1,p2){
return String.fromCharCode(parseInt(p2, 16));
});
}
}
// 子供の数を比較して多いほうを返す
function compareChildren(p) {
var result = [];
p.forEach(function(v) {
var n = v.name.value;
var tmp = getJson(client.fetchSync(uri, getParam(n, "child")).body);
if (tmp.length > result.length) {
result = tmp; // 子供を代入
parents = v; // 親を再代入
}
});
return result;
}
function display(arr) {
arr.forEach(function(v){
console.log(v.name.value);
});
}
}
【ポイント】SPARQLについて
SPARQLの記法について記載します。
コードのgetParam関数では、人物名を引数としてSPARQLクエリを生成する役割を持っています。
例えば、"織田信長"が与えられた場合、「"織田信長"の子」を表すSPARQLクエリは以下になります。
このクエリを、"DBpedia Japanese"のSPARQLエンドポイントに対し、URLパラメータに加えてGETすれば、結果を得られます。
このような応答です。
SELECT DISTINCT ?name
WHERE {
{
?w dbpedia-owl:parent <http://ja.dbpedia.org/resource/織田信長> . --1.
} UNION {
<http://ja.dbpedia.org/resource/織田信長> prop-ja:子 ?w . --2.
}
?w rdfs:label ?name . --3.
}
コード中の番号はそれぞれ以下の処理を意味しています。
1.と2.は重複した検索を行っているように見えますが、このようにしないと検索結果に漏れができます。詳細は割愛します。
- **「織田信長」を親とする人物(信長の子)**を検索し"?w"に格納
- 「織田信長」の子である人物を検索し"?w"に格納、UNIONで1.の結果とマージする
- "?w"の名前を"?name"に格納
展望
- コンソール上の表示だけではなく、"OrgChart.js"などを用いて、図示および芋づる式の家系調査(ある人の子供の子供の子供の...と辿っていける)が行えると、物凄く有用であると思う。
所見
- 私は日本史が大好き(趣味:お城巡り)なので、このようなツールがあればいいなと以前から思っていた。実現でき、とても嬉しい。
- この処理をWikipediaへのスクレイピングで実現しようとすると、アクセス回数がかなり多くなり、実行速度低下などが予想される。こちらの案を実現できて良かった。
- 基本情報技術者試験を17年春季に受験予定なので、SQLの勉強方法に悩まされていた。SPARQLの学習を通してSQLの文法を学ぶことができ良かった。