show tables
と入力することでそのテーブル一覧が表示されます。
また右側のサイドバーにあるテーブル一覧で「Show Comminity Tables」をクリックするとユーザコミュニティが作成した便利なテーブルも扱う事が出来ます。
これらのComminity Tablesはgithubで開発されており、日々新しいデータテーブルが作成されています。
実はこのユーザテーブルは、ネットワーク上にXMLを配置する事が出来る人ならば誰でも作れます。
今日はこのユーザテーブルを自作する手順をご紹介します。
ユーザテーブルはユーザテーブル群を纏めるenvファイルと、実際のクエリを記述するファイルとで構成され、YQLからenvパラメータを使って参照する事が出来ます。例えばユーザコミュニティのテーブルであればenvファイルのURLを指定して以下の様にアクセスされます。
http://developer.yahoo.com/yql/console/?env=http://datatables.org/alltables.envこのenvパラメータで指定されたURLにアクセスしてみて頂けると分かると思いますが形式は以下の様なuse文の羅列になっています。
use 'http://www.datatables.org/amazon/amazon.ecs.xml' as amazon.ecs;
use 'http://www.datatables.org/auth/auth.basic.xml' as auth.basic;
use 'http://www.datatables.org/bitly/bit.ly.shorten.xml' as bit.ly.shorten;
use 'http://www.datatables.org/data/data.html.cssselect.xml' as data.html.cssselect;
このenvファイルと、実際のXMLファイルを用意すれば自分専用のAPIを作る事が出来ます。今日はサンプルとして、はてなのユーザプロフィールをスクレイピングしてXML(もしくはJSON)を返すAPIを作ってみたいと思います。
まず配置場所としては、Google Page Creatorを使用しました。Google Page CreatorならばGoogleのアカウントさえ作れば誰でも無料でXMLを配置する事が出来ます。
はてなプロフィールを扱うAPIなのでファイル名は
hatena.profile.xml
とし、alltables.envファイルで
use 'http://mattn.jp.googlepages.com/hatena.profile.xml' as hatena.profile;
としました。次に実際のAPI部ですが、形式は以下の様になります。
<?xml version="1.0" encoding="UTF-8"?>
<table xmlns="http://query.yahooapis.com/v1/schema/table.xsd">
<meta>
<author>mattn</author>
</meta>
<bindings>
<select produces="XML" itemPath="">
<urls>
<url>http://www.hatena.ne.jp/{id}/profile</url>
</urls>
<inputs>
<key id="id" type="xs:string" paramType="variable" required="true"/>
</inputs>
<execute/>
</select>
</bindings>
</table>
YQLではXMLの中に書かれたexecuteノードにjavascriptを記述する事ができ、response.objectというオブジェクト変数に値を格納する事で独自のフォーマットを作り出す事が出来ます。またそこで使うことが出来るjavascriptにはE4X(ECMAScript for XML)が採用されており、XMLを扱いやすくなっています。
今回のはてなプロフィールAPIではhttp://www.hatena.ne.jp/mattn/で表示されるページを組み込み関数
y.query
を使ってスクレイピングします。以下javascript部のコード
var contents = y.query("select * from html where url=@url and xpath=@xpath", {
url: "http://www.hatena.ne.jp/" + id + "/",
xpath: "'//dl[@class=\"profile\"]/dd'"
});
var shortdesc = contents.results..div.(@['id']=="hatena-body")..dd[2];
var longdesc = contents.results..div.(@['id']=="hatena-body")..dd[3];
var profile =
<profile>
<user>{contents.results..div.(@['id']=="hatena-body")..dd[0].p.text().toString()}</user>
<nickname>{contents.results..div.(@['id']=="hatena-body")..dd[1].p.text().toString()}</nickname>
<shortdesc>{shortdesc ? shortdesc.*.toString() : ''}</shortdesc>
<longdesc>{longdesc ? longdesc.*.toString() : ''}</longdesc>
<medals/>
<addresses/>
<services/>
</profile>;
for each(var n in contents.results..div.(@['class']=="medals").img) {
profile.medals.medial += <medal>{n.@['title'].toString()}</medal>;
}
for each(var n in contents.results..table.(@['class']=="profile addresslist")..tr) {
profile.addresses.address +=
<address>
<name>{n.th.*.text().toString()}</name>
<value>{n.td.*.text().toString()}</value>
</address>;
}
for each(var n in contents.results..ul.(@['class']=="hatena-fotolife floatlist")[0]..a) {
profile.services.service +=
<service>
<name>{n.img.@['title'].toString()}</name>
<url>{n.@['href'].toString()}</url>
</service>
}
response.object = profile;
y.queryにはYQLから使うことが出来るselect文を使用する事ができ、xpathを使って絞り込む事も出来ます。ただ今回は一つのAPIで
- アカウントID
- ニックネーム
- 短い紹介
- 自己紹介
- アドレス情報
- 使っているサービス一覧
- 市民メダル情報
例えばE4Xでは、DOMオブジェクトの階層スキップや条件指定検索を
contents.results..div.(@['id']=="hatena-body")..dd[2];
等といった書き方をする事が出来ます。つまり無理にxpathで絞り込む必要は無いという事です。おそらくサーバ側ではrhinoあたりを使っているのかもしれませんね。
XML全体のコードは以下の様になりました。
<?xml version="1.0" encoding="UTF-8"?>
<table xmlns="http://query.yahooapis.com/v1/schema/table.xsd">
<meta>
<author>mattn</author>
</meta>
<bindings>
<select produces="XML" itemPath="">
<urls>
<url>http://www.hatena.ne.jp/{id}/profile</url>
</urls>
<inputs>
<key id="id" type="xs:string" paramType="variable" required="true"/>
</inputs>
<execute><![CDATA[
var contents = y.query("select * from html where url=@url and xpath=@xpath", {
url: "http://www.hatena.ne.jp/" + id + "/",
xpath: "'//dl[@class=\"profile\"]/dd'"
});
var shortdesc = contents.results..div.(@['id']=="hatena-body")..dd[2];
var longdesc = contents.results..div.(@['id']=="hatena-body")..dd[3];
var profile =
<profile>
<user>{contents.results..div.(@['id']=="hatena-body")..dd[0].p.text().toString()}</user>
<nickname>{contents.results..div.(@['id']=="hatena-body")..dd[1].p.text().toString()}</nickname>
<shortdesc>{shortdesc ? shortdesc.*.toString() : ''}</shortdesc>
<longdesc>{longdesc ? longdesc.*.toString() : ''}</longdesc>
<medals/>
<addresses/>
<services/>
</profile>;
for each(var n in contents.results..div.(@['class']=="medals").img) {
profile.medals.medial += <medal>{n.@['title'].toString()}</medal>;
}
for each(var n in contents.results..table.(@['class']=="profile addresslist")..tr) {
profile.addresses.address +=
<address>
<name>{n.th.*.text().toString()}</name>
<value>{n.td.*.text().toString()}</value>
</address>;
}
for each(var n in contents.results..ul.(@['class']=="hatena-fotolife floatlist")[0]..a) {
profile.services.service +=
<service>
<name>{n.img.@['title'].toString()}</name>
<url>{n.@['href'].toString()}</url>
</service>
}
response.object = profile;
]]></execute>
</select>
</bindings>
</table>
あとはこれをYQLから指定してやれば良い事になります。実際にはここで試す事が出来ます。
さてこれでAPIが出来上がったので、APIを使ったサンプルを書いてみましょう。
$(function() {
$('#hatena-profile').submit(function() {
$('#hatena-profile-ajaxloader').attr('src', 'http://mattn.kaoriya.net/images/ajax-loader.gif').show();
var query = "select * from hatena.profile where id = '" + $('#hatena-id').val() + "'";
var envurl = "http://mattn.jp.googlepages.com/alltables.env";
$.getJSON('http://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent(query) + '&format=json&env=' + encodeURIComponent(envurl) + '&callback=?', function(data) {
$('#hatena-profile-ajaxloader').hide();
$('#hatena-profile-user').text(data.query.results.profile.user);
$('#hatena-profile-nickname').text(data.query.results.profile.nickname);
$('#hatena-profile-medals').html(data.query.results.profile.medals.medal.join('<br />'));
$.each(data.query.results.profile.addresses.address, function() {
var html = $('#hatena-profile-addresses').html();
$('#hatena-profile-addresses').html(html + this.name + ':' + this.value + '<br />');
});
$.each(data.query.results.profile.services.service, function() {
var html = $('#hatena-profile-services').html();
$('#hatena-profile-services').html(html + this.name + ':' + this.url + '<br />');
});
$('#hatena-profile-info').show('slow');
});
return false;
});
});
簡単にプロフィール情報を表示するだけの物です。以下実行例です。