##iss.ndl.go.jp/api/OpenSearch を使う
「国会図書館サーチ」サイトに「外部提供インタフェース(API)」の一覧があります。
http://iss.ndl.go.jp/information/api/
一覧によると検索用APIとして、SRU, SRW, OpenSearch, OpenURL, Z39.50 の5種類があります。
このうち、手軽なURL(GET)で検索指定できて、結果をXMLで返してくれるのは、 SRU と OpenSearch です。
外部提供インタフェース仕様書 には具体的な説明しかないですが、この二つの大雑把な違いは SUR の方がより詳細な検索対象項目と検索結果項目であるのに対し、 OpenSearch は取り回せる項目は少ないものの、例えば検索ワードの指定に曖昧さが許容されるなど柔軟性があるようです(後述)。
しかし意外なのですが、書籍の発行日付の情報である pubDateタグ のデータは OpenSearch でしか取得できないようです。同じ発行日付を示す issuedタグ または W3CDTF という別のタグもあるのですが、これは出版年だけしかないものがあります(多分登録が古いデータにはこの傾向があるみたいです)。
つまり、SRU では issuedタグで発行年しかわからない書籍でも、OpenSearch には pubDateタグデータがあるので年月日まで取得できます。
しかも、pubDateタグはRFC2822の記法なので、そのままTime.parse()できます。なぜOpenSearch の方にだけこんな「おまけ」があるのか謎です。(もしかしたら、間違っているかもしれません。ご指摘ください。)
###FaradyとNokogiriを使った取得サンプル
GitHUBのGistにClass化にしたサンプルをあげました。下記URLです。
https://gist.github.com/smallmake/f3d71d6769cbf09179b86d1f0ddbdba0
以下その説明です。
単純に、書籍タイトルと著者(オプション)で検索して、取得したXMLを整形してbook[]というハッシュにして、dataという配列にするものを作ってみました。
まず、Faradyをつかったgetを用意します。
require "faraday"
def ndl_get(path, pram)
con = Faraday.new(:url => 'http://iss.ndl.go.jp') do |f|
f.request :url_encoded
f.response :logger
f.adapter Faraday.default_adapter
end
con.get path, pram
end
これを使って、Nokogiriでとりあえず取得したXMLから書籍情報だけを取り出せるようにします。
require 'nokogiri'
def get_book_info(title, creator = nil)
query = {
:mediatype => 1,
:cnt => 10
}
query[:title] = title
query[:creator] = creator if creator
response = ndl_get('/api/opensearch', query)
xml = Nokogiri::XML(response.body)
xml.remove_namespaces!
items = xml.xpath('/rss/channel/item')
mediatype は
1:本
2:記事・論文
3:新聞
4:児童書
5:レファレンス情報
6:デジタル資料
7:その他
8:障害者向け資料(障害者向け検索対象資料)
9:立法情報
です。ここでは「本」を指定。
cnt は取得する件数の指定です。
itmesに検索結果の複数の書籍データが入りますので each で個々に取り出します。
###typeアトリビュートの取り出し
基本的には上の続きで
items.each do |item|
item.children.each do |c|
key = c.name
val = "#{c.content}"
のようにして、key と val が取り出せるので、単純にそれでハッシュbook[]を作ればいいのですが、少し追加した処理について以下に説明します。
XMLを読むと、
identifierタグにはtypeアトリビュートとして ISBN, NDLBibID, JPNO, TOHANMARCNO などの重要な書籍コードがあり、
subjectタグにはtypeアトリビュートとして NDC10, NDC9 などの図書分類コードが、
発行日を示すissuedタグにはtypeアトリビュートとして(なぜかW3Cの日付フォーマット名である)W3CDTFがあります。
<identifier type="dcndl:ISBN">9784041055977</identifier>
<identifier type="dcndl:NDLBibID">028826826</identifier>
<identifier type="dcndl:JPNO">23028965</identifier>
<identifier type="dcndl:TOHANMARCNO">33728988</identifier>
<subject type="dcndl:NDLC">KS152</subject>
<subject type="dcndl:NDC10">933.7</subject>
<subject type="dcndl:NDC9">933.7</subject>
<issued type="dcterms:W3CDTF">2018</issued>
これらもハッシュとして展開するために以下のように typeアトリビュートがあるときにハッシュに展開するようにします。
items.each do |item|
book = {}
item.children.each do |c|
key = c.name
val = "#{c.content}"
label = c.attribute("type")
if label
label = "#{label}".gsub(/^dcndl:|^dcterms:/,'')
book[label] ||= []
book[label] << val unless book[label].include?(val)
val = "#{label}:#{val}"
end
book[key] ||= []
book[key] << val unless book[key].include?(val)
end
これでtypeアトリビュートの各ハッシュができるとともに、各タグについては、まとめて配列でもつようにできます(後述で文字列に変換)。上記のXMLは以下のようになります。
book["identifier"] -> "ISBN:9784041055779,NDLBibID:028826825,JPNO:23028964,TOHANMARCNO:33728987"
book["ISBN"] -> "9784041055779"
book["NDLBibID"] -> "028826825"
book["JPNO"] -> "23028964"
book["TOHANMARCNO"] -> "33728987"
book["subject"] -> "NDLC:KS152,NDC10:933.7,NDC9:933.7"
book["NDLC"] -> "KS152"
book["NDC10"] -> "933.7"
book["NDC9"] -> "933.7"
book["issued"] -> "W3CDTF:2018"
book["W3CDTF"] -> "2018"
さて、以上でほぼ処理は終わりなのですが、以上のままだと、同じタグ名をもつ複数のデータは1つのハッシュに配列で格納されていますので、配列を文字列に一括で変換します。
book = book.map {|key,val| [key, val.join(',')]}.to_h
僕がやった OpenSearch を使って、書籍情報をハッシュにする処理は以上です。
##iss.ndl.go.jp/api/SRUについて少し
実は最初、SRUを使うつもりでした。
なぜなら、openSearchでは、特定の1個の書籍情報を取り出すには ISBNを使うしかなかったためです。
SRUであれば、ISBNを使わなくてもほかに itemno(国立国会図書館サーチ内部での書誌のアイテム番号)というものがあるので、itemnoの一覧さえ予め持っていれば、特定の1冊の書籍データを決め打ちで取得できます。openSearchではitemnoでの取得はできません。
なぜ、ISBNではまずいかというのは個人的事情によるのですが、それは美術展の図録の書籍データが欲しかったのに、通常、図録というものは流通しないのでISBNがないためなのです。まあ、そもそもなぜ特定の1冊だけ決め打ちで取得したいのかということもあるのですが、それも個人的事情ですが割愛します。
とにかく、そのようなわけでSRUの利用についても試したので、少し以下に書いておきます。それがOpenSearchの柔軟さの説明にもなるからです。
###最低限のパラメータ
最低限ですが以下のようなことがまずあります。
・operation=searchRetrieve を必ず指定しなければなりません。
・maximumRecords=10 のようにopenSearchのcntにあたるものを指定(無指定だと200になる)
・recordSchema=dcndl_simple のようにスキーマを指定。dcndl_simpleが使いやすい。(無指定だとdc)
・recordPacking=xml のように指定してXMLを取得。(無指定だとstringになる)
・query 以下に検索条件を指定する(次項)
つまり、SRUでXMLのデータを10件分取得するために最低限以下のようなパラメータ指定が必要です。
http://iss.ndl.go.jp/api/sru?operation=searchRetrieve&maximumRecords=10&recordSchema=dcndl_simple&recordPacking=xml&query=<次項で説明>
###queryはひとまとめにandで接続
上記パラメータに続いて検索条件のqueryを記述するのですが、ここではURLのパラメータの"&"は使えないので、" and "でつないで記述して1つの文字列にします。
なお、上記で説明したmediatypeもこのqueryの中で指定します。
この時、気がついたのですが、OpenSearch では、titleなどでタイトルに含まれる文字をいくつかスペース(+)で区切って指定するなど曖昧な指定ができるのですが、SRUではそれはできないようです。SRUでは複数のqueryパラメータをandでつないで列記します。
例えば、『縁起もの : 版画と絵画で楽しむ吉祥図』というタイトルを検索する場合、
OpenSearchでは
/opensearch?mediatype=1&cnt=10&title=縁起もの+吉祥図
という指定で検索できますが、SRUではtitleを二回書きます。
/sru?..<既述>..&query=mediatype=1+and+title="縁起もの"+and+title="吉祥図"
※もちろんこれらはURLエンコードします(Faradayに任せることができます)
前に書いたget_book_info(title, creator) の例で言えば、引数のtitleはあいまいにタイトルに含む語の部分をスペースで区切ったもので指定することを許容しているので、以下のコードのように分解してand結合し、先述の最低限のパラメータと一緒に渡せばいいです。
def get_book_info(title, creator = nil)
data = []
query = "mediatype=1"
title_array = title.split(' ')
title_array.each do |t|
query << " and title=\"#{t}\""
end
query << " and creator=\"#{creator}\"" if creator
parm = {
operation: "searchRetrieve",
maximumRecords: 10,
recordSchema: 'dcndl_simple',
recordPacking: 'xml',
query: query
}
response = ndl_get('api/sru', parm)
ついでに、書籍情報のNokogiriでの取り出しパスについては以下のようになります。
response = ndl_get('api/sru', parm)
xml = Nokogiri::XML(response.body)
xml.remove_namespaces!
records = xml.xpath('/searchRetrieveResponse/records/record/recordData/dc')
records.each do |record|
book = {}
record.children.each do |item|
key = item.name
val = "#{item.content}"