法人インフォのAPIを触ってみた
法人インフォ
開発の松尾です。
2017/1/19に、経済産業省の「法人インフォメーション(法人インフォ)」というサービスが開始されました。政府が保有する400万件にのぼる法人情報を自由に検索できるという、なかなか画期的なサービスです。
ラクーンではB2B向けのサービスを複数展開していることもあり数多くの企業情報を取り扱います、政府によって一元化された企業情報に自由にアクセスできるというのは、ラクーンの事業にとって様々なメリットがあります。
法人インフォのAPI
行政機関が提供するサービスって、なんでしょうか一般的に使い勝手が悪かったり、やたらPDFやExcelが大好きだったり、執拗にIEを使うように迫ってきたり…、まあ全てがそうだと言ってるわけではありませんが、えーと、まあそんなイメージがあるところに、あっさりと「APIはじめました」ってのぼりが立っていて驚いたというのが正直な感想です。
APIの仕様書がPDF形式とWORD形式のみだったり(HTML版が欲しい…)、「名前空間」とか「語彙」とかメタデータっぽいよく分からないことが書いてありますが、APIがあればとりあえずOK。どうにかなるでしょう。
APIのキモはSPARQL
API仕様書を読み始めると、だんだん「なんか思っていたのと違う」という疑念が湧いてきました。
$ curl http://hojin-info.go.jp/api/companies?location=東京都中央区
※ここにずらずらと結果のJSONが出て来るイメージ
↑こんなイメージで使えるんじゃないかなあ、と妄想していたわけです。
完全に誤解でした。
完全に妄想でした。
目の前にある法人インフォのAPIは、「RDFで構成されたメタデータの塊にSPARQLのクエリを投げるインターフェース」のためのエンドポイント「ひとつだけ」で構成されています。(何を言っているのかわからねーと思うが、俺も何をされたのかわからなかった…頭がどうにかなりそうだった…全文検索だとかLIKE検索だとか、そんなチャチなもんじゃあ断じてねえ・・・)
さて、説明はしてみたものの「何を言っているのかさっぱりわからん」という方が多いんじゃないかと思いますので、順を追って説明してみます。
まず、RDFと
いうのは「メタデータ」を記述するための枠組みです。メタデータとは何ぞや?
と下手に深追いすると返り討ちに合いそうなので止めておきますが、要はさまざまな種類の情報を統一的に取り扱うための仕組みだと考えてください。乱暴すぎ
るかもしれませんが「ある形式のXML文書」だと考えても悪くは…うーん、これはさすがに語弊がありすぎるかなあ。
強引に進めると、法人インフォの中にある法人情報はこのようなメタデータで格納されています。具体的にどのような形式で保持しているのかはわかりませんが、とにかくメタデータです。大事なことなので二回言いました。
そして、このようなRDFメタデータの塊から必要な情報を抜き出すための手段として、SPARQLというクエリ言語が使われています。
PREFIX abc: <http://mynamespace.com/exampleOntologie#>
SELECT ?capital ?country
WHERE {
?x abc:cityname ?capital.
?y abc:countryname ?country.
?x abc:isCapitalOf ?y.
?y abc:isInContinent abc:africa.
}
このSQLっぽい何か、これこそがSPARQLのクエリです。
ちょっとだけ説明を試みると、「?x」のように識別子の先頭が
「?」から始まるものはSPARQLの「変数」です。WHERE句のところにずらずらと並べられた「トリプル」のセットが問い合わせの本体で、トリプルと
は「主語(Subject)」、「述語(Predicate)」、「目的語(Object)」を空白で区切って並べ、ピリオドで終了します。と、書いては
みたもののさっぱりわかりませんね。あきらめて先に進みましょう。
法人インフォはWebのインターフェースも引っくるめて、法人情報のメタデータとSPARQLによる検索処理で構成されています。
実際になんらかの法人情報を表示すると、その情報を取り出すためのSPARQLクエリを参照することができます。ある意味で徹底しているところに好感を覚えつつ、見慣れないSPARQLのクエリにめまいも覚えます。
幸いなことに法人インフォには直接SPARQLクエリを投げるためのインターフェースが用意されています。サンプルをコピペしてちょっとづついじっていれば、いつしかSPARQLの達人に…いや、無理かな…
PREFIX hj: <http://hojin-info.go.jp/ns/domain/biz/1#>
PREFIX ic: <http://imi.go.jp/ns/core/rdf#>
SELECT DISTINCT ?id ?name ?location ?updated
FROM <http://hojin-info.go.jp/graph/hojin> {
?s hj:法人基本情報 ?base .
?base ic:住所/ic:都道府県 '東京都' .
?base ic:ID/ic:識別値 ?id .
?base ic:名称/ic:表記 ?name .
FILTER(contains(str(?name), 'ラクーン')) .
OPTIONAL{?base ic:住所/ic:表記 ?location .}
OPTIONAL{?base hj:更新日時/ic:標準型日時 ?updated .}
}
ORDER BY ?name
そんなこんなで、ひぃひぃ言いながら書いてみたクエリがこちら。「法人基本情報」に含まれる住所が「東京都」で、名称に弊社の名前である「ラクーン」を含
む法人情報を検索するためのクエリです。「法人番号」、「名称」、「住所」、「更新日時」の4種類の情報を抽出しています。
クエリの実行結果です。このようにシンプルなHTMLに限らず、XML、JSON、CSVといった幅広い形式で出力結果を取得することができます。このあたりの使い勝手は素晴らしいですね。
※期待する結果は得られたので、クエリ自体は間違ってはいないと思いたいところですが、もしかすると大変に下手で恥ずかしい内容である可能性もあるので、盲信するのだけはご遠慮ください。
APIを使おう!
さて、APIを使うためにここまで色々な前提知識が必要になるとは思いませんでしたが、なんとかなりそうです。
幸いなことに、SPARQLクエリさえ出来上がれば法人インフォのAPIはごくごく簡単に使用できます。
http://api.hojin-info.go.jp/sparql?query=[SPARQLクエリ]&format=[出力形式]
たったこれだけ。
formatパラメータには出力形式を指定できます。CSV形式であれば「text/csv」、RDF/XML形式であれば「application/rdf+xml」といった感じで指定します。
し
かし、困ったことに仕様書ではJSON形式が欲しい場合に「application/sparql-result+json」が使えると書いてあるのです
が、わたしがテストした限りでは動作しませんでした。テストしているうちに気づきましたが「sparql-result+json」ではなくて
「sparql-results+json」が正しいようですね。仕様書では「s」が抜けているようです。
先程の検索結果であれば下記のようなURLになります。
http://api.hojin-info.go.jp/sparql/?query=PREFIX++hj%3A+%3Chttp%3A%2F%2Fhojin-info.go.jp%2Fns%2Fdomain%2Fbiz%2F1%23%3E%0D%0APREFIX++ic%3A+%3Chttp%3A%2F%2Fimi.go.jp%2Fns%2Fcore%2Frdf%23%3E%0D%0A%0D%0ASELECT+DISTINCT+%3Fid+%3Fname+%3Flocation+%3Fupdated%0D%0AFROM+%3Chttp%3A%2F%2Fhojin-info.go.jp%2Fgraph%2Fhojin%3E+{+%0D%0A+++++%3Fs+hj%3A%E6%B3%95%E4%BA%BA%E5%9F%BA%E6%9C%AC%E6%83%85%E5%A0%B1+%3Fbase+.%0D%0A+++++%3Fbase+ic%3A%E4%BD%8F%E6%89%80%2Fic%3A%E9%83%BD%E9%81%93%E5%BA%9C%E7%9C%8C+%27%E6%9D%B1%E4%BA%AC%E9%83%BD%27+.%0D%0A+++++%3Fbase+ic%3AID%2Fic%3A%E8%AD%98%E5%88%A5%E5%80%A4+%3Fid+.%0D%0A+++++%3Fbase+ic%3A%E5%90%8D%E7%A7%B0%2Fic%3A%E8%A1%A8%E8%A8%98+%3Fname+.%0D%0A+++++FILTER(contains(str(%3Fname)%2C+%27%E3%83%A9%E3%82%AF%E3%83%BC%E3%83%B3%27))+.%0D%0A+++++OPTIONAL{%3Fbase+ic%3A%E4%BD%8F%E6%89%80%2Fic%3A%E8%A1%A8%E8%A8%98+%3Flocation+.}+%0D%0A+++++OPTIONAL{%3Fbase+hj%3A%E6%9B%B4%E6%96%B0%E6%97%A5%E6%99%82%2Fic%3A%E6%A8%99%E6%BA%96%E5%9E%8B%E6%97%A5%E6%99%82+%3Fupdated+.}+%0D%0A}%0D%0AORDER+BY+%3Fname&format=text%2Fhtml
URLとしては長すぎますが、コピペしてブラウザから開くこともできます。
プログラムにAPIを組み込んでみよう!
さて、APIのテストには成功したので次はプログラマらしくプログラムに組み込んでみましょう。APIにアクセスする方法は単純極まりないので、どんなプログラミング言語を選んでも迷うところは少ないでしょう。
「ど・れ・で、書こーかなー」と舌なめずりしたものの、某Go本を出版した手前、やはりgolangで書かねばならんだろうなあと調査してみました。
するとSPARQLクエリのためのgolangパッケージがgithub上にあるではありませんか!
「github.com/knakk/rdf」というRDFのためのパッケージとセットでSPARQLクエリの実行をサポートしているようです。おお、これなら大した手間なくgolangで操作できそうです。
$ go get github.com/knakk/sparql
さっそく、「go get」でパッケージを入手して、一気呵成にサンプルコードを書いてみました。
↓
package main
import (
"fmt"
"log"
"github.com/knakk/sparql"
)
// 「法人インフォ」のSPARQLエンドポイント
const ENDPOINT = "http://api.hojin-info.go.jp/sparql"
// SPARQLクエリ
const QUERY = `
PREFIX hj:
PREFIX ic:
SELECT DISTINCT ?id ?name ?location ?updated
FROM {
?s hj:法人基本情報 ?base .
?base ic:住所/ic:都道府県 '東京都' .
?base ic:ID/ic:識別値 ?id .
?base ic:名称/ic:表記 ?name .
FILTER(contains(str(?name), 'ラクーン')) .
OPTIONAL{?base ic:住所/ic:表記 ?location .}
OPTIONAL{?base hj:更新日時/ic:標準型日時 ?updated .}
}
`
func main() {
// SPARQLリポジトリ
repo, err := sparql.NewRepo(ENDPOINT)
if err != nil {
log.Fatal(err)
}
// クエリの発行
res, err := repo.Query(QUERY)
if err != nil {
log.Fatal(err)
}
// 検索結果のトリプル単位でループ
for _, m := range res.Results.Bindings {
// mはキーがstringのmap型
fmt.Printf("%s,%s,%s,%s\n", m["id"], m["name"], m["location"], m["updated"])
}
}
わずかなコード量で完成しました。さっそく実行してみましょう。
{literal 1011302015931 },{literal 有限会社ラクーン },{literal 東京都杉並区和田1丁目66番12号 },{typed-literal 2015-10-28T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 3010401070464 },{literal 株式会社ラクーン },{literal 東京都港区六本木6丁目8番18号文山ビル502 },{typed-literal 2015-10-26T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 4010001075598 },{literal 株式会社ラクーン },{literal 東京都千代田区外神田5丁目2番10号 },{typed-literal 2015-10-26T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 4010001079723 },{literal 株式会社ラクーンポリッシュ },{literal 東京都千代田区神田錦町3丁目11番地8 },{typed-literal 2015-10-26T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 4012402012799 },{literal 有限会社ラクーン },{literal 東京都調布市小島町3丁目17番地6 },{typed-literal 2015-10-30T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 5010902023638 },{literal 有限会社ラクーン },{literal 東京都世田谷区給田3丁目9番2-405号 },{typed-literal 2015-10-28T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 5011102013792 },{literal 有限会社ラクーンハウス },{literal 東京都新宿区左門町6番地 },{typed-literal 2015-10-28T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 6010401047451 },{literal 株式会社ラクーンエージェンシー },{literal 東京都港区六本木7丁目1番19号 },{typed-literal 2015-10-26T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 6011602010768 },{literal 有限会社ラクーン・フォト・スペース },{literal 東京都練馬区光が丘3丁目9番2号 },{typed-literal 2015-10-28T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 7010902016152 },{literal 有限会社ラクーン },{literal 東京都世田谷区南烏山2丁目20番14号 },{typed-literal 2015-10-28T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 7011801005873 },{literal 株式会社イエロー・ラクーン },{literal 東京都足立区古千谷本町2丁目24番11号 },{typed-literal 2015-10-28T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 7011802029888 },{literal 有限会社ラクーン },{literal 東京都葛飾区白鳥3丁目11番2号 },{typed-literal 2015-10-28T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 8010001076972 },{literal 株式会社ラクーン },{literal 東京都中央区日本橋蛎殻町1丁目14番14号 },{typed-literal 2015-10-26T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 8010902016151 },{literal 有限会社ラクーン },{literal 東京都世田谷区宮坂3丁目52番1号 },{typed-literal 2015-10-28T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
{literal 9010105001727 },{literal 一般社団法人中央ラクーンズアスリートクラブ },{literal 東京都八王子市東中野216番地5 },{typed-literal 2015-10-30T00:00:00+09:00 http://www.w3.org/2001/XMLSchema#dateTime}
ばっちりです。「法人インフォ」はRDFやSPARQLといった標準技術に準拠しているようで、特別な設定もなく簡単に結果を取り出すことができました。 なんだかんだSPARQLのクエリに苦労したものの、統一された標準技術のありがたさが胸にしみます。
本当はもう少しコードの説明を書くつもりだったのに、説明のしようが無く短くなってしまいました。興味のある方は実際に動かしてみてもらえると嬉しいです。