ファイル内の全文検索の検討メモ
テキストファイル、wordやExcelなどのOfficeファイル、PDFなど様々なファイルを登録しておいて、
登録したファイルの中の文章から検索を行いたいというような話です。
今はApache Solrとかが流行ってますが、
アプリケーションの導入となると何かとハードルが高いので、
手持ちの材料で何かできないかというメモです。
雑です。
というわけで、まずはOracle Textについて調べてみた。
Oracle Textは全文検索の仕組みをOracleに載せたもの。
例えば、文章の中に"田中△一郎"、"田中・一郎"、"田中一郎" みたいに同じ単語でも間にスペースや記号があったりしても、"田中一郎"という検索ワードで一律ヒットさせたいというようなもの。
最近のバージョンであれば製品に最初から入っている(インストールの仕方によっては入ってない場合もあるが追加インストールできる)
とりあえず試してみるのに、参考にしたのはこの辺りの資料
- http://www.oracle.com/technetwork/jp/content/oracle-text-100927-251051-ja.pdf
- http://www.oracle.com/technetwork/jp/ondemand/db-technique/oracletext-ver12-351879-ja.pdf(詳細版)
- 後は適当にgoogleで探すと見つかると思う
Oracle Textの全文検索はOracleらしくSQLのwhere条件にCONTAINS('列名','検索ワード')という条件を書く事で行う。そのためには、
- 特定のテーブルの列(不定形の文章を入れるような列)にOracleText用のIndexを作る。
- そのIndexには、構文解析の設定が必要(例, N-GRAM方式のJAPANESE_VGRAM_LEXERや、形態素解析のJAPANESE__LEXERなど)
- ただしテーブルの挿入や更新と同時にそのIndexは更新されないため、定期的にIndexの同期をプロシージャで行う必要がある。(普通のIndexと比べると処理が重いためか)
などを行う必要がある。
とはいえ、これはテーブルの列について全文検索する設定なので、ファイルの場合はどうするの?
というと、上記のIndexの設定で、特定のディレクトリやURLなどを設定することで
その場所にあるファイルからテキストを自動で抽出してIndexを作成するようである。
それはそれで面倒なので、テーブルにBLOBの列を作成して、そこに全文検索のIndexを作って、
ファイルのバイナリを登録したらどうなるのかを試してみたら、
あっさりとバイナリからファイルのテキストを抽出してIndexを作ってくれました。
全部は試していませんが、ファイルの形式(ワードとかエクセルとか)を識別しますので、
それなりに使えるかと思います。
ただし、Officeのテキストボックスの中の文章は読み込めないようでした。
(Indexの定期的な同期など、考えないといけない事は色々ありますが)
補足として、検索条件にヒットした文章のどこがヒットしたのかを明示したいこともあるかと思います。
そういう場合は、別途プロシージャを使う必要があります。
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/text.102/B19214-01/cdocpkg.htm
にある、CTX_DOC.プロシージャに色々と種類があり、例えば、
CTX_DOC.MARKUP というプロシージャで、検索条件にヒットした文章の中の、
ヒットした箇所を、<<< と >>> で囲んで表示できます。
CTX_DOC.MARKUP には、全文検索のIndex名や、レコードのユニークキー、検索条件などを引数として渡す必要があるので、
検索してヒットしたレコード1件ずつに対して、MARKUPを行っていくという手順になります。
非常に雑だと自分でも思いますが、とりあえず使ってみた結果はこんな感じでした。
Oracleが手元にあるなら、とりあえずやってみてもいいかもしれませんが、
Indexの再作成や、ちょっと手の込んだことをするとなるとプロシージャを多用したりするので、そこは割と面倒だと感じました。
ファイルの抽出でOfficeのテキストボックを抽出できないのはちょっと残念なので、
別のライブラリも試す予定。
Selenium2.xのちょっとした小技
最近仕事でSeleniumに手を出していて、Selenium2.0以降のAPIを使用している。
ところが、2.0以降についての情報が少なく、こんなことするにはどうすれば?みたいなことを調べると大抵旧バージョンだったりするので、2.xでのコードのサンプルのようなものをメモとして書いてみる。
また職場のブラウザがIE6だったりするので、Selenium IDEはほとんど使ってません。
言語はJavaメインなので、他の言語やブラウザでは使えないかもしれません。
また、旧バージョンのSelenium1.xに触ったことはありません。
- 導入
ここからダウンロードする。
1.xでは、Selenium-RCと呼ばれていた、各言語のAPIは、WebDriverという名称に変わっているので、
各言語向けのWebDriverをダウンロードして、展開してクラスパスを通せば完了。
ただし、どのWebDriverを使うにしてもPCにはJREの1.5以上がインストールされている必要があります。
IE6は正式には2.xではサポートされていませんが、WinXp + IE6の組み合わせで正常に動きました。
- 基本的な操作
ブラウザの操作は、WebDriverクラスで行う。
FORM要素なんかは、WebDriverのfindElementメソッドでWebElement要素を見つけて、値を設定していく。
以下に、だらだらとサンプルコードをのせてみる。
// ブラウザに応じて生成するdriverのインスタンスを変える。 WebDriver driver = new InternetExplorerDriver(); driver.get("http://xxxxxxxx"); // text,password,textareaなどをname属性で取得して、入力 driver.findElement(By.name("hoge")).sendKeys("value"); // checkbox,radioなどはid属性があれば簡単だが、 // そうでない場合xpathでname属性、value属性を指定して、クリックさせる。 driver.findElement(By.xpath("//input[@name='hoge' and @value='on']")).click(); // selectのoption指定は、上記のようなxpathで頑張ることもできるが、Selectクラスで柔軟に指定できる。 Select select = new Select(driver.findElement(By.id("hoge"))); select.selectByIndex(1); //Optionタグのインデックスで選択。 select.selectByValue("01"); //Option要素のvalue属性で選択。 select.selectByVisibleText("表示文字列"); //Optionの表示文字列で選択。 // ファイル選択もIE6では有効なファイルを絶対パスで指定すれば選択できる。 driver.findElement(By.name("FILE_UPLOAD")).sendKeys("c:/temp/hoge.txt"); // アンカーリンクは、アンカーの表示文字列でも検索可能。 driver.findElement(By.linkText("リンク")).click(); // フォームのサブミットは適当なWebElementからでも、要素を囲むFormを探してサブミットできる。 driver.findElement(By.name("hoge")).submit(); // onclick属性の実行などを行う場合は、ボタンを選択してクリックする。 driver.findElement(By.id("hoge_button")).click(); // ブラウザを閉じる。 driver.close();
- Javascriptのalertやconfirmなんかのダイアログ
1.xだとできないって記事が相当あるけど、2.xから操作できる。
WebDriverクラスの.switchTo().alert()で、ブラウザ上でポップアップしているJavascriptのダイアログが操作できる。
onloadイベントで実行したalert何かもちゃんと処理できる。
WebDriver driver = new InternetExplorerDriver(); Alert alert = driver.switchTo().alert(); alert.getText(); //ダイアログのメッセージ alert.accept(); //alertやconfirmのOKを押す。 alert.dismiss();// confirmのキャンセルを押す。
- Windowの切り替え。
window.open()何かで新しく開いたウインドウとかに操作対象を切り替えるには、 WebDriverクラスの、switchTo().window("window_id")を使えばよく、 window_id には、window.open()の第二引数のウインドウIDを渡すみたいに公式ドキュメントに書いてあるのだが、IE6だからか旨くいかない。
解決策として、WebDriverクラスのgetWindowHandlesメソッドから取得できるIDを指定するのだが、
正直言って非常に面倒臭いコードになった。もう少し簡単にできないだろうか。
// 現在操作しているWindow IDを控える。 String currentWindowId = driver.getWindowHandle(); // javascriptで、window.open()が行われるボタンをクリック。 driver.findElement(By.id("button")).click(); // ウィンドウ表示までに時間がかかると、seleniumが先走ることがあるのでウィンドウが増えるまで待機。 (new WebDriverWait(driver, 10)).until(new ExpectedCondition<Boolean>() { public Boolean apply(WebDriver d) { return d.getWindowHandles().size() > 1; } }); // 増えたウィンドウIDを取得する。 String newWindowId = null; for (String id : driver.getWindowHandles()) { if (!id.equals(currentWindowId)) { newWindowId = id; } } // ウィンドウ切り替え WebDriver newWindowDriver = driver.switchTo().window(newWindowId);
- スナップショットを取る
WebDriverではできず、以下のようなコードで取得できた。
IEでスナップショットを取るには、SnapsIEというActiveX コンポーネントを入れる必要があるという記事もあったが、2.xでは必要ないようだ。
// Selenium-RC互換のAPIを取得。 Selenium selenium = new WebDriverBackedSelenium(driver, "http://xxxx"); // base64でエンコードされたPNGのスクリーンショットを取得 String base64png = selenium.captureScreenshotToString(); // base64デコードして、ファイル等に書き込む。割愛
ちなみに、他にもcaptureScreenShotうんぬんというメソッドはあるのだが、実装されていないため、実行時エラーとなってしまった。
- できないこと
pdfファイルなんかをダウンロードしたりすると、ファイルの保存なんかを行うダイアログが出てきますが、 このダイアログを操作することは2.xでもできない模様。。。
というわけで、(使ったことはないけど)1.xに比べて色々と便利になったような感じがしました。
今後も、小技的なものがあればまとめていく予定。
自分のまわりのこと
たまには、プライベートなことでも。
えー、実は今妊娠中なのですが(妻が)
お腹の中の子は周りの音に反応してか、結構動くみたいです。
というわけで、試した結果を以下に発表。
- クラシック→無反応w。
- 森山直太朗のう○こ→無反応。(馬鹿にするなということか)
- B'zウルトラソウル→それなりの反応。(というか、ウルトラソウッ!に合わせて蹴ってくれないかと思ってたらそれで妻がそわそわしてたw)
- ドンキーコングReturnsプレイ中→タルの発射音に過敏に反応して蹴りまくるらしい。
というわけで、胎教でクラシックって効果あるのかなと思った。
いや、単純にうるさいって文句言ってるだけなのかもしれないけどね。
ゲーマーにならないように。。。
クラシックでも、シンバルがバンバンなるやつには反応するみたい。
MacBook Air 11インチ欲しい!
MacBook Air 11インチ欲しい!
キャンペーン目当てで、移行しました。
今年はちょこちょこ書いていこうと思う。