Lucene.NETは構内検索エンジンを構築

16251 ワード

http://www.cnblogs.com/psforever/archive/2011/10/06/2200019.html
最近のプロジェクトではlucene.Net、ネット上ではluceneについてですが.Netの紹介はもうたくさんありますが、ここで私の使用心得をまとめます.私が使っているのはluceneです.Netの最新版(Lucene.Net-2.9.2)では、分詞器に盤古分詞が採用されており、効果は悪くない.lucene.Netのサイト内検索は、インデックスの作成とインデックス内の検索の2つのステップにほかならない.
一、インデックスの作成:インデックスの作成は簡単です.データベースからインデックスを生成する必要があるデータを取得し、luceneを利用します.NetのIndexWriterクラスはインデックスの作成を行います.
        private void CreateIndex()
{
Lucene.Net.Store.Directory directory = FSDirectory.Open(new System.IO.DirectoryInfo(strIndexPath));
IndexWriter writer = new IndexWriter(directory, new Lucene.Net.Analysis.PanGu.PanGuAnalyzer(),
true, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);

DataTable dtNews = news.GetNews();
for (int i = 0; i < dtNews.Rows.Count; i++)
{
Lucene.Net.Documents.Document doc = new Lucene.Net.Documents.Document();
doc.Add(new Field("Title", dtNews.Rows[i]["title"].ToString(), Field.Store.YES, Field.Index.ANALYZED));
doc.Add(new Field("Url", dtNews.Rows[i]["url"].ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
doc.Add(new Field("Content", dtNews.Rows[i]["content"].ToString(), Field.Store.YES, Field.Index.ANALYZED));
doc.Add(new Field("CreateTime", DateTimeHelper.DateTimeToUnix(Convert.ToDateTime(dtNews.Rows[i]["create_time"])).ToString(), Field.Store.YES, Field.Index.ANALYZED));
writer.AddDocument(doc);
}
writer.Optimize();
writer.Close();
}


IndexWriterコンストラクション関数の最初のパラメータは、インデックスファイルの格納場所を指定します.2番目のパラメータは分詞Analyzerを指定し、Analyzerには複数のサブクラスがあるが、その分詞効果はよくなく、ここではサードパーティのオープンソース分詞ツール盤古分詞を使用する.3番目のパラメータはインデックスを再作成するかどうかを示し、trueは再作成(以前のインデックスファイルを削除)を示し、最後のパラメータはFieldの最大数を指定します.IndexWriterで最も重要な方法void AddDocument(Document doc)は、インデックスファイルにインデックスを追加することを意味します.Documentで最も重要なインスタンスメソッドvoid Add(Fieldable field)は、インデックスレコードを作成するためのフィールドを追加します.Fileldクラスのコンストラクション関数Field(string name,string value,Field.Store store,Field.Index)は、フィールド名を表す.valueはフィールド値を表します.storeはvalue値を格納するか否かを示し、オプション値Field.Store.YESストレージ、Field.Store.NO保存しないStore.COMPRESS圧縮ストレージ;Indexはインデックスを作成する方法を示し、オプションの値Field.Index.NOインデックスを作成しないIndex.NOT_ANALYZED、インデックスを作成し、インデックスは未分詞のvalue、Field.Index.ANALYZED、インデックスを作成し、インデックスは分詞後のvalueである.
注意:Lucene.Netで直接インデックス時間フォーマットが不要な場合は、数値タイプに変換する必要があります.ここでは、Unix時間フォーマットに変換します.これにより、ある期間のデータを検索する際に簡単になります.時間をUnix時間に変換し、変換されたコードは次のようになります.
        private static readonly long _UinxBase = DateTime.Parse("1970-1-1 00:00:00").Ticks;
private const long _DOT_NET_TIME_TICK = 10000000; // C#

///<summary>
/// C# Unix
///</summary>
///<param name="time">C# </param>
///<returns> Unix </returns>
public static int DateTimeToUnix(DateTime time)
{
return (Int32)((time.ToUniversalTime().Ticks - _UinxBase) / _DOT_NET_TIME_TICK);
}

///<summary>
/// Unix C#
///</summary>
///<param name="time">Unix </param>
///<returns> C# </returns>
public static DateTime UnixToDateTime(int time)
{
try
{
long t = time * _DOT_NET_TIME_TICK + _UinxBase;
return new DateTime(t).ToLocalTime();
}
catch
{
return DateTime.Today;
}
}


二、検索利用lucene.Netは検索機能を実現し,主にIndexSearcherクラスのSearchメソッドを用いて作成したインデックスファイルにおいてキーワードによって構築されたBooleanQueryに基づいて条件に合致する結果を検索する.
        public List<EntityNews> SearchNews(string keyword, int pageSize, int pageNo, out int recCount)
{
string keywords = keyword; //
IndexSearcher search = new IndexSearcher(FSDirectory.Open(new System.IO.DirectoryInfo(strIndexPath)), true);
keyword = GetKeyWordsSplitBySpace(keyword, new PanGuTokenizer());

QueryParser titleQueryParser = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, "Title", new PanGuAnalyzer(true));
Query titleQuery = titleQueryParser.Parse(keyword);
Query urlQuery = new PrefixQuery(new Term("Url", keywords)); //URL ,
BooleanQuery bq = new BooleanQuery();
bq.Add(titleQuery, BooleanClause.Occur.SHOULD);// “or”,BooleanClause.Occur.MUST “and”,BooleanClause.Occur.MUST_NOT “not”
bq.Add(urlQuery, BooleanClause.Occur.SHOULD);

// ( 1000 )
TopScoreDocCollector collector = TopScoreDocCollector.create(pageSize * 1000, true);
search.Search(bq, null, collector);
TopDocs topDoc = collector.TopDocs(0, collector.GetTotalHits());

//
if (topDoc.totalHits > pageSize * 1000)
recCount = pageSize * 1000;
else
recCount = topDoc.totalHits;

int i = (pageNo - 1) * pageSize;
List<EntityNews> result = new List<EntityNews>();
while (i < recCount && result.Count < pageSize)
{
EntityNews news = new EntityNews();
Lucene.Net.Documents.Document docs = search.Doc(topDoc.scoreDocs[i].doc);
try
{
string strTitle = docs.Get("Title");
string strContent = docs.Get("Content");
news.Url = docs.Get("Url");
news.Time = DateTimeHelper.UnixToDateTime(Convert.ToInt32(docs.Get("CreateTime")));

//
PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter = new PanGu.HighLight.SimpleHTMLFormatter("<font style=\"color:red;\">", "</font>");
PanGu.HighLight.Highlighter highlighter = new PanGu.HighLight.Highlighter(simpleHTMLFormatter, new PanGu.Segment());
highlighter.FragmentSize = 50;

//string GetBestFragment(keywords,content) SimpleHTMLFormatter content
// content keywords ,
news.Content = highlighter.GetBestFragment(keywords, strContent);
if (string.IsNullOrEmpty(news.Content))
{
news.Content = strContent;
}
news.Title = highlighter.GetBestFragment(keywords, strTitle);
if (string.IsNullOrEmpty(news.Title))
{
news.Title = strTitle;
}
}
catch (Exception e)
{
throw e;
}
finally
{
result.Add(news);
i++;
}
}

search.Close();
return result;
}


BooleanQueryはQueryの集合であることが分かるが、我々は1つのフィールドを利用してQuery Parserを構築し(構築パラメータのfiledはインデックスが作成されたフィールドである必要がある)、その後そのParse法を利用して検索キーワードを入力し、1つのQueryを得る.また、複数のQueryはAddからBooleanQueryにある場合、所定の条件の関係が必要であり、BooleanClauseがある.Occur.MUST、BooleanClause.Occur.SHOULD、BooleanClause.Occur.MUSTの3つの選択肢は、それぞれ「与」、「または」、「非」の3つの関係に対応している.
構内検索では、ITニュースのみを検索したい場合、すべてのニュース情報が1つのテーブルに格納されている場合、ニュースのタイプを示すフィールドNewsTypeがあります.上記のインスタンスプログラムでは、このようにプログラムを変更することができます.
 QueryParser titleQueryParser = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, "Title", new PanGuAnalyzer(true));
Query titleQuery = titleQueryParser.Parse(keyword);
Query urlQuery = new PrefixQuery(new Term("Url", keywords)); //URL ,
Query typeQuery = new TermQuery(new Term("NewsType", " "));
BooleanQuery bq = new BooleanQuery();
bq.Add(titleQuery, BooleanClause.Occur.SHOULD);
bq.Add(urlQuery, BooleanClause.Occur.SHOULD);
bq.Add(typeQuery , BooleanClause.Occur.MUST);// “and”


その中のTermQueryは「語検索」を表す.つまり、完全な一致が必要である.NewsTypeはすでにインデックスされているフィールドであり、そのインデックス方式はFieldである.Index.NOT_ANALYZED.
しかし、問題は、これで私たちの要求に達することができますか?検索したニュースはすべて「娯楽ニュース」ですか?答えは否定的だ.このような結果は、TitleやUrlにマッチすれば検索されます.
このような問題に対して、BooleanQueryがニュースタイプを保存するための条件を宣言し、検索キーワードのBooleanQueryと新しいBooleanQueryに追加し、それらの間を「and」リンクに設定し、コードを以下のように変更する必要があります.
            BooleanQuery bqNewsType = null; //       Query  
BooleanQuery bqKeyword = new BooleanQuery(); // Query

QueryParser titleQueryParser = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, "Title", new PanGuAnalyzer(true));
Query titleQuery = titleQueryParser.Parse(keyword);
Query urlQuery = new PrefixQuery(new Term("URL", keywords)); //URL ,
bqKeyword.Add(titleQuery, BooleanClause.Occur.SHOULD);
bqKeyword.Add(urlQuery, BooleanClause.Occur.SHOULD);

if (!string.IsNullOrEmpty(newsType))
{
Query typeQuery = new TermQuery(new Term("NewsType", newsType)); //
bqNewsType = new BooleanQuery();
bqNewsType.Add(typeQuery, BooleanClause.Occur.SHOULD);
}

// "and"
BooleanQuery bq = new BooleanQuery();
bq.Add(bqKeyword, BooleanClause.Occur.MUST);
if (bqNewsType != null)
{
bq.Add(bqNewsType, BooleanClause.Occur.MUST);
}


これにより,探索結果において再探索を実現し,再探索の条件を1つのBooleanQueryに置き,元の探索条件を1つのBooleanQueryに置き,新しいBooleanQueryでMUSTを開始することも可能である.