錯誤試行

PCや生活の試行錯誤の成果を報告するブログ

複数pdfを横断検索する方法の検討

裁断してpdf(+OCR)化した本が大分多くなってきて、これらの書籍の中から特定の単語を検索したいということや、あのフレーズはどの本で読んだのだったかを調べたいということが時々起こるようになってきた。

複数pdfを横断した全文検索をするにはAdobe Readerの検索機能を使うのが簡単だが、毎回1から検索を行うため、検索した結果が表示されるまでファイル数が多いと時間がかかる難点がある。このため、Google検索のようにあらかじめ検索インデックスを作成して素早く検索できる方法を検討した。環境はUbuntu16.04 LTS。

結論

デスクトップ検索ソフトRecollを使う(WindowsMacにも対応している)。
検索サーバによる方法(Fess, HyperEstraier)はうまく行かなかった。

デスクトップ検索ソフトによる方法

後述する検索エンジンによる方法がうまく行かなかったため、デスクトップ検索ソフトについて調べてみた。調べてみるとBeagleというソフトがあるようだが、長く更新が止まっているらしい。その代わりにRecollというソフトがあるようなので試してみることにした。

RecollはUbuntu16では標準リポジトリに登録されているため、端末から簡単にインストールできる。WindowsMacの場合は、文末の関連サイトのRecoll公式サイトからダウンロードする。

sudo apt-get install recoll

pdfのファイル数が2000ほどあり、インデックスを作成するのに30分程度を時間を要した。検索してみると、後述の検索サーバによる方法で使ったFessでもHyperEstraierでも拾われなかったpdfが検索結果に表示されることが確認できた。その上、Openをクリックすると検索文字列を検索した状態でドキュメントビューアのEvinceが開いてくれて大変便利である。求めていたものはすぐ近くにあったのだった。


端末から検索する

RecollはGUIを使わずに、端末から検索することもできる。端末に慣れている場合は、こちらも便利。

  • 「ほげ」を検索する

    recoll -t -A -p 999 -q 'ほげ'
    # (結果)
    :3:common/rclinit.cpp:360::Recoll 1.28.5 + Xapian 1.2.22 [/home/username/.recoll]
    Recoll query: (ほげ)
    30 results
    application/pdf [file:///pass/to/pdffilename.pdf]       [pdffilename]  xxxxxxxx        bytes   
    SNIPPETS  # <--------------------------
    6 :  社で課長ほげ山君急で 
    6 :  いよ > < ほげ山君でも、ほげ山君は、 
    # ---(中略)---
    73 :  layout 1 6 html ほげ山君、お疲れ様ほげ山君の作 
    74 :  るよ課長ほげ山君 muchas gracias 
    /SNIPPETS # <--------------------------
    

    上記の「SNIPPETS」〜「/SNIPPETS」で挟まれた部分(「<--------------------------」)が、検索した文字列がヒットしたところ。
    複数の文書でヒットすれば、その数だけ「SNIPPETS」〜「/SNIPPETS」が繰り返し表示される。左端はヒットした箇所のpdfページ番号。
    1行目のコマンドにオプションで指定している「-p 999」は、1つの文書の中で「ヒットしたところを何ヶ所表示するか」の上限を指定しているもので、このため「999」という大きな数値にしている。GUIから検索した場合は「-p 7」に指定されている模様(=結果が7ヶ所まで表示される)。
    この項目を読んでいる人にはおそらく説明の必要がないことだが、ヒット数が多過ぎて画面が流れてしまう場合は、以下のように検索結果をlessコマンドに渡せばよい(「q」でlessの画面から抜けることができる)。

    recoll -t -A -p 999 -q 'ほげ' | less
    

  • AND検索
    検索文字列を並べる。

    recoll -t -A -p 999 -q 'ほげ' 'ふが'
    

  • OR検索
    「OR」を挟んで検索文字列を並べる。

    recoll -t -A -p 999 -q 'ほげ' OR 'ふが'
    

  • 検索してヒットした箇所の前後3行を表示する

    recoll -d -t -A -p 999 -q 'ほげ' | grep -n -A 3 -B 3 'ほげ' | less
    # (結果)
    :3:common/rclinit.cpp:360::Recoll 1.28.5 + Xapian 1.2.22 [/home/username/.recoll]
    Recoll query: (ほげ)
    30 results
    application/pdf [file:///pass/to/pdffilename.pdf]       [pdffilename]  xxxxxxxx        bytes   
    4-SNIPPETS
    5:6 :  社で課長ほげ山君急で 
    6 :  いよ > < ほげ山君でも、ほげ山君は、 
    # ---(中略)---
    73 :  layout 1 6 html ほげ山君、お疲れ様ほげ山君の作 
    74 :  るよ課長ほげ山君 muchas gracias 
    15-/SNIPPETS
    16-
    17- 
    --
    52-
    53-課長
    54-
    55:ほげ山君。急で悪いんだけど、
    56-
    57-え!?
    58-
    --
    # ---(後略)---
    

    すでに説明した通り「SNIPPETS」〜「/SNIPPETS」で挟まれた部分が、検索した文字列がヒットしたところだが、この結果の表示はヒットした前後のごく狭い範囲しか表示されないので文章の内容が把握しにくい。
    このため、文書全体を「-d」オプションで出力したものをgrepに渡して検索文字列にヒットした部分の前後の行まで表示させるようにしたもの(「/SNIPPETS」の後ろに表示されている)。
    1文書内でヒット数が多過ぎる場合は、「SNIPPETS」〜「/SNIPPETS」が表示されないことがあるが、実用上問題はないだろう。

  • 最初にヒットした1つの文書だけ、結果を表示する

    recoll -n 1 -t -A -p 999 -q 'ほげ'
    

    一応こういうオプションがあるということで挙げたものなので、この項目は読み飛ばしてもいい。説明すると、「-n」は「ヒットした文書をいくつまで表示するか」の上限を指定できるもので、「-n 1」で指定すると、最初にヒットした1つ目の文書の検索結果だけが表示される。デフォルトはn=0で上限なしとなっている。あまり「いくつまで」と制限するような用途が思い付かない。

  • 検索結果に検索した文字列が含まれていない
    ヒットしたから検索結果が表示されているはずなのに、検索した文字列が結果に含まれていない場合がある。これは検索文字列が自動で分割されているため。
    以下は「後方互換性」が「後方」と「互換」と「性」のフレーズに分けられ、文章中の「性」の文字列にヒットした扱いになっているもの。
    フレーズに分割させずに検索する方法は不明だが、上に挙げたように「-d」オプションで出力したものをgrepで絞り込んだ結果を確認すれば、実用上問題はないだろう。

    recoll -t -A -p 999 -q '後方互換性'
    Recoll query: (10 * (後方 PHRASE 5 互換 PHRASE 5))
    36 results
    application/pdf [file:///pass/to/pdffilename.pdf]       [pdffilename]  xxxxxxxx        bytes   
    SNIPPETS
    8 :  相互運用性をナチュ 
    8 :  との親和性向スの操 
    8 :  認識操作性を提エー 
    # ---(後略)---
    

検索サーバによる方法

先日ブラウザ閲覧履歴内全文検索環境を構築した際に検討したFessもしくはHyperEstraierを使うことを考えた。

  • Fess
    まず検索に柔軟性があるFessを検討した(エントリ作成時点で最新バージョンの10.2を使用)。検索しようとするpdfのファイルサイズが書籍であることから100MB以上と大きく、デフォルトではこれらのファイルはクロールされないためContents lentghを増加させたり、クローラに割り当てるメモリ量を増やす必要があった。
    これらの設定を行なったが、クローラへの割り当てメモリを1GB以上に設定しても実際には1GB以上割り当てされず、クロール中にメモリの使用量が増えていき割り当てたメモリの上限に達してメモリに空きが無くなるのが原因なのか、ログファイル(/opt/fess/logs/fess-crawler.log)に以下のようなエラーが記録されて、エラーが起きた際のpdfファイルはその内容がインデックスに登録されなかった。

    (/opt/fess/logs/fess-crawler.log)
    [Crawler-20161112211806-1-1] INFO  Could not serialize object
    

    OCR処理を掛けたソフトウェアは読み取りした全てのpdfで同じものを使用しているため、pdf側の問題は考えにくかった。なおPDF作成ツール(PDF Producer)はAdobe PDF Scan Library 2.2でPDF Versionは1.3(Acrobat 4.x)。pdfboxのバージョンを上げることで改善するのかもしれないが未検証。
    なお、Fessのマニュアルは公式サイトに古いバージョンのものも残されており、これがGoogle検索で引っ掛かってくるため混乱の元となった*1。バージョン10はバージョン9とは設定ファイルや設定の方法が変化しているので注意。

  • HyperEstraier
    次にwwwoffleとの連携でも使用しているHyperEstraierを検討した(バージョン:1.4.13-14ubuntu2)。手順に従って設定し、インデックスを作成するとこちらはエラー無く終了した。しかしHyperEstraierのWebインターフェースの検索フォームからファイルパスを指定して検索してみると、特定のいくつかのpdfが検索されない。調べてみるとインデックスには登録されているが検索結果から[detail]のリンクを確認してみるとテキスト内容が文字化けしていた。インデックスを作成する際に文字コードは自動認識するがこれがうまく行っていなかったようである。このため入力文字コードOCR読み取りした環境であるWindowsに合わせ、CP932を指定して再度インデックスを作成したところ、今度はうまく行ったがサブディレクトリに入っていたWebからダウンロードしたpdfの検索結果が文字化けすることになってしまった。これを防ぐにはOCR処理を掛けたpdfだけを特定して任意のディレクトリに隔離した上でインデックスを作る必要があるが大分面倒な作業になりそうだった。そのほか、インデックスを作る際に検索結果のタイトルの文字化けを防ぐため出力文字コードの指定が必要なのと、検索結果のタイトルが必ずしもpdfのファイル名にならない場合があった。

関連サイト

*1:表示されているページが古いバージョンのマニュアルでもタイトルバーには「Fess 10.2ドキュメント」と表示されるのも紛らわしい