忙了幾天終於實現一個簡單的全文搜尋在此回顧總結一下
本文介紹一下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;
/**////
/// 初始化一個索引寫入器writer,directory為建立索引的目錄,true代表如果不存在索引文件將重新建立索引文件,如果已經存在索引文件將覆寫索引文件
///
/// 傳入的要建立索引的目錄,注意是字串值,如果目錄不存在,他將會被自動建立
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
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)));
}
else
{
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);
}
/**////
/// 移除網頁中的標籤
///
/// 網頁
///
private string parseHtml(string html)
{
string temp = Regex.Replace(html, "<[^>]*>", "");
return temp.Replace(" ", " ");
}
/**////
/// 取得網頁標題
///
///
///
private string getTitle(string html)
{
Match m = Regex.Match(html, "
先建立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(), "", "");
第一步利用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://blog.tianya. cn/blogger/view_blog.asp?BlogName=aftaft