Apache Solr で異体字同時検索

さて、今回は異体字同時検索です。クラウドソーシング翻刻に限らず、字体・字形の選択ポリシーが一定していなかったり徹底されていなかったりということは、テキスト文字起こし全般においてしばしばみられます。普通にデータを作成していても、國學院大學を国学院大学と書いてしまったり、慶應義塾大学と慶応義塾大学が混在するという例は割と見られます。しかし、いちいち両方の単語を検索するのは大変ですし、Google検索や各地の検索システムでは、両方を同時に検索してくれることが結構多いように思われます。ではそれは特殊な技術かと言えばそんなことはなくて、たとえばApache Solrでは設定するだけでそれができるようになります。そこで、とりあえずその設定の仕方も含めて、一緒にやってみましょう。

ではまず、前回の手順をおさらいしつつ、今回の手順を確認してみましょう。

  1. Apache Solrをインストール&起動
  2. コアの作成
  3. 検索項目の設定(省略可)⇒今回はここを!
  4. 検索対象ドキュメントの登録

前回の手順では省略した、「3. 検索項目の設定」をきちんと設定するのが今回のポイントです。今回は、前回作ったminhonコアはそのままにしておきつつ、 新たに minhon2 というコアを作ることにして、以下、前回と重なるものは解説はざっくりするか飛ばしてコマンドのみ再掲します。

1. Apache Solrをインストール&起動

Apache Solrの最新版をダウンロードして圧縮ファイルを伸張し、そのディレクトリに入ってからPower ShellのターミナルかWSLターミナル、Macのターミナルなどを起動して、以下のようにしてSolrを起動。 Power Shellの場合は、

> bin/solr start

WSLターミナルの場合は(多分MacOSやLinuxでも同様に)

$ ./bin/solr start

※ startできないと言われたら、エラーメッセージにあわせて対応します。よくあるのはJAVAがない、あるいは、JAVA_HOMEがない、というエラーメッセージですが、JAVAをインストールしてない場合は、「無料Javaソフトウェアをダウンロード」からJAVA (runtime Environment)をダウンロード・インストールします。JAVA_HOMEがない、というエラーメッセージの場合は、JAVA_HOMEをウインドウズで設定します。JAVA_HOMEの設定は、ググると色々やりかたがでてきますのでそちらを参考にしてください。

2. コアの作成

Power Shellの場合は、

> bin/solr create -c minhon3

WSLターミナルの場合は(多分MacOSやLinuxでも同様に)

$ ./bin/solr create -c minhon3

3. 検索項目の設定(省略可)

ここまでは前回と一緒ですが、次が少々異なります。「3. 検索項目の設定(省略可)」を敢えて行います。

ここでの手順は、

3-1. フィールドタイプの設定 3-2. フィールドの設定

という風になります。つまり、「異体字検索用辞書を使って検索用インデックスを作るフィールドタイプ」というのを設定した上で、 「異体字検索用インデックスを検索したいフィールドに割り当てる」という手順になります。では以下でやってみましょう。

3-1. フィールドタイプの設定

「異体字検索用辞書を使って検索用インデックスを作るフィールドタイプ」を作成するのですが、このためには、まず、「異体字検索用辞書ファイル」を用意して、そのファイルパスを設定に組み込む必要があります。 そこで、まずは、空の「異体字検索用辞書ファイル」を作成しておきましょう。

Power Shellの場合

> mkdir server/solr/minhon3/dict > New-Item -Type File server/solr/minhon3/dict/itaiji.txt

WSL Ubuntu18やMac OS、Linux等の場合

$ mkdir server/solr/minhon3/dict $ touch server/solr/minhon3/dict/itaiji.txt

そうすると、以下のフォルダに、itaiji.txtというファイルができているはずです。(以下の画像は空のフォルダです)

f:id:digitalnagasaki:20200126180629p:plain
itaiji.txt

そこで、itaiji.txtを開いて、試しに以下の異体字ペアの内容を入力して、UTF-8で保存しておきましょう。記号も含めてこのまま入力してください。

"圖"=>"図"

"學"=>"学"

これで準備は終了です。

次に、この異体字検索用辞書ファイルを使って異体字同時検索のできるフィールドタイプを作成します。以下のようにコマンドをうってみましょう。

Power Shellの場合:

> curl.exe -X POST -H 'Content-type: application/json' --data-binary '{
    "add-field-type": {
        "name": "text_cjkuni",
        "class": "solr.TextField",
        "positionIncrementGap": 100,
        "analyzer": {
            "charFilters": [{
                    "class": "solr.MappingCharFilterFactory",
                    "mapping": "dict/itaiji.txt"
                }],
            "tokenizer": {"class": "solr.StandardTokenizerFactory"},
            "filters": [
                {"class": "solr.CJKWidthFilterFactory"},
                {"class": "solr.LowerCaseFilterFactory"},
                {
                    "class": "solr.CJKBigramFilterFactory",
                    "outputUnigrams": "true"
                }
            ]
        }
    }
}' http://localhost:8983/solr/minhon3/schema

WSL Ubuntu , Linux, MacOSなどの場合:

$ curl.exe -X POST -H 'Content-type: application/json' --data-binary '{
    "add-field-type": {
        "name": "text_cjkuni",
        "class": "solr.TextField",
        "positionIncrementGap": 100,
        "analyzer": {
            "charFilters": [{
                    "class": "solr.MappingCharFilterFactory",
                    "mapping": "dict/itaiji.txt"
                }],
            "tokenizer": {"class": "solr.StandardTokenizerFactory"},
            "filters": [
                {"class": "solr.CJKWidthFilterFactory"},
                {"class": "solr.LowerCaseFilterFactory"},
                {
                    "class": "solr.CJKBigramFilterFactory",
                    "outputUnigrams": "true"
                }
            ]
        }
    }
}' http://localhost:8983/solr/minhon3/schema

これで、フィールドタイプ text_cjkuni が作成されました。

3-2 フィールドの設定

次に、このフィールドタイプが適用されるフィールドを設定します。これまで作ってきたドキュメント登録用ファイルの中の textConn というフィールドに、これを割り当ててみます。以下のコマンドを打ってみましょう。

Power Shellの場合:

> curl.exe -X POST -H 'Content-type: application/json' --data-binary '{
"add-field" : {
"name":"textConn",
"type":"text_cjkuni",
"indexed":"true",
"stored":"true",
"required":"false",
"multiValued":"false" }}' http://localhost:8983/solr/minhon3/schema

WSL Ubuntu , Linux, MacOSなどの場合:

$ curl -X POST -H 'Content-type: application/json' --data-binary '{
"add-field" : {
"name":"textConn",
"type":"text_cjkuni",
"indexed":"true",
"stored":"true",
"required":"false",
"multiValued":"false" }}' http://localhost:8983/solr/minhon3/schema

これで、textConnフィールドに対しては、異体字同時検索がかかるようになります。

4. 検索対象ドキュメントの登録

今回は、minhon3.jsonという登録用ドキュメントをまとめたファイルを新たに作ってみました。

https://vayu.dhii.jp/minhon_search/minhon3.json

これは、テキストの内容は同じですが、id というフィールドを新たに追加しています。このidフィールドを登録ドキュメントに設定しておくと、 検索インデックスの上書き更新が可能になりますので後々便利です。

それでは、minhon3.jsonをダウンロードして、それが置いてあるフォルダでターミナルを開いて(あるいは、現在ターミナルで開いているフォルダにminhon3.jsonを持ってきて(お勧め!))、 以下のようにして、検索対象ドキュメントの入ったminhon3.jsonを、新しく作ったコア minhon3に登録してみましょう。

Power Shellの場合

> curl.exe "http://localhost:8983/solr/minhon3/update?commit=true" --data-binary "@minhon3.json" -H 'Content-type:text/json;charset=utf-8'

WSL Ubuntu18やLinux、MacOS等の場合

$ curl "http://localhost:8983/solr/minhon3/update?commit=true" --data-binary "@minhon3.json" -H 'Content-type:text/json;charset=utf-8'

これで、フィールド「textConn」は、上記の二つの異体字の対応づけが行われた状態の検索インデックスが作成されました。

さて、どうなったか確認してみましょう。

異体字同時検索の設定をしたtextConnで「"東京大學圖書"」を検索すると、以下のように"numFound":31、つまり、31件のドキュメントがヒットしています。「"東京大学図書"」でも同様です。(二重引用符で囲むのを忘れないようにしてください!)

f:id:digitalnagasaki:20200126174308p:plain
marge_result

一方、同じデータだが異体字同時検索の設定をしていないフィールド、textNoKTで検索すると(「df」項目に「textNoKT」と入力して検索)、以下のように、"東京大学図書"では23件、"東京大學圖書"では8件となります。あわせて31件ですから、ちょうどぴったりですね。

f:id:digitalnagasaki:20200126174849p:plain
noitaiji2

f:id:digitalnagasaki:20200126174654p:plain
noitaiji1

これが異体字同時検索の基本的なやり方です。

異体字のペアを後から追加する場合:

異体字のペアを増やしたければ、上記のitaiji.txtにペアを追加していけばいいのですが、こちらは複数文字でも対応してくれるようで、たとえば以下のものなどもできるようです。

"圕"=>"図書館"

ただし、ペアを増やしたら、Apache Solrを再起動してあげなければなりません。再起動は、以下のコマンドで行います。

> bin/solr restart -p 8983

(WSLとかMacでも一緒です)

そして、再起動しただけだと、矢印方向の変換しか効きません。これだと逆方向が検索できなくなってしまうので、「検索インデックスへの再登録」も行う必要があるようです。 ここで、先ほどの、「idを付与」した minhon3.json が活きてきます。再登録は、登録時と同じコマンドで大丈夫です。(もっと効率の良い方法があるかもしれませんが)

Power shellの場合は:

> curl.exe "http://localhost:8983/solr/minhon3/update?commit=true" --data-binary "@minhon3.json" -H 'Content-type:text/json;charset=utf-8'

WSL Ubuntu やLinux、MacOSなどの場合は: $ curl "http://localhost:8983/solr/minhon3/update?commit=true" --data-binary "@minhon3.json" -H 'Content-type:text/json;charset=utf-8'

これで大丈夫かと思います。

複数文字対応ができるということは、単語・フレーズ単位での表記揺れのようなことにも対応できますし、さらに言えば、たとえば、"安震"=>"安政大地震"と登録すれば、「安震」で「安政大地震」を検索するようなこともできるようになります。

ということで、Apache Solrの異体字同時検索、ぜひお試ししてみてください。

Apache Solr でとりあえず全文検索

前回記事では、Apache Solrを起動するところまでいきました。起動しただけではなんともなりませんので、まずは全文検索をできるようにしてみましょう。

Apache Solrで全文検索をできるようにするためには、大体以下のようなプロセスがあります。

  1. Apache Solrをインストール&起動
  2. コアの作成
  3. 検索項目の設定(省略可)
  4. 検索対象ドキュメントの登録

このうち「3. 検索項目の設定」を行うことによって、より便利な検索ができるようになりますが、省略してもそこそこ便利な検索ができます。そこで、 まずは、ここをスキップして、「2.コアの作成」をした上で検索対象ドキュメントの登録をしてみましょう。

2. コアの作成

コアの作成は、Webインターフェイスからでもできるらしいのですが、後々のことを考えて、CUI(コマンドラインインターフェイス)でやってみましょう。前回記事のように、SolrのディレクトリでPower ShellやWSLターミナルなどを開いた上で、

Power Shellの場合は、

> bin/solr create -c minhon

WSLターミナルの場合は(多分MacOSやLinuxでも同様に)

$ ./bin/solr create -c minhon

としてみてください。そうすると、

「Created new core 'minhon'」などと表示され、「minhon」というコアができます。

コアができたどうかを確認するべく、再び http://localhost:8983/ にアクセスしてみましょう。

ここで、左側のメニューの「Core admin」をクリックすると、先ほど作った「minhon」が以下のように表示されるはずです。

f:id:digitalnagasaki:20200125133730p:plain
core admin

そして、左側のメニューの「Core selector」をクリックすると

f:id:digitalnagasaki:20200125133915p:plain
core selector

以下のように、先ほど作った「minhon」が選べるようになっているはずです。

f:id:digitalnagasaki:20200125134009p:plain
core selector minhon

これで、コアができました。そうしましたら、次は、このコアに対してドキュメントを登録していくことになります。

3. 検索項目の設定(省略可)

今回は省略します。省略すると、自動的に無難な項目設定が行われます。検索の利便性を高めたい場合には、これを自分で詳細に行うことになります。

4. 検索対象ドキュメントの登録

検索対象ドキュメントの登録は、Webインターフェイスからでもできるようですが、大量ドキュメントの登録にはあまり向いていないので、ここではCUIで行うことにします。

準備(Power Shellのための):

最近は、LinuxやMacOSでこういうことを行う場合、curlというコマンドを使うことが多く、割と便利なのですが、PowerShellでもインストールすれば使えますので、Power Shellで行う場合にはcurlをこちらからインストールしましょう。

(なお、Power Shellでは標準でもcurlというコマンドが使えますが、標準のコマンドはcurlではない別のコマンドのエイリアスになっているというおそろしい状態ですので、気をつけてください。Power Shellでcurlを他のOSと同様に使うためには、PATHの設定をすればいいのですが、エイリアスを外す操作も必要かもしれず、ちょっと面倒です。上記のcurlをインストールした後に curl.exeという風にコマンドを打てば、インストールした方を使ってくれるようですので、以下ではPower Shellの場合は curl.exe、それ以外は curlとして、それぞれコマンドをコピペできるように用意していきます。)

ドキュメントの用意:

最近では、この種のデータの登録はJSONでデータを作るのが一般的です。特にPythonを使っている場合、リスト+辞書を作ればほぼそのまま使えますので、色々楽です。今回は、「みんなで翻刻」の「石本コレクション」の翻刻データをベースにしてご用意したのがこちらです。「みんなで翻刻」の利用条件はCC BY-SAですので、形式を改変したものも CC BY-SAとしてご利用ください。このファイルを minhon.json というファイル名で保存したという前提で続けます。

Power Shellの場合:

> curl.exe "http://localhost:8983/solr/minhon/update?commit=true" --data-binary "@minhon.json" -H 'Content-type:text/json;charset=utf-8'

WSLのUbuntu18やMac OS等の場合:

$ curl "http://localhost:8983/solr/minhon/update?commit=true" --data-binary "@minhon.json" -H 'Content-type:text/json;charset=utf-8'

という風にすると、ドキュメントが minhonコアに登録されて検索できるようになります。検索は、まずは Webインターフェイスからやってみましょう。再び http://127.0.0.1:8983/ にアクセスしてみてください。

そして、 Core selector で「minhon」を選ぶと、以下のように登録ドキュメント数が表示されているはずですが、いかがでしょうか。(!この数字は、以下のものと皆さんのお手元のものは異なる場合がありますが、1以上でしたら大丈夫ですのであまり気にしないでください!)

f:id:digitalnagasaki:20200125141937p:plain
doc_number

これがうまくいっていたら、左側のメニューの「query」をクリックして、「Execute Query」ボタンをクリックしてください。そうすると以下のように、全てを対象にして検索した結果が表示されるはずです。検索結果は標準ではこのようにJSON形式で戻ってきます。"response"⇒"numFound"でヒットした件数が表示されているはずです。

f:id:digitalnagasaki:20200125142338p:plain
numfound

上記の画面でまずもう少し注目していただきたいのは、「q」というテキストエリアと「rows」というテキスト入力フィールドです。それぞれ「:」と「10」となっています。「rows」は、検索結果の表示数ですので、適宜増やしたり減らしたりしてください。(増やしすぎるとブラウザがフリーズするので注意してください)。「q」は、検索項目と検索語を入力できるようになっていて、「検索項目:検索語」という風に書くことになっているのですが、「:」で、すべての項目のすべての言葉、という風に検索できます。ここで例えば「project:"石本"」という風に入力して「Execute Query」ボタンをクリックすると、「project」項目に「石本」が含まれるドキュメントがヒットします。

f:id:digitalnagasaki:20200125143028p:plain
query_example

では次に、本文を検索してみましょう。本文のフィールドは、このデータではいくつか用意されていますが、textConnというのフィールドが一番検索しやすいものですので、「textConn:富山」として検索してみましょう。そうすると、以下のように68件ヒットします。富山が68件も!・・・と思ってヒットしたデータをよくみてみると、どうやら「富士山」も一緒にヒットしているみたいです。

f:id:digitalnagasaki:20200125145608p:plain
toyama1

つまり、文字を分割してそれらをAND検索してしまっているようです。これをきちんと「富山」として検索したい時は「textConn:"富山"」のように、検索語を二重引用符で囲めば、その文字列として検索してくれるようになります。

f:id:digitalnagasaki:20200125150123p:plain
toyama2

そうすると、「"numFound":3」となりますね。この検索結果には皆、文字列「富山」が含まれているはずです。

ここでもう一つ注目していただきたいのは、以下の黄色いマーカーで囲んだ箇所です。このURLが、この検索を行うためのURLということになります。

f:id:digitalnagasaki:20200125150555p:plain
searchurl

Power Shellなら

> curl.exe 'http://127.0.0.1:8983/solr/minhon/select?q=textConn%3A%22%E5%AF%8C%E5%B1%B1%22'

WSL UbuntuやMacOS、Linux等では

$ curl 'http://127.0.0.1:8983/solr/minhon/select?q=textConn%3A%22%E5%AF%8C%E5%B1%B1%22'

という風にすると、JSONで検索結果が戻ってくるところを確認できると思います。

なお、JSON形式で得られるよりも、もっと簡単にエクセルなどに取り込んでしまいたい、という人には、検索メニューの中の「wt」というセレクタから「csv」を選択することもできますので、こちらも試してみてください。

f:id:digitalnagasaki:20200125151134p:plain
csv

ここでも、URLの箇所を確認してみてください。URLに「&wt=csv」というのが追加されていると思います。URLで検索結果を得る場合には、このようにしてパラメータを追加することで色々な形式のデータを得ることができます。

ということで、とりあえずドキュメントを登録して検索するところまではできるようになりました。今回はここまでとしましょう。

Apache Solr - 全文検索、異体字同時検索、ファセット…

ここ5年ほど、Webでの全文検索は、どこも大体似たような感じの機能に落ち着いてきているように思います。全文検索ができて、異体字も同時に検索ができて、ファセットから絞り込みができて…。

この背景には、Apache Luceneという全文検索ソフトウェアの存在があります。プログラミング言語Javaで書かれておりMac でもWindowsでもLinuxでも動くフリーソフトウェアです。とにかく速くて、我々が思いつく機能は大体用意されています。多言語対応も充実していて、日本語を分かち書きして単語ごとに検索する、といったこともできます。つまり、日本語分かち書きソフトも組み込まれているということです。これがあちこちのサイトで使われるようになっているのです。たとえば、最近話題のジャパンサーチでも検索にはこれを使っているそうです。

ところで、Javaで書かれているということは、手元のパソコンでも動かせるのではないか…?と誰もが思うところです。その通り、パソコン上でも動きます。色々な動かし方があるようですが、比較的簡単なのは、Apache Solrを使ってブラウザ経由で動かすという方法です。Apache SolrにはこのApache Luceneが組み込まれているので、SolrをインストールすればLuceneが使えるようになるという具合です。ちなみに、ジャパンサーチではElasticsearchを採用することでApache Luceneを使っているようです。基本的に、超大規模なものになるとElasticsearchを使った方がよさそうな感じです。

さて、このApache Solrですが、個人的経験としては、SAT大蔵経テキストデータベース 2018年版を作った時に採用しておりまして、ここで採用するまでには色々な試行錯誤をしておりました。もちろん、Elasticsearchも試してみた、というか、別の試験的なシステムに採用してみたことがありますが、Apache Solrの方が枯れていてわかりやすいような感じがしたので、こちらにしました。そこら辺の経緯は『デジタル学術空間の作り方』(オープンアクセス本)のChapter 1にも書いていますので、そういうことに興味がおありでしたらぜひご一読ください。

その後、最近になって、前回記事にてご紹介した「みんなで翻刻」の検索システムの開発に際して採用しました。と言ってもこちらは非常に簡素な使い方で、ファセットも使ってないようなものですが、せっかくですので少しApache Solrの使い方などをご紹介しておきたいと思います。これは、ググってみて、新しくてまとまった解説があんまりないことがちょっと気になったということもあります。

Apache Solrですが、使うためにはJava環境をインストールする必要があります。Mac OSとかWindows10のWSLだとJava環境も含めて比較的簡単にインストールできると思いますが、Windows10の通常のWindows環境ですと、環境変数JAVA_HOMEの設定をしなければならないようで、微妙に大変でした。とにかく、「java環境 インストール」などとしてググると色々やり方が出てきますので、そちらをご参照ください。WSLでUbuntu18.04だと多分こういう感じです。 $ sudo apt install -y openjdk-11-jdk

次に、Apache Solr のダウンロードです。こちらの頁から、「most recent Apache Solr release.」などと書かれたバージョンのBinary releasesのtar.gzかzipファイルをダウンロードします。tar.gzと言われてなんだかよくわからない場合はzipの方をダウンロードするのがおすすめです。現在の最新安定版は8.4.1ですので、たとえばこちら

なお、ここからはWindows 10とWindows 10 WSLのUbuntu18.04環境の話をしていきますが、MacOSは多分WSLと大体同じやり方でできるのではないかと思いますので適宜読み替えてください。

さて、ファイルをダウンロードしたら圧縮ファイルを伸張してそのフォルダに入ります。このとき、おそらく、ターミナルでCUI環境でないとうまく動かないのではないか…と思いますので、このフォルダでターミナルを開いてみましょう。 Windows10の場合はパワーシェルのターミナルを開くことになると思います。パワーシェルの場合は、エクスプローラ画面の何もないところをシフトキー+右クリックすると以下のようなダイアログが現れますので、「PowerShellウインドウをここで開く」を選択するとパワーシェルが開きます。 f:id:digitalnagasaki:20200123034751p:plain

パワーシェルを開くと以下のように、文字ベースでコンピュータに命令を出せるようになります。

それから、Windows10の場合は、このあたりでこちらからcurlをインストールしておいて、パワーシェルから使えることを確認しておいてください。

WSLであれば、現在のところ、大体以下のようになっていると思います。(なお、今回用意した事例で使っているApache Solrのバージョンは8.4.0で、一つだけ古いです。)

f:id:digitalnagasaki:20200123033521p:plain

そこで、以下のように、コマンドを入力してEnterキーをおしてみます。

PowerShell:

\solr-8.4.0> bin/solr start

WSL+Ubuntu18.04:

nagasaki@LV8:solr-8.4.0$ ./bin/solr start

そうすると、色々なメッセージがでてきます。メッセージが止まったなと思ったら、以下のURLにWebブラウザでアクセスしてみましょう。

http://localhost:8983/

ここで、このサイトのApach Solrの管理画面にアクセスできるようになります。

ところで、眠くなってきてしまったので、続きはまた次回としたいと思います。引き続き、よろしくお願いいたします。

みんなで翻刻のWeb API!

Web APIという言葉はあちこちで耳にされることがあると思います。API=アプリケーション・プログラミング・インターフェイス、ということで、つまり何かを作ったり動かしたりする時にやりとりをする機能、をWebで使えるようにしたもの、と考えておけばいいでしょうかね。Webブラウザの裏側でサーバ同士でデータをやりとりできるというのが肝で、各所のデータを統合した便利な機能を提供してくれたりするのはまさにWeb APIの恩恵によるものです。

世はWeb API花盛りです。花盛り過ぎて、それについて話題にすることもあまりなくなってきてはや10年、といったところです。あってもなくても、それはそれぞれのポリシーによる選択の結果をあらわしているに過ぎない、というところまで時代は進んでいるのかなと思っております。

しかしながら、一方で、開発途中のシステムだと、「Web APIも用意したけどあとまわし」ということも散見されます。また、Web APIを用意しても、「どういう構造になっているのか」「どういう風に使えるのか」を知らしめる仕様書のようなものが公開されていないと、せっかく用意したのにあまり使ってもらえないということもあります。逆に、あまり広く使われすぎると困るので仕様書は広く公開しないようにしていることもあるようです。

前置きが長くなりましたが、前回記事で紹介した「みんなで翻刻⇔みんなで検索」が実現できたのも、このWeb APIのおかげです。開発者である橋本雄太氏が、ご多忙な中で1時間でささっと作ってくださいました。ここでのWeb APIはそれほど複雑なものではなく、しかし、本文検索サイトを構築しつつ本家に更新があったらそれを検知して検索インデックスを更新する、という仕組みを作成するにあたって必要となる機能を大体網羅しているものでした。

というわけで、今回は、みんなで翻刻Web APIの紹介を少しだけ試みます。

まず、「現在登録されているプロジェクトの一覧」を取得するWeb API (のURL)は下記の通りです。

https://us-central1-honkoku2.cloudfunctions.net/api/projects

こちらにアクセスすると、プロジェクトの一覧とそれぞれの説明等に加えて、プロジェクトのIDが返戻されます。このフォーマットはWebでよく用いられるJSONというフォーマットになっています。これは、このまま見てもごちゃっとしてよくわかりませんが、全部コピペしてJSON整形ツールにかけると、構造を反映した形で見やすく表示できます。たとえばこのサイトだと以下のような感じになります。

f:id:digitalnagasaki:20200106210839p:plain
JSON画像

つまり、「みんなで翻刻(以下、「みん翻」と略します)」で現在展開されているプロジェクトの情報を得たければ、このWeb APIで取得できる、ということになります。

さて、筆者が今回作ったのは「本文検索」でした。そうすると、ここで取得できる情報だけでは検索ができません。「みん翻」のデータは、

プロジェクト>コレクション>エントリ>各頁(画像)

という風になっているようですので、次に、個々のプロジェクトが持っている「コレクション」の情報を取得します。この際には、上記のWeb APIで得られる各プロジェクトの"id"が必要になります。 これを何らかの方法で取得して、今度は以下のURLにてアクセスします。たとえば、「日本の仏典を翻刻!」のプロジェクトの場合、idは「21dzk」ですので、URLの最後に以下のようにそのプロジェクトidを付与してアクセスします。

https://us-central1-honkoku2.cloudfunctions.net/api/projects/21dzk

そうすると、このプロジェクトがどういうコレクションを持っているかという情報が得られます。これもやはり、先ほどと同じJSON形式で返戻されます。コレクションにはその説明もありますが、今回必要なのは本文ですので、さらに下の階層へと向かいます。このコレクションに含まれる(=下位にある)エントリを取得すべく、とりあえずここで入手できたコレクションのidを用いて以下のようにWeb APIにアクセスしてみます。ちなみに、以下のURL中の「2AC632E5683C37F7C69425EA497007FA」という箇所が、上記のURLから得られたコレクションのidです。

https://us-central1-honkoku2.cloudfunctions.net/api/collections/2AC632E5683C37F7C69425EA497007FA

このURLにアクセスして返戻されたJSONデータを整形すると以下のようになります。

f:id:digitalnagasaki:20200106223750p:plain
jsonデータの画像

ここで、"entries": となっているキーの値が複数で列挙されています。これらの値がそれぞれのエントリのidということになります。この中の一つのエントリidを使って以下のURLを作ってアクセスしてみます。

https://us-central1-honkoku2.cloudfunctions.net/api/entries/6AC3A275993D8AC73941D97376426A61

ここでは、「6AC3A275993D8AC73941D97376426A61」がエントリidの部分になります。

このURLにアクセスすると、このエントリに含まれる画像とそれに対応する翻刻テキストのデータが入手できます。あとは、このテキストデータを検索できるようにして、画像ともリンクできるようにすればOKです。ちなみに、翻刻テキストデータは"transcriptions"以下にリストとして入っていて、各リストは辞書になっていて、その中の「text」というキーが翻刻テキストデータのようです。また、更新日付のキーは「timestamp」、といった案配です。画像に関しては、IIIF対応画像の翻刻をシステムの基盤の一つとしていることもあるのかもしれませんが、IIIF Manifestを継承した形になっているようで、これをきちんと理解しようと思うなら、IIIF Presentation API 2.1をみておくとよいかもしれません。

というわけで、今回は、Web APIで本文を取得する方法を最短距離でまとめてみました。

エントリのJSONデータからどのデータを取り出してどのように使ったか、という話は、また次の機会としましょう。

みんなで翻刻⇔みんなで検索

みんなで翻刻 」というサイトを知らない人は、このブログの読者にはいらっしゃらないと思います。しかしながら、○○業界なら必須アイテムになりそうだし、大体の人は知ってそうな…と思ってご紹介してみると意外とそうでもないことが多く、人が自分(の仕事)にとって必要(になりそう)な情報を入手する機会を作るということがいかに難しいことか、このサイトを話題にした際にも時折感じることです。

ところで、この年末年始の半分は、この「みんなで翻刻」の検索システムの作成に費やしておりました。日本の仏典の翻刻も最近取り込んでいただいたのですが、作業を進めていくにあたり、とりあえず、返り点や改行、いくつかのタグをまたいだ全文検索、そして異体字もまとめて検索できるようになっていると便利なのではないか、ということで、開発者の橋本雄太氏にお願いしてWeb APIを作っていただきました。これを使って「みんなで翻刻」のテキストデータをごそっと取り出して少し手を加えて検索用インデックスを作り、さらにそれを検索できるインターフェイスを作成する、という作業でした。

honkoku.dhii.jp

上記のリンクをご覧いただくと、とりあえず検索ができるようになっています。検索結果から、「みんなで翻刻」の該当頁に戻れるようになっていて、そこで画像とテキストが確認できます。なお、リンク先は、この検索システムで何ができるか一目でわかるように、検索結果の状態を表示しています。

いつもの作り方であれば全然時間はかからないのですが、今回は、若い人達に「もう古いっすよそれ」と言われるやり方を捨てて、「新しいWebアプリの書き方」を勉強しながら作成しました。Python3でFlaskというマイクロフレームワークを使ってみたのでした。ついでにjQueryというJavascriptのライブラリもやめてVue.jsに移行しようと思ったのですが、こちらは思ったほどすぐに簡単に動かすことができず、いつものjQueryで書いたら一瞬で書き終わったので、こちらはもう少し使い続けることになりそうです。

全体の構成としては、Web APIを用いて取得したテキストデータをApache Solrに入れて、それをPython3からURLのクエリを投げて結果をFlaskで整形して表示、という感じです。異体字同時検索はApache SolrのcharFilterで自作のマッピングファイルを読み込ませています。メニューの絞り込みとか継承にjQueryを少し使っていて、表示のところでもBootstrap4を使っているのでそちらでもどこかでjQueryが使われているかもしれない、という状況です。

当面の目標は、「みんなで翻刻」でデータの入力・更新があったらそれを検索結果に反映させることなのですが、その機能はまだできていません。そろそろいくつかの事務仕事に集中しなければなりませんので、そちらが片付いてからまた取り掛かりますので、もう少し時間がかかりそうです。ですが、そこまでは必ずやります。

せっかくですので、ここで得たノウハウをブログに開陳していきたいところなのですが、今はちょっと時間がないので、少し時間ができたらぼちぼちやっていきたいと思います。

ということで、本年もよろしくお願いいたします。

オープンアクセス出版『デジタル学術空間の作り方』が刊行されました

ここしばらく、文章を書く時間のほとんどを費やしていたものが、ついに刊行されました。

もうじきアマゾンで紙版が入手可能になる予定ですが、

https://www.amazon.co.jp/exec/obidos/ASIN/490965819X/hanmotocom-22www.amazon.co.jp

科研費事業の成果報告という位置づけの刊行物であり、オープンアクセスでも入手できるようになっています。このような試みに親切にも対応してくださった『文学通信』社のリポジトリにて全文を無料で閲覧できます。

repository.bungaku-report.com

第一部では、これまでほとんど語られることのなかったSAT大蔵経テキストデータベース研究会の通史が綴られています。後半部分の、テキストデータベース公開以降の話はあちこちで論文が出ていますのでCiNii等で探せばほとんどの話はみつかると思いますが、前半部分、とくに研究会の設立からテキストデータ完成に至る経緯は、日本でのデジタルアーカイブ構築の一事例としても、ある研究分野のデジタル技術への向き合い方の一例としても、興味深いものと思います。身内を褒めるようで恐縮ですが、一連のネゴシエーションを乗り切ってこられた関係者の先生方、とりわけ下田正弘先生の御尽力には改めて圧倒されるものがあります。

本書について個人的に面白いと思うところは第二部にも多くあります。仏教研究に長らく関わりつつデジタル技術を何らかの形で活用してきた方々、資料としての仏典に含まれる情報をデジタル技術を用いて探求する方々など、それぞれの立場から、仏典をめぐるデジタル技術の状況が語られており、情報技術の適用の仕方としても、テキスト研究のこれからを考える上でも、様々なヒントがちりばめられています。 第二部の個々の記事の解説はこちらにてご覧いただけますので、試しに読んでみる前にちょっと目を通してみていただくとご参考になるかもしれません。

まだまだできていないことも多いですが、これからも、デジタル学術空間をどう作っていくか、試行錯誤を続けつつ、良さそうなものは皆さんと共有しながら続けていきたいと思っております。

個人的には、あと1冊刊行しなければならないものがあるので、ブログ書きはもう少し遅延し続けるかもしれませんが、ここ数ヶ月に比べれば、この後しばらくは更新頻度は多少高まると思います。

今後とも、よろしくお願いいたします。

IIIF manifestファイルを簡単に作ってみよう

今回は、最低限のIIIF manifestファイルを簡単に作ってみる方法をご用意してみました。経緯等は後回しにして、とりあえずやり方をば。

画像ファイルは、前回記事のやり方でpyramid tiffになっているものとします。拡張子は.tif。 そして、一つの資料ごとに一つのフォルダ(ディレクトリ)にまとめられているものとします。

そこで、まずは以下のようなCSVファイルを作成します。エクセルで作ってCSV UTF-8で保存すれば 大丈夫かと思います。(一応実験済み)

dir,title,license,attribution,viewingDirection,translator,publisher
0040_01,大方廣佛華嚴經 序~巻第二,https://creativecommons.org/licenses/by/4.0/deed.ja,東京大学総合図書館・SAT大蔵経テキストデータベース研究会,right-to-left,實叉難陀,鉄眼禅師
0040_02,大方廣佛華嚴經 巻第三~六,https://creativecommons.org/licenses/by/4.1/deed.ja,東京大学総合図書館・SAT大蔵経テキストデータベース研究会,right-to-left,實叉難陀,鉄眼禅師
0040_03,大方廣佛華嚴經 巻第七~十,https://creativecommons.org/licenses/by/4.2/deed.ja,東京大学総合図書館・SAT大蔵経テキストデータベース研究会,right-to-left,實叉難陀,鉄眼禅師
0040_04,大方廣佛華嚴經 巻第十一~十四,https://creativecommons.org/licenses/by/4.3/deed.ja,東京大学総合図書館・SAT大蔵経テキストデータベース研究会,right-to-left,實叉難陀,鉄眼禅師
0040_05,大方廣佛華嚴經 巻第十五~十七,https://creativecommons.org/licenses/by/4.4/deed.ja,東京大学総合図書館・SAT大蔵経テキストデータベース研究会,right-to-left,實叉難陀,鉄眼禅師
0040_06,大方廣佛華嚴經 巻第十八~二十,https://creativecommons.org/licenses/by/4.5/deed.ja,東京大学総合図書館・SAT大蔵経テキストデータベース研究会,right-to-left,實叉難陀,鉄眼禅師

最も大事なのは、一番左の列です。ここには、画像のフォルダ(ディレクトリ)名を入れておきます。前後の スラッシュは無しです。ここの情報を使って画像フォルダと突合させますので、これは必ず正確に記述してください。 (コピペ推奨です)

その他の内容ですが、まず、一行目は項目を書いて、二行目からコンテンツに対応する各種情報を書いていきます。titleはタイトルを書いておきます。 license (利用条件), attribution (帰属)、viewingDirection(読んでいく方向)あたりは書いておいた方がよいです。特に 右から左にページめくりしていくような物(縦書きの日本語・漢文資料など)はviewingDirectionのところに right-to-leftを書いておくと後々いいことがあります。 licenseは、ライセンス(利用条件)情報が書かれたベージのURLを書いておくことに なっています。なお、項目名については、IIIF Presentation APIで定められているものは英語で書いた方がよいですが、 それ以外の独自の項目名は日本語でつけても今のところはあんまり問題ありません。

繰り返しになりますが、エクセルで作った場合はCSV UTF-8で、そうでなければ普通にUTF-8で保存して いただけば大丈夫かと思います。ここでは仮にファイル名を bib.csv としておきます。

それから、準備として、Python3から画像の大きさを取得できるように、以下のようにモジュールをインストールしておきます。

% sudo pip3 install pillow

さて、次に、以下のスクリプトを回して見ます。

#!/usr/bin/python3
import sys
import glob
import os
import csv
import json
from PIL import Image
base_url = 'https://candra.dhii.jp/iiif/tetsugen/'
#ここ↑はマニフェストが設置されるURLに応じて書き換える
base_image_url = 'https://candra.dhii.jp/iiifimgs/tetsugen/'
#ここ↑はIIIF Image APIのURLに応じて書き換える
all_bib = {}
all_bib2 = {}
bib_title = []
mani_keys = ['dir','title','license','attribution','within','logo', 'description','viewingHint','viewingDirection']
#ここ↑は、IIIF Presentation APIにて、トップレベルで規定される項目を入れておきます。必要に応じて適宜増やしてください。
with open(sys.argv[1], newline='', encoding='utf_8_sig') as csvfile:
  spamreader = csv.reader(csvfile, delimiter=',', quotechar='"')
  rn = 0
  for row in spamreader:
    if rn == 0:
      bib_title = row
    else:
      each_bib = {}
      each_bib.update(zip(bib_title,row))
      link_name = row[0]
      all_bib[link_name] = each_bib
    rn = rn + 1;
#print (all_bib)

for key in all_bib.keys():
  each_manifest = {}
  all_meta = []
  file_dir0 = key
  glob_name = key+"/*.tif"
  if os.path.isdir(key):
   list_file_names = sorted(glob.glob(glob_name))
   if len(list_file_names) == 0:
     glob_name = key+"/*.ptif"
     list_file_names = sorted(glob.glob(glob_name))
   for item in all_bib[key]:
     if item not in mani_keys:
       each_meta  = {}
       item_value = all_bib[key][item]
       each_meta['label'] = item
       each_meta['value'] = item_value
       all_meta.append(each_meta)
   each_manifest['@id'] = base_url+key+'/manifest.json'
   each_manifest['@type'] = 'sc:Manifest'
   each_manifest['@context'] = 'http://iiif.io/api/presentation/2/context.json'
   each_manifest['metadata'] = all_meta
   for mani_key in mani_keys:
     if all_bib[key].get(mani_key):
       if mani_key == 'title':
         each_manifest['label'] = all_bib[key][mani_key]
       elif mani_key != 'dir':
         each_manifest[mani_key] = all_bib[key][mani_key]
   cn = 0
   sequence = {}
   canvases = []
   for file_path in list_file_names:
     service = {}
     resource = {}
     mani_image = {}
     canvas = {}
     file_dir = os.path.split(file_path)[0]
     if os.path.isdir(file_dir):
       cn = cn + 1
       canvas_number = 'p'+str(cn)+'.json'
       image_url_id = base_image_url+file_path
       service['@context'] = 'http://iiif.io/api/image/2/context.json'
       service['@id']  = image_url_id
       service['profile'] = 'http://iiif.io/api/image/2/level1.json'
       img = Image.open(file_path)
       width, height = img.size
       resource['@type'] = 'dctypes:Image'
       resource['format'] = 'image/jpeg'
       resource['width'] = width
       resource['height'] = height
       resource['@id'] = image_url_id+'/full/full/0/default.jpg'
       resource['service'] = service
       mani_image['@type']  = 'oa:Annotation'
       mani_image['motivation']  = 'sc:painting'
       mani_image['resource']  = resource
       mani_image['@id']  = base_url+file_dir+'/annotation/'+canvas_number
       mani_image['on']  = base_url+file_dir+'/canvas/'+canvas_number
       canvas['label'] = 'p. '+str(cn)
       canvas['images'] = []
       canvas['images'].append(mani_image)
       canvas['width'] = width
       canvas['height'] = height
       canvas['@type'] = 'sc:Canvas'
       canvas['@id'] = base_url+file_dir+'/canvas/'+canvas_number
       canvases.append(canvas)
   sequence['@id'] =  base_url+file_dir0+'/sequence/s1.json'
   sequence['@type'] =  'sc:Sequence'
   sequence['label'] =  'Current Page Order'
   sequence['canvases'] = canvases
   each_manifest['sequences'] = []
   each_manifest['sequences'].append(sequence)
   write_file_path = file_dir0+'/manifest.json'
   with open(write_file_path, mode='w') as f:
     json.dump(each_manifest, f, ensure_ascii=False)

ちょっと長いですが、こちらを mk_manifest.pyというファイル名で保存します。 ただし、二箇所、どうしても書き換えねばならないところがありますので、 その箇所にコメントをつけています。その二箇所は 環境に応じて書き換えてください。

ここで、各ファイルの位置関係は、以下のようになっています。

---mk_manifest.py

---bib.csv

---0040_01---tiffファイル群

---0040_02---tiffファイル群

---0040_03---tiffファイル群

---0040_04---tiffファイル群

この状態で、以下のようにコマンドを走らせます。

% python3 mk_manifest.py bib.csv

そうすると、各画像フォルダの中に manifest.json ファイルができていると思います。これで、できあがりです。 ディレクトリ構成も適正になっている場合には、manifestファイル中のこのマニフェストファイルのURIを 示す@idを使ってWebブラウザで表示させてみてください。人間の目では見づらいですが、こんな風になって いれば多分大丈夫です。あとは、MiradorやUniversal viewer、IIIF Curation viewer、TIFYなどに読み込ませれば 普通に表示できるはずです。

閑話休題

さて、これを作った経緯についても簡単に書いておきますと、IIIF Manifestの書き方がよくわからない、という 話をしばしばいただいていて、そこがなんとなくハードルになっているように感じたためです。すでに 江草氏による解説記事も用意されていますが、もう少し別の角度から情報提供してみてもいいのかもしれないと 思って、こういうものをちょっと作ってご紹介してみました。

もう少し言えば、内製でこれから勉強して取り組まなければならない人がいる、ということを風の噂で耳にしたということもあります。

この記事は、少しだけプログラミングができる人を対象としたものです。ただ、本当に少々でいいというのが、上記のものを 見ていただくとわかると思います。CSVファイルの内容を読み込んで、画像の大きさとファイル名も読み込んで、 それらをIIIF Manifest の形にあわせてlist とdictを組み合わせて構造化し、最後にjsonとして書き出しをする、という 形になっています。画像フォルダの階層がもっと深い場合は glob.glob() に読み込ませるワイルドカードを 少し変更してみる、などの手立てが必要です。とはいえ、もっとエレガントな書き方もできますので、改良版を作成&公開してくださる人がおられたら 大変ありがたいところです。

画像フォルダとmanifestファイルの位置関係については、色々なやり方があると思いますので、場合によっては、 それにあわせてmanifestファイルの保存場所も変更したりする必要があるかもしれません。そこら辺は適宜 修正する必要があると思いますが、どうしてもよくわからないけどなんとかしたい・しなければならない という人はご相談ください。

なお、元々はPHPで書いていたものを簡素化してPythonで書き直したものですので、書き方にPHPっぽい(Pythonに最適化されてない)ところがあるかもしれませんが、 その点はご容赦ください。