目錄
Lucene.net站內搜索—1、SEO優化
Lucene.net站內搜索—2、Lucene.Net簡介和分詞
Lucene.net站內搜索—3、最簡單搜索引擎代碼
Lucene.net站內搜索—4、搜索引擎第一版技術儲備(簡單介紹Log4Net、生產者消費者模式)
Lucene.net站內搜索—5、搜索引擎第一版實現
Lucene.net站內搜索—6、站內搜索第二版
代碼
先看代碼,后面再一一講解
引入命名空間:
- using Lucene.Net.Store;
- using System.IO;
- using Lucene.Net.Index;
- using Lucene.Net.Analysis.PanGu;
- using Lucene.Net.Documents;
- using Lucene.Net.Search;
1、 對數據進行索引
- string indexPath = @"C:\1017index";//注意和磁盤上文件夾的大小寫一致,否則會報錯。
- FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory());
- bool isUpdate = IndexReader.IndexExists(directory);//判斷索引庫是否存在
- if (isUpdate)
- {
- //如果索引目錄被鎖定(比如索引過程中程序異常退出),則首先解鎖
- //Lucene.Net在寫索引庫之前會自動加鎖,在close的時候會自動解鎖
- //不能多線程執行,只能處理意外被永遠鎖定的情況
- if (IndexWriter.IsLocked(directory))
- {
- IndexWriter.Unlock(directory);//un-否定。強制解鎖
- }
- }
- IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);
- for (int i = 1000; i < 1100; i++)
- {
- string txt = File.ReadAllText(@"D:\我的文檔\文章\" + i + ".txt");
- Document document = new Document();//一條Document相當於一條記錄
- document.Add(new Field("id", i.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
- //每個Document可以有自己的屬性(字段),所有字段名都是自定義的,值都是string類型
- //Field.Store.YES不僅要對文章進行分詞記錄,也要保存原文,就不用去數據庫里查一次了
- //需要進行全文檢索的字段加 Field.Index. ANALYZED
- document.Add(new Field("msg", txt, Field.Store.YES, Field.Index.ANALYZED,
- Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));
- //防止重復索引
- writer.DeleteDocuments(new Term("id", i.ToString()));//防止存在的數據//delete from t where id=i
- //如果不存在則刪除0條
- writer.AddDocument(document);//把文檔寫入索引庫
- }
- writer.Close();
- directory.Close();//不要忘了Close,否則索引結果搜不到
2、搜索的代碼
- string indexPath = @"C:\1017index";
- string kw = TextBox1.Text;
- FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NoLockFactory());
- IndexReader reader = IndexReader.Open(directory, true);
- IndexSearcher searcher = new IndexSearcher(reader);
- PhraseQuery query = new PhraseQuery();//查詢條件
- query.Add(new Term("msg", kw));//where contains("msg",kw)
- //foreach (string word in kw.Split(' '))//先用空格,讓用戶去分詞,空格分隔的就是詞“計算機 專業”
- //{
- // query.Add(new Term("msg", word));//contains("msg",word)
- //}
- query.SetSlop(100);//兩個詞的距離大於100(經驗值)就不放入搜索結果,因為距離太遠相關度就不高了
- TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);//盛放查詢結果的容器
- searcher.Search(query, null, collector);//使用query這個查詢條件進行搜索,搜索結果放入collector
- //collector.GetTotalHits()總的結果條數
- ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs;//從查詢結果中取出第m條到第n條的數據
- List<SearchResult> list = new List<SearchResult>();
- for (int i = 0; i < docs.Length; i++)//遍歷查詢結果
- {
- int docId = docs[i].doc;//拿到文檔的id。因為Document可能非常占內存(DataSet和DataReader的區別)
- //所以查詢結果中只有id,具體內容需要二次查詢
- Document doc = searcher.Doc(docId);//根據id查詢內容。放進去的是Document,查出來的還是Document
- //Console.WriteLine(doc.Get("id"));
- //Console.WriteLine(doc.Get("msg"));
- SearchResult result = new SearchResult();
- result.Id = Convert.ToInt32(doc.Get("id"));
- result.Msg = doc.Get("msg");//只有 Field.Store.YES的字段才能用Get查出來
- list.Add(result);
- }
- Repeater1.DataSource = list;
- Repeater1.DataBind();
aspx代碼:
- <form id="form1" runat="server">
- <div>
- <asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="創建索引" />
- <br />
- <br />
- <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
- <asp:Button ID="Button2" runat="server" onclick="Button2_Click" Text="搜索" />
- <br />
- <ul>
- <asp:Repeater ID="Repeater1" runat="server">
- <ItemTemplate><li>Id:<%#Eval("Id") %><br /><%#Eval("Msg") %></li></ItemTemplate>
- </asp:Repeater>
- </ul>
- </div>
- </form>
Lucene.Net核心類簡介
先運行寫好的索引的代碼,再向下講解各個類的作用,不用背代碼。
(*)Directory表示索引文件(Lucene.net用來保存用戶扔過來的數據的地方)保存的地方,是抽象類,兩個子類FSDirectory(文件中)、RAMDirectory (內存中)。使用的時候別和IO里的Directory弄混了。
創建FSDirectory的方法,FSDirectory directory =FSDirectory.Open(new DirectoryInfo(indexPath),new NativeFSLockFactory()), path索引的文件夾路徑
IndexReader對索引進行讀取的類,對IndexWriter進行寫的類。IndexReader的靜態方法bool IndexExists(Directory directory)判斷目錄directory是否是一個索引目錄。IndexWriter的bool IsLocked(Directory directory) 判斷目錄是否鎖定,在對目錄寫之前會先把目錄鎖定。兩個IndexWriter沒法同時寫一個索引文件。IndexWriter在進行寫操作的時候會自動 加鎖,close的時候會自動解鎖。IndexWriter.Unlock方法手動解鎖(比如還沒來得及close IndexWriter 程序就崩潰了,可能造成一直被鎖定)。
創建索引
構造函數:IndexWriter(Directorydir, Analyzer a, bool create, MaxFieldLength mfl)因為IndexWriter把輸入寫入索引的時候,Lucene.net是把寫入的文件用指定的分詞器將文章分詞(這樣檢索的時候才能查的快), 然后將詞放入索引文件。
void AddDocument(Document doc),向索引中添加文檔(Insert)。Document類代表要索引的文檔(文章),最重要的方法Add(Field field),向文檔中添加字段。Document是一片文檔,Field是字段(屬性)。Document相當於一條記錄,Field相當於字段。
Field類的構造函數 Field(string name, string value, Field.Store store, Field.Indexindex, Field.TermVector termVector):name表示字段名; value表示字段值;
store表示是否存儲value值,可選值Field.Store.YES存儲,Field.Store.NO不存 儲,Field.Store.COMPRESS壓縮存儲;默認只保存分詞以后的一堆詞,而不保存分詞之前的內容,搜索的時候無法根據分詞后的東西還原原 文,因此如果要顯示原文(比如文章正文)則需要設置存儲。
index表示如何創建索引,可選值Field.Index. NOT_ANALYZED,不創建索引,Field.Index. ANALYZED,創建索引;創建索引的字段才可以比較好的檢索。是否碎屍萬段!是否需要按照這個字段進行“全文檢索”。
termVector表示如何保存索引詞之間的距離。“北京歡迎你們大家”,索引中是如何保存“北京”和“大家”之間“隔多少單詞”。方便只檢索在一定距離之內的詞。
為什么要把帖子的url做為一個Field,因為要在搜索展示的時候先帖子地址取出來構建超鏈接,所以Field.Store.YES;一般不需要 對url進行檢索,所以Field.Index.NOT_ANALYZED 。根據《紅樓夢》構建的“詞:頁數”紙,在構建完成后就可以把原文《紅樓夢》扔了
案例:對1000至1100號帖子進行索引。“只要能看懂例子和文檔,稍作修改即可實現自己的需求”。除了基礎知識外,第三方開發包只要“能看懂,改改即可”
搜索
IndexSearcher是進行搜索的類,構造函數傳遞一個IndexReader。IndexSearcher的void Search(Query query, Filter filter, Collector results)方法用來搜索,Query是查詢條件, filter目前傳遞null, results是檢索結果,TopScoreDocCollector.create(1000, true)方法創建一個Collector,1000表示最多結果條數,Collector就是一個結果收集器。
搜索所采用的分詞算法必須和索引的一致。Query有很多子類,PhraseQuery是一個子類。 PhraseQuery用來進行多個關鍵詞的檢索,調用Add方法添加關鍵詞,query.Add(new Term("字段名", 關鍵詞)),PhraseQuery. SetSlop(int slop)用來設置關鍵詞之間的最大距離,默認是0,設置了Slop以后哪怕文檔中兩個關鍵詞之間沒有緊挨着也能找到。query.Add(new Term("字段名", 關鍵詞))query.Add(new Term("字段名2", 關鍵詞2))
類似於:where 字段名 contains 關鍵詞 and 字段名2 contains 關鍵詞2
如何實現字段名 contains 關鍵詞 or 字段名2 contains 關鍵詞2, BooleanQuery:MUST// 與運算;SHOULD // 或運算;MUST_NOT// 非運算
調用TopScoreDocCollector的GetTotalHits()方法得到搜索結果條數,調用Hits的TopDocs TopDocs(int start, int howMany)得到一個范圍內的結果(分頁),TopDocs的scoreDocs字段是結果ScoreDoc數組, ScoreDoc 的doc字段為Lucene.Net為文檔分配的id(為降低內存占用,只先返回文檔id),根據這個id調用searcher的Doc方法就能拿到Document了(放進去的是Document,取出來的也是Document);
調用doc.Get("字段名")可以得到文檔指定字段的值,注意只有Store.YES的字段才能得到,因為Store.NO的沒有保存全部內容,只保存了分割后的詞。
編寫檢索功能,搜索“網站 志願者”。練習分詞,用戶不用空格。如果確定用盤古分詞,那么用盤古的Segment類更方便。檢索不出來的可能的原因:路徑問題,分詞是否正確、盤古分詞如果指定忽略大小寫,則需要統一按照小寫進行搜索。Todo:第一個版本應該保存body和title,搜索結果形成超鏈接,不顯示正文。
文章重復索引的問題
總結下執行順序:發布文章的時候,創建索引,然后搜索文章的時候,就直接從索引庫進行搜索。
使用回顧
1、解壓PanGu4Lucene_V2.3.1.0.zip,把Dictionaries放到項目中,改名為Dict,然后把Dict文件夾下的文件都設置為“如果較新則復制。”(網站是沒有那個選項的。都用Web應用程序)
2、再添加對Lucene.Net.dll、PanGu.dll、PanGu.Lucene.Analyzer.dll(Pangu分詞和Lucene的一個橋接)。
設置斷點查找問題,斷點要設在關鍵性的行上,看是上面的問題還是下面的問題,二分法。