忙了幾天終于實作一個簡單的全文搜尋在此回顧總結一下
本文介紹一下Lucene.Net 是什麼?Lucene.Net 能作什麼?以及怎麼做的問題?最後給出 Lucene.Net 實作全文搜尋的一個示例
1、Lucene.Net 是什麼?
Lucene.net 起初是一個開源項目然後轉向商業化,也在Lucene.net 2.0已經釋出,不過是要money D ,Lucene.net的命運有點類似于FreeTextBox ,它在 1.6.5 版本之後釋出的 2.0 開始了商業路線,2.0 提供了 DLL 方式的免費版本,源代碼版本則必須購買商業的許可 licence;不過它留下了 1.6.5 版本的源代碼,還是可以看到大部分的内部細節,但 2.0 版本中添加的對 Mozilla 浏覽器的支援部分隻有通過它生成的 HTML 和 JavaScript 腳本去窺測。
Lucene 是 Java 世界中常用的索引 API,使用它提供的方法可以為文本資料建立索引,并提供檢索。(參考:NLucene 和 Lucene .NET)NLucene 是第一個的 .net 移植,也是一個有 .net 風格的版本,使用 .net 的命名規範和類庫設計。不過 NLucene 項目的 leader 由于精力原因,隻釋出了 1.2beta 版本。Lucene.NET 項目出現後,NLucene 就沒有新的計劃了。
Lucene.NET 當初号稱要做 up-to-date 的 .net Lucene 移植,它隻在命名方面采納了 .net 的建議,主要目标傾向于和 Java Lucene 相容:一個是索引格式相容,達到可以共同工作的目的;一個是命名接近(隻相差很少,比如大小寫等),目的是可以友善開發者使用 Java Lucene 相關的代碼和資料。
不知什麼時候 Lucene.NET 項目已經放棄了開源計劃,轉向了商業。它居然把 SourceForge 上已經開源的檔案也删除了。與此同時,SourceForge 上又出現了 dotLucene 項目,出于對 Lucene.NET 的抗議,dotLucene 幾乎将 Lucene.NET 的代碼原封不動放在上面作為他們的起點。(https://sourceforge.net/forum/forum.php?thread_id=1153933&forum_id=408004)。
說白了Lucene.Net就是是一個資訊檢索的函數庫(Library),利用它你可以為你的應用加上索引和搜尋的功能.
Lucene的使用者不必深入了解有關全文檢索的知識,僅僅學會使用庫中的幾個類,知道怎麼調用Library中的函數,就可以為你的應用實作全文檢索的功能.
不過千萬别期望Lucene是一個象google和百度那樣的搜尋引擎,它僅僅是一個工具,一個Library.你也可以把它了解為一個将索引,搜尋功能封裝的很好的一套簡單易用的API.利用這套API你可以做很多有關搜尋的事情,而且很友善,它可以滿足你對一個應用做簡單的全文搜尋,作為應用的開發者(非專業搜尋引擎開發者)來說,它的功能足以滿足你。
2、Lucene.Net 可以作什麼?
Lucene可以對任何的資料做索引和搜尋. Lucene不管資料源是什麼格式,隻要它能被轉化為文字的形式,就可以被Lucene所分析利用.也就是說不管是MS word, Html ,pdf還是其他什麼形式的檔案隻要你可以從中抽取出文字形式的内容就可以被Lucene所用.你就可以用Lucene對它們進行索引以及搜尋.
3、使用 Lucene.Net 怎麼做?
簡單的歸結為:建立索引,和使用索引,其中建立索引就是将要搜尋的資料源的那些資訊作為我們的關鍵資訊來存儲或者是分析,為搜尋留下标記就象Word裡面建立目錄(個人了解),使用索引就是在搜尋的時候根據索引的資訊來分析資料源将我們需要的資訊提取出來。
具體請看一下示例:
建立索引的類
public class IntranetIndexer
{
/**/////索引寫入器
private IndexWriter writer;
//要寫入索引的檔案的根目錄
private string docRootDirectory;
//要比對的檔案格式
private string[] pattern;
/**//// <summary>
/// 初始化一個索引寫入器writer,directory為建立索引的目錄,true代表如果不存在索引檔案将重新建立索引檔案,如果已經存在索引檔案将覆寫索引檔案
/// </summary>
/// <param name="directory">傳入的要建立索引的目錄,注意是字元串值,如果目錄不存在,他将會被自動建立</param>
public IntranetIndexer(string directory)
{
writer = new IndexWriter(directory, new StandardAnalyzer(), true);
writer.SetUseCompoundFile(true);
}
public void AddDirectory(DirectoryInfo directory, string [] pattern)
this.docRootDirectory = directory.FullName;
this.pattern = pattern;
addSubDirectory(directory);
private void addSubDirectory(DirectoryInfo directory)
for(int i=0;i<pattern .Length ;i++)
{
foreach (FileInfo fi in directory.GetFiles(pattern[i]))
{
AddHtmlDocument(fi.FullName);
}
}
foreach (DirectoryInfo di in directory.GetDirectories())
addSubDirectory(di);
public void AddHtmlDocument(string path)
string exname=Path.GetExtension (path);
Document doc = new Document();
string html;
if(exname.ToLower ()==".html" ||exname .ToLower ()==".htm"||exname .ToLower ()==".txt")
using(StreamReader sr=new StreamReader (path,System .Text .Encoding .Default ))
{
html = sr.ReadToEnd();
else
using (StreamReader sr = new StreamReader(path, System.Text.Encoding.Unicode ))
html = sr.ReadToEnd();
int relativePathStartsAt = this.docRootDirectory.EndsWith("\\") ? this.docRootDirectory.Length : this.docRootDirectory.Length + 1;
string relativePath = path.Substring(relativePathStartsAt);
string title=Path.GetFileName(path);
//判斷若是網頁則去标簽否則不用
if(exname.ToLower ()==".html" ||exname .ToLower ()==".htm")
doc.Add(Field.UnStored("text", parseHtml(html)));
doc.Add (Field .UnStored ("text",html));
doc.Add(Field.Keyword("path", relativePath));
//doc.Add(Field.Text("title", getTitle(html)));
doc.Add (Field .Text ("title",title));
writer.AddDocument(doc);
}
/// 去除網頁中的标簽
/// <param name="html">網頁</param>
/// <returns>傳回去除後的網頁文本</returns>
private string parseHtml(string html)
string temp = Regex.Replace(html, "<[^>]*>", "");
return temp.Replace(" ", " ");
/// 擷取網頁标題
/// <param name="html"></param>
/// <returns></returns>
private string getTitle(string html)
Match m = Regex.Match(html, "<title>(.*)</title>");
if (m.Groups.Count == 2)
return m.Groups[1].Value;
return "文檔标題未知";
/// 優化索引并關閉寫入器
public void Close()
writer.Optimize();
writer.Close();
}
首先建立Document對象,然後為Document對象添加一些屬性Field.你可以把Document對象看成是虛拟檔案,将來将從此擷取資訊.而Field則看成是描述此虛拟檔案的中繼資料(metadata).其中Field包括四個類型: Keywork
該類型的資料将不被分析,而會被索引并儲存儲存在索引中.
UnIndexed
該類型的資料不會被分析也不會被索引,但是會儲存在索引.
UnStored
和UnIndexed剛好相反,被分析被索引,但是不被儲存.
Text
和UnStrored類似.如果值的類型為string還會被儲存.如果值的類型為Reader就不會被儲存和UnStored一樣.
最後将每一個Document添加到索引當中。
下面是對索引進行搜尋
//建立一個索引器
IndexSearcher searcher = new IndexSearcher(indexDirectory);
//解析索引的text字段以便搜尋
Query query = QueryParser.Parse(this.Q, "text", new StandardAnalyzer());
//将搜尋結果放在hits中
Hits hits = searcher.Search(query);
//統計搜尋的總記錄數
this.total = hits.Length();
//高亮顯示
QueryHighlightExtractor highlighter = new QueryHighlightExtractor(query, new StandardAnalyzer(), "<font color=red>", "</font>");
第一步利用IndexSearcher打開索引檔案用于後面搜尋,其中的參數是索引檔案的路徑.
第二步使用QueryParser将可讀性較好的查詢語句(比如查詢的詞lucene ,以及一些進階方式lucene AND .net)轉化為Lucene内部使用的查詢對象.
第三步執行搜尋.并将結果傳回到hits集合.需要注意的是Lucene并不是一次将所有的結果放入hits中而是采取一次放一部分的方式.出于空間考慮.
然後将搜尋的結果進行處理并在頁面上顯示出來:
for (int i = startAt; i < resultsCount; i++)
{
Document doc = hits.Doc(i);
string path = doc.Get("path");
string location =Server.MapPath("documents")+"\\"+path;
string exname=Path.GetExtension (path);
string plainText ;
string str=doc.Get ("title");
if(exname==".html" || exname ==".htm" || exname ==".txt")
using (StreamReader sr = new StreamReader(location, System.Text.Encoding.Default))
{
plainText = parseHtml(sr.ReadToEnd());
}
}
else
using (StreamReader sr = new StreamReader(location, System.Text.Encoding.Unicode ))
plainText = sr.ReadToEnd();
//DataTable 添加行
DataRow row = this.Results.NewRow();
row["title"] = doc.Get("title");
string IP=Request.Url.Host;//擷取伺服器IP
//Request.Url.Port;
row["path"]=@"http://"+IP+"/WebUI/Search/documents/"+path;
row["sample"] = highlighter.GetBestFragments(plainText, 80, 2, "
");
this.Results.Rows.Add(row);
}
searcher.Close();//關閉搜尋器
想對Lucene.Net 進行更進階,更全面,更深層次了解的請參閱一下網站:
http://www.alphatom.com/
http://blog.tianya.cn/blogger/view_blog.asp?BlogName=aftaft