7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rubyで国会図書館検索APIを使って書籍検索

Last updated at Posted at 2019-02-22

##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で返してくれるのは、 SRUOpenSearch です。

外部提供インタフェース仕様書 には具体的な説明しかないですが、この二つの大雑把な違いは 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を用意します。

sample1.rb
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から書籍情報だけを取り出せるようにします。

sample2.rb
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アトリビュートの取り出し

基本的には上の続きで

sample3.rb
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アトリビュートがあるときにハッシュに展開するようにします。

sample4.rb
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つのハッシュに配列で格納されていますので、配列を文字列に一括で変換します。

sample4.rb
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 のようにopenSearchcntにあたるものを指定(無指定だと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結合し、先述の最低限のパラメータと一緒に渡せばいいです。

sample5.rb
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での取り出しパスについては以下のようになります。

sample6.rb
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}"

以上です。
ndl_screen1.png

7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?