[PC] Lucene.Net Ver. 4.8.0 beta-00005 ~Lucene.Netでインデックス作成してみる~

Lucene.Netでインデックス作成

Lucene.NetがJapaneseAnalyzerに対応したということで、早速インデックスの作成プログラムを書いてみました。まずは、Visual Studio Community 2017 (以下、VS2017)の設定方法から説明します。

プロジェクトファイル作成

VS2017のメニューの[ファイル] > [新規作成] > [プロジェクト]から、.NET Frameworkのコンソールアプリを選択し、名前を付けて保存します。

次に、[ツール] > [NuGet パッケージマネージャー] > [ソリューションのNuGetパッケージの管理]を選択し、NuGetのパッケージ管理画面を表示します。検索画面で、「Lucene.Net」と入力し、且つ[プレリリースを含める]チェックボックスをオンにして、検索します。

Lucene.NET NuGet Package Search

Lucene.Netのv4.8.0-beta-00005を選択します。そして、インストールする先ほど作成したプロジェクトを選択して、[インストール]ボタンをクリックします。

Lucene.NET NuGet Package Install

同様にして、Lucene.Net.Analysis.Kuromojiをインストールします。

Lucene.NET Kuromoji Search

Commonも同時にインストールすかと問い合わせが表示されるため、[OK]をクリックして合わせて入れておきます。

Lucene.NET Kuromoji and Common
無事にCommonとKuromojiとがインストールされるのを確認します。
Lucene.NET Kuromoji Install

コーディング

次は、コーディングなのですが、FlexLuceneで使ったものを使いまわします。もちろんusing Lucene.Net.*という形になります。また、JapaneseAnalyzerやIndexWriterConfigの引数が古い形なのでバージョンを指定してあげる必要があります。LuceneVersion.LUCENE_48をつけます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using Lucene.Net.Analysis.Ja;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;

namespace LdNetIndex
{
    class Program
    {
        const string TARGETDIR = @"C:\temp\testdir"; //ソースとなるフォルダー
        const string INDEXDIR = @"c:\temp\ldn-index"; //インデックスの場所
        const string FIELD_TITLE = "title";
        const string FIELD_PLACE = "place";
        const string FIELD_CONTENT = "content";
        const string FIELD_MODIFIED = "modified";

        static void Main(string[] args)
        {
            FSDirectory dir = FSDirectory.Open(INDEXDIR);

            // テキストの解析方法(アナライザー)を定義
            JapaneseAnalyzer analyzer = new JapaneseAnalyzer(LuceneVersion.LUCENE_48);
            IndexWriterConfig config = new IndexWriterConfig(LuceneVersion.LUCENE_48, analyzer);
            config.OpenMode = OpenMode.CREATE_OR_APPEND;
            IndexWriter writer = new IndexWriter(dir, config);
            IndexReader reader = DirectoryReader.Open(writer, false);

            //開始時間の取得
            DateTime startDt = DateTime.Now;

            //ファイルのフルパスを基にhtmlファイルを読み込む
            IndexFolder(ref writer, ref reader, TARGETDIR, "*.htm*");

            //インデクサのクローズ
            writer.Commit();

            //終了時間の取得
            DateTime endDt = DateTime.Now;
            System.Console.WriteLine("{0}タイマ刻み数かかりました", (endDt - startDt).Ticks);
        }

        private static void IndexFolder(ref IndexWriter writer, ref IndexReader reader, string srcDirName, string filterString)
        {
            string[] files = System.IO.Directory.GetFiles(srcDirName, filterString, System.IO.SearchOption.TopDirectoryOnly);
            try
            {

                foreach (string file in files)
                {
                    string title, content, f;
                    title = "";
                    content = "";
                    f = file;
                    HTMLParse(ref title, ref content, ref f);

                    //文書の解析
                    Document doc = new Document();

                    Field fldTitle = new StringField(FIELD_TITLE, title, Field.Store.YES);
                    Field fldPlace = new StringField(FIELD_PLACE, f, Field.Store.YES);
                    Field sfModified = new SortedDocValuesField(FIELD_MODIFIED, new BytesRef(System.IO.File.GetLastWriteTime(f).ToString()));
                    Field fldModified = new StoredField(FIELD_MODIFIED, System.IO.File.GetLastWriteTime(f).ToString());
                    Field fldContent = new TextField(FIELD_CONTENT, content, Field.Store.YES);
                    doc.Add(fldTitle);
                    doc.Add(sfModified);
                    doc.Add(fldModified);
                    doc.Add(fldPlace);
                    doc.Add(fldContent);
                    Term tterm = new Term("place", f.ToString());
                    writer.UpdateDocument(tterm, doc);
                    //writer.AddDocument(doc);
                }
            }
            catch (System.IO.IOException ex)
            {
                System.Console.WriteLine(ex.ToString());
            }


            //コピー元のディレクトリにあるディレクトリについて、再帰的に呼び出す
            string[] dirs = System.IO.Directory.GetDirectories(srcDirName);
            foreach (string dir in dirs)
            {
                IndexFolder(ref writer, ref reader, dir, filterString);
            }

        }

        /*************************
         * HTMLの解析
         *************************/
        private static void HTMLParse(ref string title, ref string content, ref string fileName)
        {
            StreamReader sr = new StreamReader(fileName, Encoding.GetEncoding("Shift_JIS"));
            string text = sr.ReadToEnd();
            //正規表現パターンとオプションを指定してRegexオブジェクトを作成 
            Regex rTitle = new Regex("<title[^>]*>(.*?)", RegexOptions.IgnoreCase | RegexOptions.Singleline);
            Regex rPre = new Regex("<body[^>]*>(.*?)", RegexOptions.IgnoreCase | RegexOptions.Singleline);

            //TextBox1.Text内で正規表現と一致する対象をすべて検索 
            MatchCollection mcTitle = rTitle.Matches(text);
            MatchCollection mcPre = rPre.Matches(text);

            foreach (Match m in mcTitle)
            {
                title = m.Groups[1].Value;
            }
            foreach (Match m in mcPre)
            {
                content = m.Groups[1].Value;
            }
        }

若干、記載が古い形式なので、config.OpenMode = OpenMode.CREATE_OR_APPEND;とかは、直接値をセットする形になっています。FlexLuceneのバージョン6.3.0を使った場合は、Get/Setで入力していたのと少し違います。ただ、ほんの少しの手直しで動作させることができました。

まとめ

Lucene.Net v4.8.0 beta-00005でJapaneseAnalyzerがサポートされたことで、いろいろな展開ができるようになると思います。ゆくゆくはAndroidアプリ(Xarmin)に利用できないかと夢は膨らむばかりです。是非みなさんもLucene.Netを使った全文検索をお試しください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です