ライブラリアン・コラム
大量のファイルから必要情報を抽出・整形する―― CiNii APIのXMLを対象に
今満 亨崇
2024年8月
はじめに
複数のファイルからデータを抜き出して1つのExcelファイルにまとめる、あるファイルの記述ルールを別のルールに変換する、といった作業は時折生じる。各種計測機器やシステムが出力するデータを分析する時や、AシステムからエクスポートしたデータをBシステムにインポートする時、管理用の業務データを提出用のレポートにまとめる時などが思い浮かぶ。こういった作業は極力コンピュータにやらせたいが、そのためのプログラムやコマンドを書くスキルは決して汎用技能とは言えない。対象となるデータが多ければ多いほど、作業自体を諦めてしまう。
本記事では、システムが出力するデータを分析する想定で、大量のXMLファイルから必要な情報を抽出する方法を検討してみた。XMLをパース(構造を解析してその階層構造に沿って必要な情報を抽出)するプログラムを書くのが王道だが、これは学習コストが高いため、極力普段使用しているアプリケーションで処理する方法を考える。
事前準備
具体的には木下の行った蔵書評価の分析作業(木下 2023)をExcelで実施できるように、CiNii APIのXMLからデータを抽出し、1行1書誌かつ、項目毎にセルに分かれたデータを生成する。元となるXMLは前稿で作成したCiNii APIの書誌詳細XML群を使用するので、未読の方は先にそちらの記事を参照してほしい。もしくはCiNii Booksの適当な書誌詳細画面を表示して、URL末尾に".rdf"を追記してアクセスするとXMLが表示されるので、それをメモ帳にコピー&ペーストして、拡張子は.xmlで保存したものをいくつか用意しておいてほしい。ファイル名はd*.xmlの形式とする(*は任意の数字)。環境によっては.rdfのファイルが直接ダウンロードされる場合もあり、その時はファイル名の変更だけでよい。このXMLファイル群は「C:\cinii\2_detail」ディレクトリ内に保存されているものとする。
今回の記事では、1. コマンドプロンプトですべてのXMLファイルから必要な情報がある行を抽出し、2. Wordでそのデータを1行1書誌に整え、3. Excelでセル毎に項目を分割する流れで行う。また、本記事で使用するサンプルのExcelファイル(以下、「サンプルファイル」という)を公開しているので併せてご確認いただきたい。
コマンドプロンプトでXMLファイルから必要な情報がある行を抽出
では、すべてのXMLファイルから必要な情報を含む行を抽出しよう。木下の文献(木下2023)から「ISBN」「出版年」「主題」「所蔵機関ID」が、また分析を補助する情報として「書誌ID」「タイトル」が必要と考え、ここでの抽出対象とする。実際のXMLファイルや、CiNii Booksの図書・雑誌情報のRDFの説明(https://support.nii.ac.jp/ja/cib/api/b_rdf)を参照しながら、これらの情報が含まれる行のみを特定できるXMLタグの記述を確認しておこう。ここでは表1のとおり整理する。
表1 詳細情報XMLファイルから抽出する情報の具体例
抽出項目* | 具体例** |
---|---|
書誌ID[1] |
<foaf:isPrimaryTopicOf rdf:resource="https://ci.nii.ac.jp/ncid/BC13731550.rdf"/> |
1タイトル[1] | <dc:title>アジア動向年報</dc:title> |
ISBN[0~n] | <dcterms:hasPart rdf:resource="urn:isbn:9784258020065" dc:title="2010-2019 : 台湾編"/> |
出版年[0~1] | <prism:publicationDate>2022.2</prism:publicationDate> |
主題[0~n] | <dc:subject>NDC6:302.2</dc:subject> |
所蔵機関ID[1~n]*** | <foaf:Organization rdf:about="https://ci.nii.ac.jp/library/FA003749"> |
* []内は当該項目の出現回数の想定。nは任意の数字。「1行1書誌に整形」セクションで利用する
** 赤字は後のコマンドで使用する部分
*** 最初は「所蔵機関名」を対象にしようと考えたが、これに対応するタグ<foaf:name>は「著者に関する記述<foaf:maker>」内でも使用されており、うまく抽出できないため不採用とした。
続いてこれらの情報を抽出するコマンドを検討するが、具体的には次のものを使用する。コマンドプロンプトを起動1してXMLファイルが保存されているフォルダへ移動した後(図1の1行目)、このコマンドを実行しよう。
findstr /c:"<foaf:isPrimaryTopicOf rdf:resource=" /c:"<dc:title>" /c:"urn:isbn:" /c:"<prism:publicationDate>" /c:"<dc:subject>" /c:"<foaf:Organization rdf:about=" *.xml >> detail.txt
- findstr:指定したファイルに指定した文字列が含まれていればその行を出力するコマンド。/c:オプションの後にダブルクオーテーションで囲った検索対象文字列を記述する。/c:オプションは半角スペース区切りで複数列挙でき、その場合OR検索となる。なお前稿ではダブルクオーテーションを検索するには\"と記述する旨を述べたが、複数条件だと動作しなかった。筆者の環境だけかもしれないが、ダブルクオーテーションを含まない検索条件にすることで回避している(<dcterms:hasPart rdf:resource=\"urn:isbn:で検索予定だったものをurn:isbn:へ変更した)。
検索対象とするファイルは*.xmlと記述することで、同フォルダ内のすべてのXMLファイルが対象となる(*はその場所にどのような文字が何文字あってもよいことを示す)。 - >>:">>"の左側の処理結果を、右側で指定するファイルに追記させる記号。ここではdetail.txtというファイルに書出しさせている。追記なので、処理をやり直す際は事前にファイルを削除しておかないと、前回の結果が残ってしまうことに注意。この記号以降の記述を省略すると、ファイルではなく画面に処理結果が表示される。
detail.txtをメモ帳で開いてみると、コマンドで指定した条件に合致する行が、行頭にファイル名を付与する形で保存されていることが分かる。
1行1書誌に整形
本セクションではWordの置換機能を活用してデータを1書誌1行に整形していく。また、「所蔵機関ID」など複数ある項目(表1参照)については"|"区切りとなるようにする。
まずはdetail.txtをメモ帳で開き、すべて選択(Ctrl+A)してコピーし、Wordへ貼り付けよう。その後、置換ダイアログ(Ctrl+H)を表示して、「半角と全角を区別する」のみがチェックされているように検索オプションを設定してほしい。
設定が完了したら表2の順番で、「すべて置換」ボタンで、置換を実施する。各置換処理の目的や詳細は「解説」列を参照してほしい。適宜「検索」タブに切り替えて置換される個所を確認しながら実施すると理解が深まる。すべての置換を実施した後に改行(^p)を検索すると、XMLファイル数と同数がヒットする(=1行1書誌のデータになっている)はずだ。
表2 置換内容と手順
項番 | 検索する文字列 | 置換後の文字列 | 解説 |
---|---|---|---|
1 | d^#.xml: | (空欄) | findstrコマンドの結果、各行の冒頭にd1.xml:のようなファイル名が付加されている。それを削除する。^#は任意の数字1文字を表す(これらの特殊な文字は、置換ダイアログ最下部の「特殊文字▼」ボタン押下で確認できる)。 |
2 | d^#^#.xml: | (空欄) | 目的は同上。d10.xml:のような数字2桁のファイル名に対応する。これを、ファイル名の桁数に応じて、^#の数を増やして実施する。 |
3 | .rdf"/>^p | .rdf"/> | 書誌ID行の末尾の改行を削除する。^pは改行(段落区切り)を表す。 |
4 | </dc:title>^p | </dc:title> | タイトル行の末尾の改行を削除する。 |
5 | dc:title="*" | (空欄) | ISBN行に含まれる不要な記述(例: dc:title="ミャンマー編")を削除する。1文字目を半角スペースにしている点に注意。この時だけ、置換ダイアログの「半角と全角を区別する」のチェックを外し、「ワイルドカードを使用する」をチェックしてから実行すること。これにより*は0文字以上の文字として処理される。 |
6 | "/>^p<dcterms:hasPart rdf:resource="urn:isbn: | | | ISBNが複数ある場合、|で一行にまとめる。置換ダイアログの「ワイルドカードを使用する」のチェックを外し、「半角と全角を区別する」を再チェックして実行すること。なお、今回のデータ中にこのパターンは存在しなかった。 |
7 | "/>^p | "/> | ISBN行の末尾にある改行を削除する。"/>^p自体は書誌ID行にもあったが、項番3にて削除済み。 |
8 | </prism:publicationDate>^p | </prism:publicationDate> | 出版年行の末尾にある改行を削除する。 |
9 | </dc:subject>^p<dc:subject> | | | 主題が複数ある場合、|で1行にまとめる。 |
10 | </dc:subject>^p | </dc:subject> | 主題行の末尾にある改行を削除する。 |
11 | ">^p<foaf:Organization rdf:about="https://ci.nii.ac.jp/library/ | | | 所蔵機関IDが複数ある場合、|で1行にまとめる。 |
12 | https://ci.nii.ac.jp/library/ | (空欄) | 所蔵機関ID行の不要情報を削除する。 |
1セル1データに整形
最後にExcelの関数を駆使して、1セル1データに整形していこう2。置換が終了した後のWordのデータを全選択(Ctrl+A)してコピーし、Excelの[A2]セルに貼り付ける。サンプルファイルでは[1. 項目毎に抽出]シートを参照してほしい。このシートの1行目は見出しにしているので、2行目以下に具体的なデータがある。
なおサンプルファイル中の数式は、セル内で改行している(セル内はAlt+Enterで改行できる)。そのため図3のように数式バー下部をマウスでドラッグして、表示領域を広げて確認してほしい。
- <書誌ID>
- [B2]セルに、=MID(A2, 64, 10)の関数を入力して抽出している。MID関数は指定した文字(A2)の、何文字目(64)から、何文字分(10)を抽出するか指定する関数である。今回紹介する中では比較的利用頻度の多い関数であり、使ったことがある読者もいるのではないだろうか。
- <タイトル>
-
[C2][D2]の2セルを用いている。[C2]セルは途中経過を分かりやすくするもので、[D2]セルが最終的に欲しい値である。[C2]セルに、=CHOOSECOLS(TEXTSPLIT(A2, "<dc:title>"),2)と関数を入力している。TEXTSPLITは指定した文字(A2)が、指定した文字("<dc:title>")で区切られているとExcelに認識させる関数であり、さらにCHOOSECOLSは、その区切られた並びのうち、指定した順番のもの(2)を抽出する関数である。より分かりやすく書くと、[A2]セルの文字で<dc:title>より後ろの文字だけを抽出している。
さらに[D2]セルでは、=CHOOSECOLS(TEXTSPLIT(C2, "</dc:title>"),1)と関数を入力している。[C2]セルと同じ関数を使用しているので詳細な説明は省くが、一言で言えば、[C2]セルの文字で</dc:title>より前の文字だけを抽出している。結果としてこの[C2][D2]セルでは、<dc:title>タグ内の文字列を抽出している。
- <ISBN>
-
これも途中経過を示すために[E2][F2]の2セルを用いている。[E2]セルは=CHOOSECOLS(TEXTSPLIT(A2, "urn:isbn:"),2)関数を入力している。<タイトル>の時と同様で、urn:isbn:より後ろの文字を抽出している。[F2]セルには=LEFT(E2,FIND("""", E2)-1)とこれまでと異なる関数を入力している。LEFTは指定した文字列(E2)を、左から指定した分だけ抽出する関数であり、その文字数はFIND関数で調べて指定している。FIND関数は指定した文字列(")が、指定した文字列(E2)の何文字目に出現するかを調べる関数であるが、これら関数を何も考えずに使用すると指定した文字(")自身を含む番目になってしまうので、-1している。つまりこの2セルでは、urn:isbn:と"の間の文字を抽出している。これまでどおりCHOOSECOLS、TEXTSPLIT関数で抽出することももちろんできたが、関数利用の別パターンとして示した。
Excelの関数中で、文字列としてのダブルクオーテーション一文字を示すには""""とダブルクオーテーションを4回入力する。なお、この並びの中での各文字の意味は次のようになる。
- ここから文字列が始まることを示す記号。
- 次に来る文字は特殊な文字(例えば、文字列の終わりであることを示す記号)【ではない】ことを示す記号。
- 実際に示されるダブルクオーテーション。
- ここで文字列が終わることを示す記号。
なおサンプルファイルでは、FIND関数の""""について手入力すると意図した結果が得られず、[A2]セル中の"をコピーして、3文字目にペーストすると問題なくなった。この例に限らず特に記号類に関しては、関数で意図した結果が得られなかったらコピー&ペーストを試してみて欲しい。
- <出版年>
- [G2]セルに=CHOOSECOLS(TEXTSPLIT(CHOOSECOLS(TEXTSPLIT(A2, "<prism:publicationDate>"), 2),"</prism:publicationDate>"),1)と関数を入力している。使用している関数は前述のとおりであり、意味するところは<prism:publicationDate>タグ内の文字列抽出である。これは<タイトル>で行った抽出過程を1セルにまとめて記述する方法の例である。
- <主題>
-
[H2]セルに=IF(IFERROR(CHOOSECOLS(TEXTSPLIT(CHOOSECOLS(TEXTSPLIT(A2, "<dc:subject>"), 2), "</dc:subject>"), 1), "error")="error", "", CHOOSECOLS(TEXTSPLIT(CHOOSECOLS(TEXTSPLIT(A2, "<dc:subject>"), 2), "</dc:subject>"), 1))と関数を入力している。<出版年>と同様、<dc:subject>タグ内の文字列を抽出している。これまでの関数と異なるのは、当該タグ(ここでは<dc:subject>)が存在しない場合に#VALUE!と表示されるエラーを回避し、空欄とするようにしている点だ。
IFERROR関数はこれから実行しようとする関数がエラーの場合に、指定した文字(error)を返す関数である。これをIF関数の条件分岐と組み合わせて、<dc:subject>タグが存在しない場合は空欄とし、存在すればその中の文字を抽出する処理が実現できる。
- <所蔵機関>
- [I2]セルに=LEFT(CHOOSECOLS(TEXTSPLIT(A2, "<foaf:Organization rdf:about=""",),2), FIND("""", CHOOSECOLS(TEXTSPLIT(A2, "<foaf:Organization rdf:about=""",),2))-1)と関数を入力している。使用している関数は前述のとおりであり、意味するところは<foaf:Organization rdf:about="と"の間の文字列抽出である。これは<ISBN>で行った抽出過程を1セルにまとめて記述する方法の例である。
- <関数の確定>
- ここまでの説明では2行目に関数を入力してきた。これらをコピー&ペーストやオートフィル機能を活用するなどして、3行目以下に反映させよう。そのうえで、後の分析で数式の参照先がずれ、数字が変わることを防ごう。[1. 項目ごとに抽出]シートを全選択(Ctrl+A)してコピーし、別のシートに【値として】貼り付けよう。また、数式のエラーは#VALUE!表示になっているので、これが邪魔であれば置換機能で一括削除しておこう(ここまで処理したものはサンプルファイルの[2. 最終結果]シート参照)。
おわりに
今回作成したExcelファイルを用いれば、資料ごとの所蔵館数を一覧にしたり([I列:機関]で"|"の数を数える関数を用いる)、ISBNがある資料の数([F列:ISBN]が空欄でないセルを数える関数を用いる)を調べることができる。これらを出版年とクロス集計することも容易だろう(ピボットテーブル機能を用いる)。また、木下(2023)と同じ検索条件でCiNiiからデータを抽出していれば、似たような分析も実施できるだろう(木下はアジア情報室の書誌データをNDLの内部システムから抽出しているので、それに相当する作業は別途必要である)。
本記事ではコマンドプロンプト、Word、Excelの機能を活用して、大量のXMLファイルから必要情報の抽出を試みた。この手法の限界として例えば、XMLをパースするのではなくテキストマッチで情報抽出を行っている都合上、XMLの構成によってはうまく抽出できないデータが存在し得る(具体例は表1の***を参照)。また、WordやExcelを多用している都合上、PCのスペックおよびデータ量によっては処理しきれずフリーズする事が起こりえる。その場合は一度に処理するデータ量を制限しなければならない。
これら制限には注意が必要だが、プログラムを書くよりは試しやすい方法を示せたと感じている。本記事の手法が誰かの役に立てば幸いである。
参考文献
- 木下雅弘 2023. 「アジア情報室所蔵中国語図書の蔵書評価 ― CiNii Books との比較を通じて ―」『アジア情報室通報』vol. 21, no.3, p.13-16, https://dl.ndl.go.jp/pid/13013043(参照 2024-04-26).
著者プロフィール
今満亨崇(いまみつみちたか) アジア経済研究所学術情報センター図書館情報課。担当は図書館の管理するシステム全般。最近の活動に日本図書館協会開催の研修講師や「特集:インフォプロのためのプログラミング事例集」(『情報の科学と技術』70巻4号、2020年)の編集担当主査などがある。
注
- 起動方法などは拙稿「コマンドプロンプトを使ってみよう」の冒頭をご参照いただきたい。https://www.ide.go.jp/Japanese/Library/Column/2022/0804.html
- 本記事で使用しているExcelの関数は比較的新しいものであるため、読者の方の環境、Excelのバージョンによっては動作しない恐れがあることをご了解いただきたい。