Lucene.net+盘古分词 做站内搜索
Lucene.Net简介:
Lucene.Net是由Java版本的Lucene(卢思银)移植过来的,所有的类、方法都几乎和Lucene一模一样,因此使用时参考Lucene即可。
Lucene.Net只是一个全文检索开发包,不是一个成型的搜索引擎,它的功能就是把数据扔给Lucene.Net,查询数据的时候从Lucene.Net中查询数据,可以看做是提供了全文检索功能的数据库。Lucene.Net不管文本数据怎么来的。用户可以基于Lucene.Net开发满足自己需求的搜索引擎。Lucene.Net只能对文本信息进行检索。如果不是文本信息,要转换为文本信息。
Lucene.Net内置一元分词算法,例:“北京欢迎你”,分词之后“北” ”京” 欢” ”迎 ” ”你”。
明显不适用中文分词,所以这里需要用到一些中文的分词算法:庖丁解牛,盘古分词
下面介绍Lucene.Net+盘古分词如何在项目中应用
第一步:准备相关资料
盘古分词: 链接:https://pan.baidu.com/s/1kUBe95X 密码:mrqi
Lucene.Net :链接:https://pan.baidu.com/s/1eRSjLJ0 密码:z3zs
第二步:添加
1、将PanGu_Release_V2.3.1.0文件下的Dictionaries添加到项目根路径下并改名为Dict,添加PanGu.dll,PanGu.Lucene.Analyzer的引用
2、把Dict目录下的所有文件右击“属性”->“复制到输出目录”设定为“如果较新则赋值”,这样每次生成的时候都会自动把文件拷贝到“bin\Debug”下
3、添加Lucene.dll的引用
第三步:
1 //对输入的搜索条件进行分词 2 public static string[] PanGuSplitWord(string str) 3 { 4 List<string> list = new List<string>(); 5 Analyzer analyzer = new PanGuAnalyzer(); 6 TokenStream tokenStream = analyzer.TokenStream("", new StringReader(str)); 7 Lucene.Net.Analysis.Token token = null; 8 while ((token = tokenStream.Next()) != null) 9 { 10 list.Add(token.TermText()); 11 } 12 return list.ToArray(); 13 } 14 15 16 // /创建HTMLFormatter,参数为高亮单词的前后缀 17 public static string CreateHightLight(string keywords, string Content) 18 { 19 PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter = 20 new PanGu.HighLight.SimpleHTMLFormatter("<font color=\"red\">", "</font>"); 21 //创建Highlighter ,输入HTMLFormatter 和盘古分词对象Semgent 22 PanGu.HighLight.Highlighter highlighter = 23 new PanGu.HighLight.Highlighter(simpleHTMLFormatter, 24 new Segment()); 25 //设置每个摘要段的字符数 26 highlighter.FragmentSize = 150; 27 //获取最匹配的摘要段 28 return highlighter.GetBestFragment(keywords, Content); 29 30 }
1 public enum LuceneEnumType 2 { 3 Add,//向添加 4 Delete//删除 5 } 6 7 8 private void WriteSearchContent() 9 { 10 string indexPath = @"C:\lucenedir";//注意和磁盘上文件夹的大小写一致,否则会报错。将创建的分词内容放在该目录下。 11 FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory());//指定索引文件(打开索引目录) FS指的是就是FileSystem 12 bool isUpdate = IndexReader.IndexExists(directory);//IndexReader:对索引进行读取的类。该语句的作用:判断索引库文件夹是否存在以及索引特征文件是否存在。 13 if (isUpdate) 14 { 15 //同时只能有一段代码对索引库进行写操作。当使用IndexWriter打开directory时会自动对索引库文件上锁。 16 //如果索引目录被锁定(比如索引过程中程序异常退出),则首先解锁(提示一下:如果我现在正在写着已经加锁了,但是还没有写完,这时候又来一个请求,那么不就解锁了吗?这个问题后面会解决) 17 if (IndexWriter.IsLocked(directory)) 18 { 19 IndexWriter.Unlock(directory); 20 } 21 } 22 IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);//向索引库中写索引。这时在这里加锁。 23 while (queue.Count > 0) 24 { 25 IndexContent indexContent =queue.Dequeue();//出队 26 writer.DeleteDocuments(new Term("id", indexContent.Id.ToString())); 27 if (indexContent.LuceneEnumType == LuceneEnumType.Delete) 28 { 29 continue; 30 } 31 Document document = new Document();//表示一篇文档。 32 33 //Field.Store.YES:表示是否存储原值。只有当Field.Store.YES在后面才能用doc.Get("number")取出值来.Field.Index. NOT_ANALYZED:不进行分词保存 34 document.Add(new Field("id", indexContent.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); 35 36 //Field.Index. ANALYZED:进行分词保存:也就是要进行全文的字段要设置分词 保存(因为要进行模糊查询) 37 38 //Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS:不仅保存分词还保存分词的距离。 39 document.Add(new Field("title", indexContent.Title, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS)); 40 41 document.Add(new Field("content", indexContent.Content, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS)); 42 writer.AddDocument(document); 43 } 44 45 writer.Close();//会自动解锁。 46 directory.Close();//不要忘了Close,否则索引结果搜不到 47 }
1 /// <summary> 2 /// 搜索 3 /// </summary> 4 public List<SearchResultViewModel> SearchBookContent() 5 { 6 string indexPath = @"C:\lucenedir"; 7 string kw = Request["txtSearchContent"]; 8 string[] keywords = Common.WebCommon.PanGuSplitWord(kw); 9 FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NoLockFactory()); 10 IndexReader reader = IndexReader.Open(directory, true); 11 IndexSearcher searcher = new IndexSearcher(reader); 12 //搜索条件 13 PhraseQuery query = new PhraseQuery(); 14 foreach (string word in keywords)//先用空格,让用户去分词,空格分隔的就是词“计算机 专业” 15 { 16 query.Add(new Term("content", word)); 17 } 18 //query.Add(new Term("body","语言"));--可以添加查询条件,两者是add关系.顺序没有关系. 19 // query.Add(new Term("body", "大学生")); 20 // query.Add(new Term("content", kw));//body中含有kw的文章 21 query.SetSlop(100);//多个查询条件的词之间的最大距离.在文章中相隔太远 也就无意义.(例如 “大学生”这个查询条件和"简历"这个查询条件之间如果间隔的词太多也就没有意义了。) 22 //TopScoreDocCollector是盛放查询结果的容器 23 TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true); 24 searcher.Search(query, null, collector);//根据query查询条件进行查询,查询结果放入collector容器 25 ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs;//得到所有查询结果中的文档,GetTotalHits():表示总条数 TopDocs(300, 20);//表示得到300(从300开始),到320(结束)的文档内容. 26 //可以用来实现分页功能 27 List<SearchResultViewModel> searchResultList = new List<SearchResultViewModel>(); 28 for (int i = 0; i < docs.Length; i++) 29 { 30 // 31 //搜索ScoreDoc[]只能获得文档的id,这样不会把查询结果的Document一次性加载到内存中。降低了内存压力,需要获得文档的详细内容的时候通过searcher.Doc来根据文档id来获得文档的详细内容对象Document. 32 int docId = docs[i].doc;//得到查询结果文档的id(Lucene内部分配的id) 33 Document doc = searcher.Doc(docId);//找到文档id对应的文档详细信息 34 SearchResultViewModel viewModel = new SearchResultViewModel(); 35 viewModel.Id = int.Parse(doc.Get("id")); 36 viewModel.Title = doc.Get("title"); 37 viewModel.Url = "/Book/ShowDetail/?id=" + viewModel.Id; 38 viewModel.Content =Common.WebCommon.CreateHightLight(kw, doc.Get("content")); 39 searchResultList.Add(viewModel); 40 } 41 42 SearchDetails searchDetail = new SearchDetails(); 43 searchDetail.KeyWords = kw; 44 searchDetail.Id = Guid.NewGuid(); 45 searchDetail.SearchDateTime = DateTime.Now; 46 searchDetailService.AddEntity(searchDetail); 47 return searchResultList; 48 49 }
public class IndexContent { public string Id { get; set; } public string Title { get; set; } public string Content { get; set; } public LuceneEnumType LuceneEnumType { get; set; } } public enum DeleteEnumType { /// <summary> /// 正常显示 /// </summary> Normal=0, /// <summary> /// 逻辑删除 /// </summary> LogicDelete=1, /// <summary> /// 物理删除 /// </summary> PhyicDelete=2 }
1 public sealed class IndexManager 2 { 3 private static readonly IndexManager indexManager = new IndexManager(); 4 private IndexManager() 5 { 6 } 7 public static IndexManager GetInstance() 8 { 9 return indexManager; 10 } 11 Queue<IndexContent> queue = new Queue<IndexContent>(); 12 /// <summary> 13 /// 调用该方法,向队列中添加数据 14 /// </summary> 15 /// <param name="id"></param> 16 /// <param name="title"></param> 17 /// <param name="content"></param> 18 public void AddOrEditQueue(string id,string title,string content) 19 { 20 IndexContent indexContent = new IndexContent(); 21 indexContent.Content = content; 22 indexContent.Id = id; 23 indexContent.Title = title; 24 indexContent.LuceneEnumType = LuceneEnumType.Add; 25 queue.Enqueue(indexContent); 26 } 27 /// <summary> 28 /// 删除 29 /// </summary> 30 /// <param name="id"></param> 31 public void DeleteQueue(string id) 32 { 33 IndexContent indexContent = new IndexContent(); 34 indexContent.Id = id; 35 indexContent.LuceneEnumType = LuceneEnumType.Delete; 36 queue.Enqueue(indexContent); 37 } 38 39 /// <summary> 40 /// 创建线程 41 /// </summary> 42 public void CreateThread() 43 { 44 Thread myThread = new Thread(CreateSearchIndex); 45 myThread.IsBackground = true; 46 myThread.Start(); 47 } 48 }
第四步:
在程序开始时
IndexManager.GetInstance().CreateThread();//开启线程,扫描队列获取信息写到Lucene.Net中。