データベースから読み込んだ本のタイトルのデータで国会図書館の NDL opensearch を使って書籍情報をデータベースに書き込む。

https://qiita.com/dauuricus/items/563cbcc9776f66cb672e

このページは、「マンガサイトにつひての色々な事情 03 」の補足です。

コードは Ruby ですが、google colab 上に IRuby カーネルをセットして実行することを想定しています。（じゃなくても可能かもしれないですが、それ以外で Ruby プログラムを書いたことが無いために不明です。インターネット接続されていて Ruby が動くコンピューターであればということで後ほど、Android で検証するつもりです。）

<details><summary>Android 32bit OS gem list</summary>
<div>

*** LOCAL GEMS ***

abbrev (default: 0.1.0)
base64 (default: 0.1.0)
benchmark (default: 0.1.1)
bigdecimal (default: 3.0.0)
bundler (default: 2.2.22)
cgi (default: 0.2.0)
csv (default: 3.1.9)
date (default: 3.1.0)
dbm (default: 1.1.0)
debug (default: 0.1.0)
delegate (default: 0.2.0)
did_you_mean (default: 1.5.0)
digest (default: 3.0.0)
drb (default: 2.0.4)
english (default: 0.7.1)
erb (default: 2.2.0)
etc (default: 1.2.0)
faraday (1.5.1, 1.4.3)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.1.0)
faraday-patron (1.0.0)
fcntl (default: 1.0.0)
fiddle (default: 1.0.6)
fileutils (default: 1.5.0)
find (default: 0.1.0)
forwardable (default: 1.3.2)
getoptlong (default: 0.1.1)
glib2 (3.4.4)
io-console (default: 0.5.7)
io-nonblock (default: 0.1.0)
io-wait (default: 0.1.0)
ipaddr (default: 1.2.2)
irb (default: 1.3.5)
json (default: 2.5.1)
logger (default: 1.4.3)
matrix (default: 0.3.1)
mini_portile2 (2.5.1, 2.3.0)
minitest (5.14.2)
multipart-post (2.1.1)
mutex_m (default: 0.1.1)
native-package-installer (1.1.1)
net-ftp (default: 0.1.2)
net-http (default: 0.1.1)
net-imap (default: 0.1.1)
net-pop (default: 0.1.1)
net-protocol (default: 0.1.0)
net-smtp (default: 0.2.1)
nkf (default: 0.1.0)
nokogiri (1.8.1)
observer (default: 0.1.1)
open-uri (default: 0.1.0)
open3 (default: 0.1.1)
openssl (default: 2.2.0)
optparse (default: 0.1.0)
ostruct (default: 0.3.1)
pathname (default: 0.1.0)
pkg-config (1.4.6)
power_assert (1.2.0)
pp (default: 0.1.0)
prettyprint (default: 0.1.0)
prime (default: 0.1.2)
pstore (default: 0.1.1)
psych (default: 3.3.0)
racc (default: 1.5.1)
rake (13.0.3)
rbs (1.0.4)
rdoc (default: 6.3.1)
readline (default: 0.0.2)                                                               readline-ext (default: 0.1.1)
reline (default: 0.2.5)
resolv (default: 0.2.0)
resolv-replace (default: 0.1.0)
rexml (3.2.5)
rinda (default: 0.1.0)
rss (0.2.9)
ruby2_keywords (0.0.4)
securerandom (default: 0.1.0)
set (default: 1.0.1)
shellwords (default: 0.1.0)
singleton (default: 0.1.1)
stringio (default: 3.0.0)
strscan (default: 3.0.0)
syslog (default: 0.1.0)
tempfile (default: 0.1.1)
test-unit (3.3.7)
time (default: 0.1.0)
timeout (default: 0.1.1)
tmpdir (default: 0.1.2)
tracer (default: 0.1.1)
tsort (default: 0.1.0)
typeprof (0.12.0)
un (default: 0.1.0)
uri (default: 0.10.1)
weakref (default: 0.1.1)
yaml (default: 0.1.1)
zlib (default: 1.1.0)
</div></details>

セットアップ自体については、こちらを参考に。

<a href="https://qiita.com/dauuricus/items/0717c8a08aba7abc7575">Googlecolab を使って iRuby で selenium と chromium で徘徊する為に「Googlecolab を iRuby カーネル ... Ruby 2.5.1 で動作させて Selenium を使うための設定。」</a> の記事のなかでは chromium browser と selenium webdriver のセットアップを解説していますが、本記事においては ["faraday"](https://lostisland.github.io/faraday/) が別途必要ですが webdriver などは不要です。

---

本の **ISBN 情報なしに**、本の題名から書籍データを抽出したいということ。

SQLite データベースから本のタイトルと著者を読みこんで、国会図書館サーチで書籍検索する。
ここでは、NDL searchのコード眺めます。

読み込むデータベースのテーブル mangathank.db > [**tbl_manga**]

![Screenshot_20210713-063156_kindlephoto-1050866.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/0e0ec7c5-9a8a-bc90-6edd-82d8e5978166.png)


```
    id INTEGER PRIMARY KEY,
    title text,
    url text,
    updated_datetime datetime,
    author text,
    book_title text
```

詳細はこちら [マンガサイト観測 1](https://zenn.dev/kurocat/articles/eb233dc31bb285)

####NDL-search--Ruby-プログラム-for-Googlecolab

[**tbl_manga**] のカラム id, title を読み込んで 国会図書館 NDL サーチ で書籍データを照会して [**tbl_bookdata**]に書き込む。

![Screenshot_20210713-063641_kindlephoto-1334876.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/84d64898-ce75-b871-ec28-f111cf4c6db9.png)

![Screenshot_20210713-064114_kindlephoto-1607658.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/736a9dc2-5779-c175-3e95-66d686ee9a1e.png)


bookdata.db > [**tbl_bookdata**]

```
    id INTEGER PRIMARY KEY,
    book_title text,
    url text,
    author text,
    creatortranscription text,
    volume text,
    seriestitle text,
    publisher text,
    isbn text,
    mangathank_title text,
    ex_id integer
```

たとえば、

`[田河水泡] のらくろ 漫画集 文庫版 第1巻`

という tbl_manga の `title` の値の場合は、正規表現を使って、必要のない文字列を除去しタイトルと著者に分け NDL サーチにリクエストするクエリに組み込まれる。

NdlSearch インスタンスのクエリの中にはこのようにセットされる。

```
	title = 'のらくろ 漫画集 1'
	creator = '田河水泡'
```

レスポンスは xml で返されるが以下の html ページに記載された内容と同等になる。

　国会図書館サーチ  'のらくろ 漫画集 1', '田河水泡' の検索結果

https://iss.ndl.go.jp/books/R100000002-I000001314129-00?ar=4e1f&lat=&lng=%3Atitle

このような検索結果の情報が tbl_bookdata に書き込まれる。
tbl_mangaの `title` の値は、tbl_bookdata の `mangathank_title` にコピーされる。
同様に tbl_manga の `id` の値のは、 tbl_bookdata の `ex_id` にコピーされる。

`OpenSearch URL	> XML(RSS)	 https://iss.ndl.go.jp/api/opensearch`

```ruby:kokkaitosyokan_ndl_search.rb
#2021.7.13
require "faraday"
require 'nokogiri'
require 'sqlite3'
require 'time'
require 'date'

class NdlSearch

  def get_book_info(title, creator = nil)
    data = []
    query = {
      :mediatype => 1,
      :cnt => 1
    }
    query[:title] = title
    query[:creator] = creator if creator
    if creator == ''
        puts 'author :??'
    end
    puts
    print "query :#{query}"
    puts
    puts
    response = ndl_get('/api/opensearch', query)
    
    xml = Nokogiri::XML(response.body)
    xml.remove_namespaces!
    items = xml.xpath('//item')
    unless items.any? then
        puts
        puts 'ndl has no item'
        data << {"totalResults"=>"0"}
    else
    #pp items.to_s
        items.each do |item|
          #puts
          #puts item
          book = {}
          item.children.each do |c|
            key = c.name
            next if key == 'text'
            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
          book = book.map {|key,val| [key, val.join(',')]}.to_h
          data << book
        end
    end
    print 'data: ' 
    puts data
    puts
    data
  end

  private

  def ndl_get(path, pram)
    con = Faraday.new(:url => 'https://iss.ndl.go.jp') do |f|
      f.request  :url_encoded
      #f.response :logger
      f.adapter :net_http
    end
    con.get path, pram
  end
end

#DB

SQL =<<EOS
create table tbl_bookdata (
    id INTEGER PRIMARY KEY,
    book_title text,
    url text,
    author text,
    creatortranscription text,
    volume text,
    seriestitle text,
    publisher text,
    isbn text,
    mangathank_title text,
    ex_id integer
);
EOS

count = 0
new_db = SQLite3::Database.open("bookdata.db")
db = SQLite3::Database.open("mangathank4.db")
new_db.execute(SQL)

temp_author = ''
temp_title = ''

50.times do |api|
    count += 1
    puts
    print count,' '
    search_data = db.execute("select book_title,author,title,id from tbl_manga where id ='#{count}' ;")
    *book_data = search_data.pop
    #book_data[0] #=> book_title
    #book_data[1] #=> author
    #book_data[2] #=> title
    #book_data[3] #==> id
    mangathank_title = book_data[2].to_s.gsub(/\'/, "\'\'")

    if book_data[2] == "null" then
        p count
        pp book_data
        new_db.execute("insert into tbl_bookdata (id, book_title, author, mangathank_title, ex_id ) values('#{count}','book_title:nothing','author:nothing','#{mangathank_title}','#{book_data[3]}');")
    else
        author_data = book_data[2].to_s.slice(/((?<=\[).*?(?=\]))/)
        #puts "author_dat:#{author_data}"
        if author_data != nil
            author_data.gsub!(/\ x\ /,' ')
            author_data.sub!(/((?<=[\p{Hiragana}\p{Han}\p{Katakana}])x(?=[\p{Hiragana}\p{Han}\p{Katakana}]))/,' ')
            author_data.gsub!(/\(|\)/,"\(" =>' ',"\)"=>'')
            author_data.gsub!(/×/,' ')
            author_data.gsub!(/\ &/,' ')
        end
        if /(\ )/.match(author_data) then
            #/(\S+$)/.match(author_data)
            #person = /(?<=['\ '])\S.*$/.match(author_data)
            #str_array = person.to_s.split
            str_array = author_data.to_s.split
            person = str_array.pop
        else
            person = author_data.to_s
        end
        print("author_data: ",  author_data , "  person: " , person)
        puts
        num = book_data[2].to_s.slice(/((?<=第)\d+(?=巻|卷$))/)
        #num = /((?<=第)\d+(?=巻$))/.match(book_data[0].to_s)
        #book_data_0 = book_data[0].to_s.sub(/((?=第).*巻)/,'')
        book_data_0 = book_data[2].to_s.gsub(/((?=第).*(巻|卷))/,'')
        book_data_0.gsub!(/((?=第).*話)/,'')
        book_data_0.gsub!(/(.(?<=\()文庫版(?=\)).)/,'')
        book_data_0.gsub!(/(.(?<=\[)文庫版(?=\]).)/,'')
        book_data_0.gsub!(/文庫版/,'')
        book_data_0.gsub!(/(.(?<=\()完(?=\)).)/,'')
        book_data_0.gsub!(/(.(?<=【).*(?=】).)/,'')
        book_data_0.gsub!(/(.(?<=\[).+?(?=\]).)/,'')
        book_data_0.lstrip!
        book_data_0.rstrip!
        
        if book_data_0 == temp_title then
            if str_array then
                person = temp_author
            end
        else
            temp_title = book_data_0
            temp_author = person
        end

        if num != nil then
            num = num.to_i
            book_data_0 += ' ' + num.to_s
        end
        #puts
        #puts book_data[0]
        #puts book_data_0
        #puts
        ndl_search = NdlSearch.new

        onemore = 'true'

        while onemore == 'true' do
            res =  ndl_search.get_book_info( book_data_0,person )
            onemore = 'false'

            if res == nil then
                #puts "res: empty"
                book_data_0.gsub!(/\'/,"\'\'")
                puts book_data_0
                new_db.execute("insert into tbl_bookdata (id, book_title, author, mangathank_title, ex_id ) values('#{count}','#{book_data_0}','#{author_data}','#{mangathank_title}','#{book_data[3]}');")
                onemore = 'false'
            else
                res.each_with_index do |book,index|
                   #print "book : "
                   if book != "null" then
                       #puts  "res:#{book}" 
                       #puts "item:#{index}"
                       book.each do |key, val|
                            #puts "#{key}:#{val}"
                            if key == 'totalResults' then
                                #puts
                                #print "no match title name #{person} ",book_data[3],'  '
                                book_data_0.gsub!(/\'/,"\'\'")
                                #puts book_data_0,person,mangathank_title
                                unless str_array.nil? then
                                    if str_array.size > 0 then
                                        person = str_array.shift
                                        puts
                                        print "#{person} ?"
                                        puts
                                        puts
                                        onemore = 'true'
                                        #sleep 0.2
                                        break
                                    else
                                        new_db.execute("insert into tbl_bookdata (id, book_title, author, mangathank_title, ex_id ) values('#{count}', '#{book_data_0}','#{author_data}','#{mangathank_title}','#{book_data[3]}');")
                                        onemore = 'false'
                                        #sleep 0.2
                                        break
                                    end

                                else    
                                    new_db.execute("insert into tbl_bookdata (id, book_title, author, mangathank_title, ex_id ) values('#{count}', '#{book_data_0}','#{author_data}','#{mangathank_title}','#{book_data[3]}');")
                                    onemore = 'false'
                                end
                                break
                            end
                            #puts "#{key}:#{val}"
                            if key == 'title' then
                                temp_author = person
                                puts "☆☆ #{count} ☆☆"
                                puts "#{key}:#{val}"
                                title = val.to_s.gsub(/\'/, "\'\'")
                                new_db.execute("insert into tbl_bookdata (id, book_title, mangathank_title, ex_id ) values('#{count}', '#{title}','#{mangathank_title}','#{book_data[3]}');")
                            elsif key == 'author' then
                                author = val.to_s.gsub(/\'/, "\'\'")
                                new_db.execute("update tbl_bookdata set author = '#{author}' where id = '#{count}';")
                            elsif key == 'creatorTranscription' then
                                creatortranscription = val.to_s.gsub(/\'/, "\'\'")
                                new_db.execute("update tbl_bookdata set creatortranscription = '#{creatortranscription}' where id = '#{count}';")
                            elsif key == 'volume' then
                                volume = val.to_s.gsub(/\'/, "\'\'")
                                new_db.execute("update tbl_bookdata set volume = '#{volume}' where id = '#{count}';")
                            elsif key == 'link' then
                                url = val
                                new_db.execute("update tbl_bookdata set url = '#{url}' where id = '#{count}';")
                            elsif key == 'publisher' then
                                puts "#{key}:#{val}"
                                publisher = val.to_s.gsub(/\'/, "\'\'")
                                new_db.execute("update tbl_bookdata set publisher = '#{publisher}' where id = '#{count}';")
                            elsif key == 'ISBN' then
                                isbn = val.to_s.gsub(/\'/, "\'\'")
                                new_db.execute("update tbl_bookdata set isbn = '#{isbn}' where id = '#{count}';")
                            elsif key == 'seriesTitle' then
                                seriestitle = val.to_s.gsub(/\'/, "\'\'")
                                new_db.execute("update tbl_bookdata set seriestitle = '#{seriestitle}' where id = '#{count}';")

                            onemore = 'false'
                            else
                                #new_db.execute("update tbl_bookdata set author = '', creatortranscription = '', volume = '',  url = '', publisher = '', isbn = '', seriestitle = '' ;")
                                onemore = 'false'
                                next
                            end
                        end
                    else
                        onemore = 'false'
                        puts "error"
                        mangathank_title = book_data[2].to_s.gsub(/\'/, "\'\'")
                        new_db.execute("insert into tbl_bookdata (id, author, mangathank_title, ex_id ) values('#{count}','#{author_data}','#{mangathank_title}','#{book_data[3]}');")
                    end
                end
            end
        end 
    end
end
```

## bookdata.db から ISBN を抽出

### 100 個取り出す (python)

```python:extract_100_isbn.py
import sqlite3
import re

def extract_isbn(cur,tablename,temp_isbn,offset):
    max = 100
    counter = 0
    if counter < max:
        for i in range(10000):
            if counter > max - 1:
                counter = 0
                break
            else:
                id = i + offset
                isbn = cur.execute("SELECT isbn FROM %s WHERE id=?;"% tablename,[id])

                for x in isbn:
                    if x != temp_isbn:
                        y = re.findall("(?<=\(\').+?(?=\'\,\))", str(x))
                        if y !=[]:
                            counter += 1
                            offset = id + 1
                            #print(str(counter)+':')
                            #print(id, end='  ')
                            print(*y)
                            temp_isbn = x

    return offset

db_name = 'bookdata.db'
db = sqlite3.connect(db_name)

cur = db.cursor()

res =cur.execute("SELECT name FROM sqlite_master WHERE type='table';")
tablename = ''
for name in res:
    #print(name[0])
    tablename = name[0]

offset = 1
temp_isbn = ''

offset = extract_isbn(cur,tablename,temp_isbn,offset)

db.close()
```

### 100 個取り出す (Ruby)
```Ruby
require 'sqlite3'

def extract_isbn(res,offset)
    max = offset + 100
    counter = 0 
    res.each_with_index do |data,i|
        if data[0] != nil then
            if counter < offset then
                counter += 1
                #puts i
                next
            end
            if counter < max then
                counter += 1
                puts data[0]
            else
                offset = counter
                break
            end    
        end
    end
    return offset
end

db = SQLite3::Database.open "bookdata_.db"

res = db.execute("select distinct(isbn) from tbl_bookdata ;")

offset = 0

offset = extract_isbn(res,offset)


db.close
```

### ISBN を CSV に保存 (python)
```python:to_csv.py
import sqlite3
import re
import pandas as pd

db_name = 'bookdata.db'
db = sqlite3.connect(db_name)

cur = db.cursor()

res =cur.execute("SELECT name FROM sqlite_master WHERE type='table';")

tablename = ''
for name in res:
    #print(name[0])
    tablename = name[0]

res = cur.execute("SELECT distinct(isbn) FROM %s ;" % tablename)

isbn_list = []

for v in res:
    #print(re.findall("(?<=\(\').*?(?=\'\,\))", str(v)))
    isbn_list += v

db.close()
#print(isbn_list)

dict = {'isbn':isbn_list}

df = pd.DataFrame(dict) 
    
# saving the dataframe 
df.to_csv('isbn.csv')

```

### ISBN を CSV に保存 (Ruby)

```ruby
require 'sqlite3'
require 'csv'

db = SQLite3::Database.open "bookdata.db"

db.execute("select distinct(isbn) from tbl_bookdata ;") do |data|
    if data[0] != nil then
        CSV.open("isbn.csv","a") do |f|
            f << [data[0]]
        end
    end
end
```

##関連記事

https://qiita.com/dauuricus/items/563cbcc9776f66cb672e

https://zenn.dev/kurocat/articles/eb233dc31bb285

https://zenn.dev/kurocat/articles/55565f019754cb

https://zenn.dev/kurocat/articles/96f88ef6d8d71b