借助 Lucene.Net 构建站内搜索引擎(上)

前言:最近翻开了之前老杨(杨中科)的Lucene.Net站内搜索项目的教学视频,于是作为老杨脑残粉的我又跟着复习了一遍,学习途中做了一些笔记也就成了接下来您看到的这篇博文,仅仅是我的个人笔记,大神请呵呵一笑而过。相信做过站内搜索的.Net程序员应该对Lucene.Net不陌生,没做过的也许会问:就不是个查询嘛!为什么不能使用Like模糊查找呢?原因很简单:模糊查询的契合度太低,匹配关键字之间不能含有其他内容。最重要的是它会造成数据库全表扫描,效率低下,即使使用视图,也会造成数据库服务器"亚历山大"!因此,有必要了解一下Lucene.Net这个神器(也许现在早已不是)!

一、Lucene.Net简介

  

Lucene.Net只是一个全文检索开发包,不是一个成型的搜索引擎。

它的功能就是负责将文本数据按照某种分词算法进行切词,分词后的结果存储在索引库中,从索引库检索数据的速度灰常快。

  对以上加粗的词汇稍作下阐述:

  文本数据:Lucene.Net只能对文本信息进行检索,所以非文本信息要么转换成为文本信息,要么你就死了这条心吧!

  分词算法:将一句完整的话分解成若干词汇的算法  常见的一元分词(Lucene.Net内置就是一元分词,效率高,契合度低),二元分词,基于词库的分词算法(契合度高,效率低)...

  切词:将一句完整的话,按分词算法切成若干词语

  比如:"不是所有痞子都叫一毛" 这句话,如果根据一元分词算法则被切成: 不 是 所 有 痞 子 都 叫 一 毛

如果二元分词算法则切成: 不是 是所 所有 有痞 痞子 子都 都叫 叫一  一毛

如果基于词库的算法有可能:不是 所有 痞子 都叫 一毛 具体看词库

  索引库:简单的理解成一个提供了全文检索功能的数据库,见下图所示:

二、几种分词的使用

  毫无疑问,Lucene.Net中最核心的内容就是分词,下面我们来体验一下基本的一元分词、二元分词以及基于词库分词的代表:盘古分词。首先,我们准备一个ASP.Net Web项目(这里使用的是WebForms技术),引入Lucene.Net和PanGu的dll,以及加入CJK分词的两个class(均在附件下载部分可以下载),分词演示Demo的项目结构如下图所示:

2.1 一元分词

  核心代码

    protected void btnGetSegmentation_Click(object sender, EventArgs e)
    {
        string words = txtWords.Text;
        if (string.IsNullOrEmpty(words))
        {
            return;
        }

        Analyzer analyzer = new StandardAnalyzer(); // 标准分词 → 一元分词
        TokenStream tokenStream = analyzer.TokenStream("", new StringReader(words));
        Token token = null;
        while ((token = tokenStream.Next()) != null) // 只要还有词,就不返回null
        {
            string word = token.TermText(); // token.TermText() 取得当前分词
            Response.Write(word + "   |  ");
        }
    }

  效果演示

  可以看到一元分词将这句话的每个字都作为一个词组。前面提到,Lucene.Net维护着一个索引库,如果每个字都作为一个词组,那么索引库会变得尤为巨大,当然,分词的算法很简单,因此分词效率上会很高。

2.2 二元分词

  核心代码

    protected void btnGetSegmentation_Click(object sender, EventArgs e)
    {
        string words = txtWords.Text;
        if (string.IsNullOrEmpty(words))
        {
            return;
        }

        Analyzer analyzer = new CJKAnalyzer(); // CJK分词 → 二元分词
        TokenStream tokenStream = analyzer.TokenStream("", new StringReader(words));
        Token token = null;

        while ((token = tokenStream.Next()) != null) // 只要还有词,就不返回null
        {
            string word = token.TermText(); // token.TermText() 取得当前分词
            Response.Write(word + "   |  ");
        }
    }

  效果演示

  可以看到二元分词通过将两个字作为一个词组,在词组的数量上较一元分词有了一定减少,但是分词的效果仍然不佳,比如:个来 这个分词结果就不符合语义,加入索引库也会是没什么机会会被用到。

2.3 盘古分词

  使用步骤

  (1)从PanGu开发包中取得PanGu.dll 与 PanGu.Lucenet.Analyzer.dll并加入到项目中

  (2)从PanGu开发包中取得Dict文件,并在Bin目录下创建一个Dict文件夹将Dict文件一起copy进去

  效果演示

  可以看到,使用基于词库的盘古分词进行分词后的效果较前两种好得太多,不过中间的“就跑不脱”这个词组优点不符合语义。刚刚提到盘古分词是基于词库的分词,因此我们可以到词库里边去为跑不脱(四川方言)添加一个词组到词库当中。

  分词扩展

  词库就是我们刚刚加入到Bin/Dict目录下的Dict文件,借助PanGu开发包中的DictManage.exe打开Dict文件,为跑不脱添加一个词组吧!

  (1)找到DictManage词库管理工具

  (2)打开我们的Dict文件并添加一个词组

  (3)在DictManage.exe中查找词组,然后保存,设置新版本号

  (4)重新打开页面查看分词结果

  修改词库之后的分词结果是不是更加符合我们得常规思维习惯了呢?

三、一个最简单的搜索引擎

3.1 搭建项目

  这个Demo需要模拟的场景是一个BBS论坛,每天BBS论坛都会新增很多新的帖子,每篇帖子都会存入数据库。从前面介绍可知,数据库中的内容也会转换为文本信息存入索引库,用户在前端搜索时会直接从索引库中获取查询结果。整个流程如下图所示:

  我们仍然在之前分词Demo的基础上实现这个小Demo,整个项目的结构如下图所示:

  好了,准备一个Web页面来展示吧:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SearchEngineV1.aspx.cs" Inherits="Manulife.SearchEngine.LuceneNet.Views.SearchEngineV1" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>最简单的搜索引擎</title>
</head>
<body>
    <form id="mainForm" runat="server">
        <div align="center">
            <asp:Button ID="btnCreateIndex" runat="server" Text="Create Index" OnClick="btnCreateIndex_Click" />
            <asp:Label ID="lblIndexStatus" runat="server" Visible="false" />
            <hr />
            <asp:TextBox ID="txtKeyWords" runat="server" Text="" Width="250"></asp:TextBox>
            <asp:Button ID="btnGetSearchResult" runat="server" Text="Search" OnClick="btnGetSearchResult_Click" />
            <hr />
        </div>
        <div>
            <ul>
                <asp:Repeater ID="rptSearchResult" runat="server">
                    <ItemTemplate>
                        <li>Id:<%#Eval("Id") %><br />
                            <%#Eval("Msg") %></li>
                    </ItemTemplate>
                </asp:Repeater>
            </ul>
        </div>
    </form>
</body>
</html>

  页面的结构如下图所示:

  页面很简单,只有两个button,一个textbox,以及一个repeater列表。其中:

  (1)Create Index : 点击该按钮会遍历文章/帖子的文本文件夹,对每个帖子进行分词,并将分词后的结果存入索引库;

  (2)Search :点击该按钮会将用户输入的关键词与索引库中的内容进行匹配,并将匹配后的结果显示在repeater列表中;

3.2 创建索引

  核心代码:

    /// <summary>
    /// 创建索引
    /// </summary>
    protected void btnCreateIndex_Click(object sender, EventArgs e)
    {
        string indexPath = Context.Server.MapPath("~/Index"); // 索引文档保存位置
        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);  //unlock:强制解锁,待优化
            }
        }
        //  创建向索引库写操作对象  IndexWriter(索引目录,指定使用盘古分词进行切词,最大写入长度限制)
        //  补充:使用IndexWriter打开directory时会自动对索引库文件上锁
        IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate,
            IndexWriter.MaxFieldLength.UNLIMITED);

        for (int i = 1000; i < 1100; i++)
        {
            string txt = File.ReadAllText(Context.Server.MapPath("~/Upload/Articles/") + i + ".txt");
            //  一条Document相当于一条记录
            Document document = new Document();
            //  每个Document可以有自己的属性(字段),所有字段名都是自定义的,值都是string类型
            //  Field.Store.YES不仅要对文章进行分词记录,也要保存原文,就不用去数据库里查一次了
            document.Add(new Field("id", i.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            //  需要进行全文检索的字段加 Field.Index. ANALYZED
            //  Field.Index.ANALYZED:指定文章内容按照分词后结果保存,否则无法实现后续的模糊查询
            //  WITH_POSITIONS_OFFSETS:指示不仅保存分割后的词,还保存词之间的距离
            document.Add(new Field("msg", txt, Field.Store.YES, Field.Index.ANALYZED,
                Field.TermVector.WITH_POSITIONS_OFFSETS));
            //  防止重复索引,如果不存在则删除0条
            writer.DeleteDocuments(new Term("id", i.ToString()));// 防止已存在的数据 => delete from t where id=i
            //  把文档写入索引库
            writer.AddDocument(document);
            Console.WriteLine("索引{0}创建完毕", i.ToString());
        }

        writer.Close(); // Close后自动对索引库文件解锁
        directory.Close();  //  不要忘了Close,否则索引结果搜不到

        lblIndexStatus.Text = "索引文件创建成功!";
        lblIndexStatus.Visible = true;
        btnCreateIndex.Enabled = false;
    }

  效果展示:

  应用场景:

  在BBS论坛新发布一个帖子的事件时,添加到数据库之后,再进行创建索引的操作,保存到索引库,这样帖子内容就存了两份,一份在数据库,一份在索引库。

3.2 获取结果

  核心代码:

    /// <summary>
    /// 获取搜索结果
    /// </summary>
    protected void btnGetSearchResult_Click(object sender, EventArgs e)
    {
        string keyword = txtKeyWords.Text;

        string indexPath = Context.Server.MapPath("~/Index"); // 索引文档保存位置
        FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NoLockFactory());
        IndexReader reader = IndexReader.Open(directory, true);
        IndexSearcher searcher = new IndexSearcher(reader);
        // 查询条件
        PhraseQuery query = new PhraseQuery();
        // 等同于 where contains("msg",kw)
        query.Add(new Term("msg", keyword));
        // 两个词的距离大于100(经验值)就不放入搜索结果,因为距离太远相关度就不高了
        query.SetSlop(100);
        // TopScoreDocCollector:盛放查询结果的容器
        TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);
        // 使用query这个查询条件进行搜索,搜索结果放入collector
        searcher.Search(query, null, collector);
        // 从查询结果中取出第m条到第n条的数据
        // collector.GetTotalHits()表示总的结果条数
        ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs;
        // 遍历查询结果
        IList<SearchResult> resultList = new List<SearchResult>();
        for (int i = 0; i < docs.Length; i++)
        {
            // 拿到文档的id,因为Document可能非常占内存(DataSet和DataReader的区别)
            int docId = docs[i].doc;
            // 所以查询结果中只有id,具体内容需要二次查询
            // 根据id查询内容:放进去的是Document,查出来的还是Document
            Document doc = searcher.Doc(docId);
            SearchResult result = new SearchResult();
            result.Id = Convert.ToInt32(doc.Get("id"));
            result.Msg = HighlightHelper.HighLight(keyword, doc.Get("msg"));

            resultList.Add(result);
        }

        // 绑定到Repeater
        rptSearchResult.DataSource = resultList;
        rptSearchResult.DataBind();
    }

  效果展示:

附件下载

  Lucene.Net开发包 : 点我下载

  PanGu盘古分词开发包点我下载

  简单搜索引擎Demo点我下载

参考资料

(1)杨中科,《Lucene.Net站内搜索公开课》

(2)痞子一毛,《Lucene.Net

(3)MeteorSeed,《使用Lucene.Net实现全文检索

(4)Lucene.Net官方网站:http://lucenenet.apache.org/download.html

作者:周旭龙

出处:http://edisonchou.cnblogs.com/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

时间: 2024-10-10 08:24:31

借助 Lucene.Net 构建站内搜索引擎(上)的相关文章

站内搜索引擎的作用与技术选型

互联网已经是一个海量的信息资源库,所以产生了百度,谷歌这样的搜索引擎,帮助用户找到全网的信息,而每个网站,也同样需要一个站内搜索,帮助站内的用户检索需要的信息,从这个意义上讲,站内搜索其实是解决信息过载的一个解决方案,是每个网站必备的功能,也是提高用户体验的有效方式之一 一般来说,很多网站都是直接使用第三方的系统搭建网站,比如PHPCMS,DEDECMS,DISCUZ等,但有些CMS系统内置的搜索系统比较弱,搜索出来的结果往往相关性不是太好,影响用户体验,那么应该如何低成本,快速搭建一个强大的站

使用Lucene.NET实现站内搜索

使用Lucene.NET实现站内搜索 导入Lucene.NET 开发包 Lucene 是apache软件基金会一个开放源代码的全文检索引擎工具包,是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎.Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎.Lucene.Net 是 .NET 版的Lucene. 你可以在这里下载到最新的Lucene.NET 创建索引.更新索引.删除索引 搜索

一步步开发自己的博客 .NET版(5、Lucenne.Net 和 必应站内搜索)

前言 这次开发的博客主要功能或特点:    第一:可以兼容各终端,特别是手机端.    第二:到时会用到大量html5,炫啊.    第三:导入博客园的精华文章,并做分类.(不要封我)    第四:做个插件,任何网站上的技术文章都可以转发收藏 到本博客. 所以打算写个系类:<一步步搭建自己的博客> 一步步开发自己的博客  .NET版(1.页面布局.blog迁移.数据加载) 一步步开发自己的博客  .NET版(2.评论功能) 一步步开发自己的博客  .NET版(3.注册登录功能) 一步步开发自己

在ssh中利用Solr服务建立的界面化站内搜索

继上次匆匆搭建起结合solr和nutch的所谓站内搜索引擎之后,虽当时心中兴奋不已,可是看了看百度,再只能看看我的控制台的打印出每个索引项的几行文字,哦,好像差距还是有点大-- 简陋的信息显示环境最起码给了我一个信号,这条路没有走错,好吧,让我们来继续探索搜索引擎的奥秘吧. 上期回顾:上次主要是介绍了solrj,通过solrj的api与solr服务器进行通信,获取服务器上的索引数据以及在编写程序中遇到的一些问题和解决方法.本期主要是建立与solr服务器的通信,提供搜索界面输入关键字或搜索规则,根

新站SEO通过百度站内搜索建立索引之我见

虽说现在360搜索的势头非常强劲,但是百度凭借着高居50%以上的市场份额依旧是国内各大中小站长SEO最为重视的搜索引擎.然后,随着市场格局的不断变化,百度对站长的态度也越趋于明朗化,从推出站长平台到站内搜索,都在进一步的示好广大站长. 当然,想成为一名合格的SEOer,首先你必须对各主流搜索引擎的站长平台中所提供的工具了然于胸.然后,你才有资格在大家面前谈所谓的SEO.今天,鄙人也站在一位新手站长的角度,谈谈工具之一的百度站内搜索对于新站的好处. 一个站点,如果连索引量都建立不起来,然后你来跟我

利用Solr服务建立的界面化站内搜索---solr2

继上次匆匆搭建起结合solr和nutch的所谓站内搜索引擎之后,虽当时心中兴奋不已,可是看了看百度,再只能看看我的控制台的打印出每个索引项的几行文字,哦,好像差距还是有点大…… 简陋的信息显示环境最起码给了我一个信号,这条路没有走错,好吧,让我们来继续探索搜索引擎的奥秘吧. 上期回顾:上次主要是介绍了solrj,通过solrj的api与solr服务器进行通信,获取服务器上的索引数据以及在编写程序中遇到的一些问题和解决方法.本期主要是建立与solr服务器的通信,提供搜索界面输入关键字或搜索规则,根

基于lucene.net 和ICTCLAS2014的站内搜索的实现1

Lucene.net是一个搜索引擎的框架,它自身并不能实现搜索,需要我们自己在其中实现索引的建立,索引的查找.所有这些都是根据它自身提供的API来实现.Lucene.net本身是基于java的,但是经过翻译成.ne版本的,可以在ASP.net中使用这个来实现站内搜索. 要实现基于汉语的搜索引擎,首先的要实现汉语的分词.目前网上大部分都是利用已经有的盘古分词来实现的分词系统,但是盘古分词效果不太好.在这里我把最新的ICTCLAS2014嵌入到Lucene.net中.Lucene.net中所有的分词

Lucene.Net 站内搜索

Lucene.Net 站内搜索 一  全文检索: like查询是全表扫描(为性能杀手)Lucene.Net搜索引擎,开源,而sql搜索引擎是收费的Lucene.Net只是一个全文检索开发包(只是帮我们存数据取数据,并没有界面,可以看作一个数据库,只能对文本信息进行检索)Lucene.Net原理:把文本切词保存,然后根据词汇表的页来找到文章 二  分词算法: //一元分词算法(引用Lucene.Net.dll)  一元分词算法 //二元分词算法(CJK:China Japan Korean 需要再

关于优化排名,搜索引擎,站内优化的学习摘要

关于优化排名,搜索引擎,站内优化的学习摘要 你的排名你说了算,学习笔记摘要: 优化排名,不同的人员操作效果不一样,关键词排名行情是变量的,对与不同的网站在操作方式要非常慎重,让你的排名你说了算,掌握专业的SEO优化知识. 排序是有规律的还是没有规律的? 就是人为的为它写一些规则,让电脑去执行,是死的,是有规律的.做SEO就是一直在研究排序的规律. 淘宝也是一个搜索引擎,也有排序规则,与人气.销量.评价.价格这些是它的规则. 搜索引擎也有自己的规律,如何寻找规律呢?我们做调查,人数基数越少越不准确