最近公司开发一个知识库系统,毫无疑问需要用到站内搜索的功能。最终选用Lucene.Net,现将项目中的一些收获和核心代码整理如下:
1. Lucene.Net是什么以及工作原理?
Lucene.Net只是一个全文检索开发包,不是一个成型的搜索引擎,它的功能就是:把数据扔给Lucene.Net ,查询数据的时候从Lucene.Net 查询数据,可以看做是提供了全文检索功能的一个数据库。Lucene.Net不管文本数据怎么来的。用户可以基于Lucene.Net开发满足自己需求的搜索引擎。 Lucene.Net只能对文本信息进行检索。如果不是文本信息,要转换为文本信息,比如要检索Excel文件,就要用NPOI把Excel读取成字符串,然后把字符串扔给Lucene.Net。Lucene.Net会把扔给它的文本切词保存,加快检索速度。
![](https://images0.cnblogs.com/blog/37762/201303/06135621-fe563c51c3a944ec975dcce4283f30f7.png)
2. 分词
分词是核心的算法,搜索引擎内部保存的就是一个个的“词(Word)”。英文分词很简单,按照空格分隔就可以。中文则麻烦,把“北京,Hi欢迎你们大家” 拆成“北京 Hi 欢迎 你们 大家”。“the”,“,”,“和”,“啊”,“的”等对于搜索来说无意义的词一般都属于不参与分词的无意义单词(noise word)。
Lucene.Net中不同的分词算法就是不同的类。所有分词算法类都从Analyzer类继承,不同的分词算法有不同的优缺点。
(*)内置的StandardAnalyzer是将英文按照空格、标点符号等进行分词,将中文按照单个字进行分词,一个汉字算一个词即ChineseAnalyzer
(*)二元分词算法,每两个汉字算一个单词,“欢迎你们大家”会分词为“欢迎 迎你 你们 们大 大家”,网上找到的一个二元分词算法CJKAnalyzer。
(*)基于词库的分词算法,基于一个词库进行分词,可以提高分词的成功率。有庖丁解牛、盘古分词等。效率低
3. 盘古分词算法使用
Analyzer analyzer = new PanGuAnalyzer(); TokenStream tokenStream = analyzer.TokenStream("", new StringReader(TextBox1.Text)); Lucene.Net.Analysis.Token token = null; while ((token = tokenStream.Next()) != null) { Response.Write(token.TermText()+"<br/>"); }
一下是上述代码运行的结果,分词的准确率还是很高的:
4 Lucene.Net应用实例 源代码
1. 创建索引
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
string indexPath = "c:/index"; FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory()); bool isUpdate = IndexReader.IndexExists(directory); if (isUpdate) { //如果索引目录被锁定(比如索引过程中程序异常退出),则首先解锁 if (IndexWriter.IsLocked(directory)) { IndexWriter.Unlock(directory); } } var writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, IndexWriter.MaxFieldLength.UNLIMITED); var wc = new WebClient(); wc.Encoding = Encoding.UTF8; //否则下载的是乱码 int maxId = GetMaxId(); for (int i = 1; i <= maxId; i++) { string url = "http://localhost:8081/showtopic-" + i + ".aspx"; string html = wc.DownloadString(url); var doc = new HTMLDocumentClass(); doc.designMode = "on"; //不让解析引擎去尝试运行javascript doc.IHTMLDocument2_write(html); doc.close(); string title = doc.title; string body = doc.body.innerText; //去掉标签 //为避免重复索引,所以先删除number=i的记录,再重新添加 writer.DeleteDocuments(new Term("number", i.ToString())); var document = new Document(); //只有对需要全文检索的字段才ANALYZED document.Add(new Field("number", i.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); document.Add(new Field("title", title, Field.Store.YES, Field.Index.NOT_ANALYZED)); document.Add(new Field("body", body, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS)); writer.AddDocument(document); } writer.Close(); directory.Close(); //不要忘了Close,否则索引结果搜不到;
2.搜索
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
string indexPath = "c:/index"; string kw = TextBox1.Text; FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NoLockFactory()); IndexReader reader = IndexReader.Open(directory, true); var searcher = new IndexSearcher(reader); var query = new PhraseQuery(); //todo:把用户输入的关键词进行拆词 foreach (string word in CommonHelper.SplitWord(TextBox1.Text)) //先用空格,让用户去分词,空格分隔的就是词“计算机 专业” { query.Add(new Term("body", word)); } //query.Add(new Term("body","计算机")); //query.Add(new Term("body", "专业")); query.SetSlop(100); TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true); searcher.Search(query, null, collector); ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs; var listResult = new List<SearchResult>(); for (int i = 0; i < docs.Length; i++) { int docId = docs[i].doc; //取到文档的编号(主键,这个是Lucene .net分配的) //检索结果中只有文档的id,如果要取Document,则需要Doc再去取 //降低内容占用 Document doc = searcher.Doc(docId); //根据id找Document string number = doc.Get("number"); string title = doc.Get("title"); string body = doc.Get("body"); var result = new SearchResult(); result.Number = number; result.Title = title; result.BodyPreview = Preview(body, TextBox1.Text); listResult.Add(result); } repeaterResult.DataSource = listResult; repeaterResult.DataBind();
3.关键字高亮
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
//创建HTMLFormatter,参数为高亮单词的前后缀 var simpleHTMLFormatter = new SimpleHTMLFormatter("<font color=\"red\">", "</font>"); //创建 Highlighter ,输入HTMLFormatter 和 盘古分词对象Semgent var highlighter = new Highlighter(simpleHTMLFormatter, new Segment()); //设置每个摘要段的字符数 highlighter.FragmentSize = 100; //获取最匹配的摘要段 String bodyPreview = highlighter.GetBestFragment(keyword, body); return bodyPreview;
"http://news.oceansoft.com.cn/showtopic-" + i + ".aspx";
string html = wc.DownloadString(url);
这是我从公司的内部论坛上下载页面内容,然后给这些内容建立索引。拿到代码之后需要实际情况修改