站内搜索之--4--盘古分词算法

 上一篇:站内搜索--3--之Lucene.Net使用

盘古分词最新字典文件下载位置

http://pangusegment.codeplex.com/releases/view/47411

默认字典位置为 ..\Dictionaries 可以通过设置PanGu.xml 文件来修改字典的位置

Demo.exe 分词演示程序

DictManage.exe 字典管理程序

PanGu.xml 分词配置文件

PanGu.HighLight.dll 高亮组件

使用具体用法参考《PanguMannual.pdf》

配置详细:

打开PanGu4Lucene\WebDemo\Bin,将Dictionaries添加到项目根路径(改名为Dict)并且选中所有的文件在属性下设置复制到输出目录为:如果较新则复制。其中ChsDoubleName1.txt、ChsDoubleName2.txt存放的是中文名字中常用的第二个字这样就能猜测人名、ChsSingleName.txt(存放的是单名的第二个字)、Dict.dct存放的是词库需要使用PanGu_Release的DictManage.exe才能打开查看和操作(工作的项目中要将行业单词添加到词库中,比如餐饮搜索、租房搜索、视频搜索等),将PanGu.dll、PanGu.Lucene.Analyzer.dll拷贝到lib并添加对PanGu.dll(同目录下不要有Pangu.xml,那个默认的配置文件的选项对于分词结果有很多无用信息)、PanGu.Lucene.Analyzer.dll的引用

案例:Text文件下的Text2.aspx

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 />");

            }

 

案例:利用Lucene.Net和盘古分词对某博客网站1100号帖子进行索引(把帖子的url做为一个Field,因为要在搜索展示的时候先帖子地址取出来构建超链接,所以Field.Store.YES;一般不需要对url进行检索,所以Field.Index.NOT_ANALYZED)---参考站内搜索复习的text3.aspx

private static ILog log = LogManager.GetLogger(typeof(text3));

 

下面是创建索引的代码:

        protected void Button1_Click(object sender, EventArgs e)

        {

            //把帖子的url做为一个Field,因为要在搜索展示的时候先帖子地址取出来构建超链接,所以Field.Store.YES;一般不需要对url进行检索,所以Field.Index.NOT_ANALYZED

            //一下的代码只需要使用的时候知道怎么修改就可以没必要全弄懂

            string indexPath = @"D:\index";

            FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory());//表示将创建的索引文件保存在indexPath目录下

            bool isUpdate = IndexReader.IndexExists(directory);//)判断目录directory是否是一个索引目录

            if (isUpdate)

            {

                //如果索引目录被锁定(比如索引过程中程序异常退出),则首先解锁

                if (IndexWriter.IsLocked(directory))//) 判断目录是否锁定,在对目录写之前会先把目录锁定

                {

                    IndexWriter.Unlock(directory);//如果没有锁定则需要手动锁定因为。两个IndexWriter无法同时写一个索引文件

                }

            }

            IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);//IndexWriter把输入写入索引的时候,Lucene.net是把写入的文件用指定的分词算法将文章分词(这样检索的时候才能查的快),然后将词放入索引文件。

            WebClient wc = new WebClient();

  wc.Encoding = Encoding.UTF8;//指定编码否则下载的是乱码

            for (int i = 1; i < 100; i++)

            {

                string url = "http://***"+i+".aspx";

                string str=  wc.DownloadString(url);

                Document document = new Document();//创建一行记录

                document.Add(new Field("number", i.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));

                //向索引库中添加字段,number表示帖子的序号;

                //第二个参数:store表示是否存储value值,可选值 Field.Store.YES存储, Field.Store.NO不存储, Field.Store.COMPRESS压缩存储;默认只保存分词以后的一堆词,而不保存分词之前的内容,搜索的时候无法根据分词后的词还原原文,因此如果要显示原文(比如文章正文)则需要设置存储;

                //第三个参数: index表示如何创建索引,可选值Field.Index. NOT_ANALYZED(不创建索引),Field.Index. ANALYZED(创建索引);创建索引的字段才可以比较好的检索。是否碎尸万段!是否需要按照这个字段进行“全文检索”只有对需要全文检索的字段才ANALYZED

                document.Add(new Field("body", str, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));//body表示全文检索的内容

                writer.AddDocument(document);

                //第四个参数:termVector表示如何保存索引词之间的距离。“北京欢迎你们大家”,索引中是如何保存“北京”和“大家”之间“隔多少单词”。方便只检索在一定距离之内的词。

 

                log.Debug("索引" + i + "完毕");

              

            }

            writer.Close();

            directory.Close();//不要忘了Close,否则索引结果搜不到

            log.Debug("全部索引完毕");

          

        }

 

以下是搜索的一个案例;(暂时需要在文本框手动空格隔开计算机 专业)

protected void Button2_Click(object sender, EventArgs e)

        {

            string indexPath = @"D:\index";

            string kw = TextBox1.Text;

            FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NoLockFactory());

            IndexReader reader = IndexReader.Open(directory, true);

            //IndexSearcher需要传递一个IndexReader对象

            IndexSearcher searcher = new IndexSearcher(reader);

            //PhraseQuery用来进行多个关键词的检索,调用Add方法添加关键词

            PhraseQuery query = new PhraseQuery();

            //先用空格,让用户去分词,空格分隔的就是词“计算机   专业”

            foreach (string word in kw.Split(' '))

            {

                query.Add(new Term("body", word));

            }

            //PhraseQuery. SetSlop(int slop)用来设置关键词之间的最大距离,默认是0,设置了Slop以后哪怕文档中两个关键词之间没有紧挨着也能找到。

            query.SetSlop(100);

            TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);

            searcher.Search(query, null, collector);

            ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs;

            for (int i = 0; i < docs.Length; i++)

            {

                //取得文档的编号(主键)是Lucene.Net提供的

                int docId = docs[i].doc;

                //检索文档中只有文档的ID,如果要取得Document课通过Doc提到

                Document doc = searcher.Doc(docId);

                string number=doc.Get("number");

                string body=doc.Get("body");

                Response.Write(number+"<br />");

                Response.Write(body + "<br />");

                Response.Write("<hr />");

            }

        }

 

网页采集

上述创建索引和搜索存在的不足:使用WebClient下载的是整个html包括了html标签和文字,要解决的问题是下载html后解析它提取innerText(可以尝试使用正则表达式)下面使用微软的Microsoft.mshtml 的方案IE浏览器就是使用它解析html的。WebClient抓取到的是页面的源代码,需要得到页面的标题、文字、超链接。用mshtml进行html的解析,IE就是使用mshtml进行网页解析的。添加对Microsoft.mshtml的引用(如果是VS2010,修改这个引用的“嵌入互操作类型”为False。(*)“复制本地”设置为True、“特定版本”设置为False,这样在没有安装VS的机器中也可以用。)

HTMLDocumentClass doc = new HTMLDocumentClass();

doc.designMode = "on"; //不让解析引擎去尝试运行javascript

doc.IHTMLDocument2_write(要解析的代码);

doc.title、doc.body.innerText,更多用法自己探索。

所有Dom方法都能在mshtml中调用

 

高亮显示

 

高亮显示,只显示包含关键词的部分。参考盘古分词的文档的高亮显示的组件。

高亮组件PanGu.HighLight.dll 调用方法:将PanGu.HighLight.dll和PanGu.HighLight.pdb拷贝到lib下并且添加对dll的引用

//创建HTMLFormatter,参数为高亮单词的前后缀

PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter =

new PanGu.HighLight.SimpleHTMLFormatter("<font color=\"red\">", "</font>");

//创建Highlighter ,输入HTMLFormatter 和盘古分词对象Semgent

PanGu.HighLight.Highlighter highlighter =

new PanGu.HighLight.Highlighter(simpleHTMLFormatter,

new Segment());

//设置每个摘要段的字符数

highlighter.FragmentSize = 50;

//获取最匹配的摘要段

String abstract = highlighter.GetBestFragment(keywords, news.Content);

 

最大帖子编号

 

最大帖子编号:因为RSS中就是最新帖子信息,所以读取RSS的一个item的link就是帖子地址,最大帖子编号:因为RSS中就是最新帖子信息,所以读取RSS的一个item的link就是帖子地址,使用Linq To XML分析RSS。用正则表达式就可以分析出最大帖子编号用正则表达式就可以分析出最大帖子编号。

 

解决重复索引

不用每次启动索引之前清空索引库,因为耗时会很长,这段时间内用户会搜不到信息。在AddDocument之前先移除旧有文档:

indexWriter.DeleteDocuments(new Term("url", aurl));//删除旧的收录//注意DeleteDocuments不是静态方法

删除索引中所有url字段的值等于aurl的Document,相当于delete from tttt where url=@url。

 

解决自动分词

下面通过一个案例实现上述改进:

在Text下新建SearchResult.cs类实现存数需要绑定在Repeater控件上的信息、新建HelperText.cs类,分词方法,接受用户输入的搜索词通过盘古分词进行分词。

public class SearchResult

    {

        //索引的编号

        public string Number { get;set;}

        //标题

        public string Title { get; set; }

        //文章

        public string BodyView { get; set; }

    }

 

//分词方法,接受用户输入的搜索词通过盘古分词进行分词

public static string[] SpliteWord(string word)

        {

            List<string> list = new List<string>();

            Analyzer analyzer = new PanGuAnalyzer();

            TokenStream tokenStream = analyzer.TokenStream("", new StringReader(word));

            Lucene.Net.Analysis.Token token = null;

            while ((token = tokenStream.Next()) != null)

            {

                list.Add(token.TermText());

            }

            return list.ToArray();

        }

 

下面是创建索引的方法:

  private static ILog log = LogManager.GetLogger(typeof(text4));

        protected void Button1_Click(object sender, EventArgs e)

        {

 

            //把帖子的url做为一个Field,因为要在搜索展示的时候先帖子地址取出来构建超链接,所以Field.Store.YES;一般不需要对url进行检索,所以Field.Index.NOT_ANALYZED

            //一下的代码只需要使用的时候知道怎么修改就可以没必要全弄懂

            string indexPath = @"D:\index";

            FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory());//表示将创建的索引文件保存在indexPath目录下

            bool isUpdate = IndexReader.IndexExists(directory);//)判断目录directory是否是一个索引目录

            //判断一下isUpdate的原因: IndexWriter的第三个参数表示是否创建索引文件夹,如果设置为true那么无论索引文件夹是否存在都是覆盖,

            if (isUpdate)

            {

                //如果索引目录被锁定(比如索引过程中程序异常退出),则首先解锁

                if (IndexWriter.IsLocked(directory))//) 判断目录是否锁定,在对目录写之前会先把目录锁定

                {

                    IndexWriter.Unlock(directory);//如果没有锁定则需要手动锁定因为。两个IndexWriter无法同时写一个索引文件

                }

            }

            IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);//IndexWriter把输入写入索引的时候,Lucene.net是把写入的文件用指定的分词算法将文章分词(这样检索的时候才能查的快),然后将词放入索引文件。第三个参数!isUpdate的意义: IndexWriter的第三个参数表示是否创建索引文件夹,如果设置为true那么无论索引文件夹是否存在都是覆盖,如果为false则只是更新

            WebClient wc = new WebClient();

            wc.Encoding = Encoding.UTF8;//指定编码否则下载的是乱码

 

            for (int i = 1; i <GetMaxId(); i++)

            {

                string url = "http://localhost:8080/showtopic-" + i + ".aspx";

                string str = wc.DownloadString(url);

                Document document = new Document();//创建一行记录

 

                HTMLDocumentClass doc = new HTMLDocumentClass();

                doc.designMode = "on"; //阻止解析引擎去尝试运行javascript

                doc.IHTMLDocument2_write(str);//解析下载的html

                doc.close();

                string title = doc.title;//获取当前下载页面的标题

                string body = doc.body.innerText;//获取页面的innerText

 

 

                //因为每次重新启动索引的时候原来的并不会的被删掉,这样反复索引索引库会点的越来越大,但是Lucene并没有提供更新索引的方法,这样只能先删除后添加,所以在添加以前删掉序号不管有没有这个索引

                writer.DeleteDocuments(new Term("number",i.ToString()));//删除旧的收录

                document.Add(new Field("number", i.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));

 

                //向索引库中添加字段,number表示帖子的序号;

                //第二个参数:store表示是否存储value值,可选值 Field.Store.YES存储, Field.Store.NO不存储, Field.Store.COMPRESS压缩存储;默认只保存分词以后的一堆词,而不保存分词之前的内容,搜索的时候无法根据分词后的词还原原文,因此如果要显示原文(比如文章正文)则需要设置存储;

                //第三个参数: index表示如何创建索引,可选值Field.Index. NOT_ANALYZED(不创建索引),Field.Index. ANALYZED(创建索引);创建索引的字段才可以比较好的检索。是否碎尸万段!是否需要按照这个字段进行“全文检索”只有对需要全文检索的字段才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, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));//body表示全文检索的内容

                writer.AddDocument(document);

 

                //第四个参数:termVector表示如何保存索引词之间的距离。“北京欢迎你们大家”,索引中是如何保存“北京”和“大家”之间“隔多少单词”。方便只检索在一定距离之内的词。

 

                log.Debug("索引" + i + "完毕");

 

            }

            writer.Close();

            directory.Close();//不要忘了Close,否则索引结果搜不到

            log.Debug("全部索引完毕");

        }

下面是搜索的实现:

protected void Button2_Click(object sender, EventArgs e)

        {

            string indexPath = @"D:\index";

            string kw = TextBox1.Text;

            FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NoLockFactory());

            IndexReader reader = IndexReader.Open(directory, true);

            //IndexSearcher需要传递一个IndexReader对象

            IndexSearcher searcher = new IndexSearcher(reader);

            //PhraseQuery用来进行多个关键词的检索,调用Add方法添加关键词

            PhraseQuery query = new PhraseQuery();

            //自动分词

            foreach (string word in HelperText.SpliteWord(kw))

            {

                query.Add(new Term("body", word));

            }

            //PhraseQuery. SetSlop(int slop)用来设置关键词之间的最大距离,默认是0,设置了Slop以后哪怕文档中两个关键词之间没有紧挨着也能找到。

            query.SetSlop(100);

            TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);

            searcher.Search(query, null, collector);

            ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs;

            List<SearchResult> listSr = new List<SearchResult>();

            for (int i = 0; i < docs.Length; i++)

            {

                //取得文档的编号(主键)是Lucene.Net提供的

                int docId = docs[i].doc;

              

                //检索文档中只有文档的ID,如果要取得Document课通过Doc提到

              

              

                Document doc = searcher.Doc(docId);

                string body = doc.Get("body");

                string number = doc.Get("number");

                string title= doc.Get("title");

                SearchResult sr = new SearchResult();

                sr.Number = number;

                sr.Title =title;

 

                //string bodyView = HightKeyWord(kw, body);

 

                sr.BodyView = HightKeyWord(kw, body);

 

                listSr.Add(sr);

             

            }

            Repeater1.DataSource = listSr;

            Repeater1.DataBind();

        }

下面是实现关键词高亮显示的实现:

/// <summary>

        /// 关键词高亮显示

        /// </summary>

        /// <param name="keywords">高亮词</param>

        /// <param name="body">文档</param>

        /// <returns></returns>

 

private static string HightKeyWord(string keywords, string body)

        {

            //创建HTMLFormatter,参数为高亮单词的前后缀

            PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter =

            new PanGu.HighLight.SimpleHTMLFormatter("<font color=\"red\">", "</font>");

            //创建Highlighter ,输入HTMLFormatter 和盘古分词对象Semgent

            PanGu.HighLight.Highlighter highlighter =

            new PanGu.HighLight.Highlighter(simpleHTMLFormatter,

            new Segment());

            //设置每个摘要段的字符数

            highlighter.FragmentSize = 50;

            //获取最匹配的摘要段

            string bodyView = highlighter.GetBestFragment(keywords, body);

            //需要改进的地方:如果文档中木有符合搜索关键词应该做的处理:如string.IsNullOrEmpty(bodyView)返回“抱歉没有符合你要求的信息”

            if (string.IsNullOrWhiteSpace(bodyView))

            {

                bodyView = "非常抱歉,没有查询到您所需的信息!!";

            }

            return bodyView;

           

        }

下面是获取最大帖子编号的实现:

/// <summary>

        /// 获取最大的帖子编号页面

        /// </summary>

        /// <returns></returns>

        private int GetMaxId()

        {

            string rssUrl = "http:/**/rss.aspx";

            //通过查看rss发现最大编号的规律,下面附上了rss部分格式

            //下载XML文档

            XDocument xd = XDocument.Load(rssUrl);

            //一个XML文档有且只有一个根节点,获取根节点channel(使用元素Element而不采用Nodes,是因为Element有更多的方法可选)

            XElement channel = xd.Root.Element("channel");

            //得到根节点channel下第一个item节点rss中有很多item节点所以使用Elements,一个item节点就是一个内容的主要部分,主要保存了文章标题和链接地址

            XElement item= channel.Elements("item").First();

            //获得帖子最大编号的文章的链接地址,因为在rss中最新更新的订阅都在最前面

            string linkUrl=  item.Element("link").Value;

            //使用正则表达式匹配出最大编号id

            Match match=  Regex.Match(linkUrl, @"showtopic-(\d+)\.aspx");

            int id =Convert.ToInt32( match.Groups[1].Value);

            return id;

        }

下面是前端展示:

<ul>

            <asp:Repeater ID="Repeater1" runat="server">

                <ItemTemplate>

                    <li>

                        <a href='http://**<%#Eval("Number") %>.aspx'><%#Eval("Title") %>

                        </a><%#Eval("BodyView")%>

                    </li>

                </ItemTemplate>

            </asp:Repeater>

        </ul>

 效果:

posted on 2013-01-14 21:53  aolinwxfx  阅读(1436)  评论(1编辑  收藏  举报

导航