当前位置: 首页 > news >正文

Community Server系列之十:让CS2支持中文搜索

        前面几节里有朋友提到CS2对中文搜索支持的不好,那么这一节就提前到这里来讲讲怎样解决CS2对中文搜索的问题。
        我们都知道,英文和中文语言上的不同导致了处理英文和中文的不同方法,最明显的不同就是英文是以单词为最小单位,而中文则是以字为最小单位,这样造成了程序上的不一样,而在CS2中默认的搜索模块只考虑到英文这一方面,对中文相当于不认识了,怎样让其认识中文呢,这就需要我们来分析CS2处理搜索的机制。
        虽然CS2中使用的分词搜索,但其技术本身并不是很复杂,我们完全可以自己动手来改造其搜索效果。简单一点我就直截了当的介绍其分词搜索的原理吧。与我们常见的一些使用SQL语句在数据结构里搜索不同,分词搜索把需要搜索的数据预先进行索引(这里的索引不是通常所指的数据库本身的索引),在搜索引擎里的文章最小单位是词,英文按照单词,也就是按空格分开,中文就需要用到分词技术了,把一篇文章智能的分成多个词语的组合的技术,这也是搜索技术的核心,搜索结果的理想程度很大程度取决于分词的理想程度,把一篇文章分开为词语的组合后,将其内容逐个记录并保存权重值,当然这里也涉及到一些高级技术,在CS中使用了较简单的方式,直接记录词在其文章中的信息。搜索文章的时候在此即可快速定位到需要的文章。说了这么多感觉比较抽象,还是结合实例来说吧。
        打开解决方案我们会看到有CommunityServerSearchBarrel这样的项目,在这个项目里都是一些job和Item形式的类,这些类就是完成索引的关键类,针对不同应用都有不同的job,说到Job,在之前的系列 已经介绍过其工作方式就是在后台单独的进程里运行的组件,在CommunityServer.config的jobs节点里可以对这些Job进行配置,我们可以在这里配置索引的间隔时间。其原理即为,当到了设定的时间间隔,针对不同应用的SearchJob在后台独立的线程开始工作,先判断是否出现了未进行索引的文章如果有新的文章出现则读取出来进行索引(一般为几十条数据),索引完后进行记录,下次就不再重复进行了,直到文章被修改后再次进行。
        简单了解了这么多之后让我们来解决问题吧,CS2不支持中文究竟问题出在哪里呢,让我们打开数据库的cs_SearchBarrel表,我们可以看到这里就是保存分词结果的地方,不过可以看到正确的一个个英文单词却很难看到一个正确的中文词语,这就是为什么CS2对中文支持的这么不好了,CS2在搜索时是检索这个表的数据的,这个表数据有问题当然就没有办法检索到正确的信息了。好了,现在已经很明确了,我们就是需要修改CS2对中文的分词,分词是一个复杂的技术,我们可以利用现有的分词组件来帮助我们。
        博客园真是个好地方,在正需要分词组件的时候Eunge 兄就发布了免费版本(虽然没开放源码,不过能用就行,何必太叫真呢),真是及时雨啊,无论分词效果怎样,这总归是个解决方案,我试用了它的组件,感觉还不错,能胜任一般的应用了,于是就拿了过来,发布网址参见:http://lovinger2000.cnblogs.com/archive/2006/03/02/ChineseTokenizerDll.html 那到这个好东东之后就可以开始我们的改造了:

        那么我们应该如何改造呢,对,把系统分词的方法替换成中文组件的方法即可,那么怎样嵌入我们的中文分词组件呢,当然是顺潮流使用代理模式了,这样如果我们有更好的分词组件扩展起来是非常容易的。首先来看看CommunityServerSearchBarrel这个项目,这个项目就是处理搜索相关的地方。找到核心的调用让我们看看SearchJob.cs的Index方法,这个方面就是索引文章的方法了,在这里我们可以看到string[] wordsToIndex = SearchTerms.CleanSearchTerms(contentToIndex);这样的语句,不难理解,这个就是把文档的各部分转换为词的字符串数组了。SearchTerms.CleanSearchTerms这个方法在CommunityServerComponents项目的Search/SearchTerms.cs下,在这里就是要修改的核心了,我们可以看到其中都是对英文单词的处理,当然,我们只要替换掉对英文的处理为中文分词原则上就可以,主要的修改为:

CleanSearchTerms
public static string[] CleanSearchTerms(string searchTerms)
        
{

            
if (searchTerms == null)
                
return new string[0];

            
// Force the searchTerms to lower case
            
//
            searchTerms = searchTerms.ToLower();      
            
try
            
{

                
// Strip any markup characters
                
//
                searchTerms = Transforms.StripHtmlXmlTags(searchTerms).Replace(" """);

                
// Remove non-alpha/numeric characters
                
//Edit by lf 没必要去掉特殊字符,有些时候仍然需要搜索它
                
//searchTerms = Regex.Replace(searchTerms, "[^\\w]", " ", RegexOptions.Compiled | RegexOptions.Multiline);

                
// Replace special words with symbols
                /**/////Edit by lf 这几句要出错
                //searchTerms = Regex.Replace(searchTerms, "\\bor\\b", "||", RegexOptions.Compiled | RegexOptions.Multiline);
                
//searchTerms = Regex.Replace(searchTerms, "\\band\\b", "&&", RegexOptions.Compiled | RegexOptions.Multiline);

                
// Finally remove any extra spaces from the string
                
//空格也不需要替换,中文分词自动替换
                
//searchTerms = Regex.Replace(searchTerms, " {1,}", " ", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline);

            }

            
catch (System.Exception ex)
            
{
                
try
                
{
                    CSException csEx 
= new CSException(CSExceptionType.SearchUnknownError, "中文分词异常,这个是在替换特殊字符的时候的异常", ex);
                    csEx.Log();
                }

                
catch { }
            }

            
if (searchTerms.Trim() != string.Empty)
            
{
                
//Edit by lf设置为中文搜索
                try return ChineseTokenizeProvider.Instance().ChineseTokenize(searchTerms); }
                
catch (System.Exception ex)
                
{
                    
try
                    
{
                        CSException csEx 
= new CSException(CSExceptionType.SearchUnknownError, "中文分词异常", ex);
                        csEx.Log();
                    }

                    
catch { }
                }

            }


            
return searchTerms.Split(' ');
        }

        怎样引入我们的分词组件呢,让我们先建立一个抽象的Provider来作为代理的基础类,在这里建立名为ChineseTokenizeProvider,当然是在CommunityServerComponents项目里建立了。代码如下:

ChineseTokenizeProvider
using System;
using System.Collections.Generic;
using System.Text;
using CommunityServer.Components;
using CommunityServer.Configuration;

namespace CommunityServer.Components
{
    
/**//// <summary>
    
/// 中文分词的支持
    
/// </summary>
    
/// <remarks>Edit by lf</remarks>

    public abstract class ChineseTokenizeProvider
    
{

        
Instance#region Instance

        
private static ChineseTokenizeProvider _defaultInstance = null;

        
static ChineseTokenizeProvider()
        
{
            CreateDefaultCommonProvider();
        }



        
public static ChineseTokenizeProvider Instance() 
        
{
            
return _defaultInstance;
        }



        
private static void CreateDefaultCommonProvider()
        
{

            CSConfiguration config 
= CSConfiguration.GetConfig();
            
            Provider chineseProviders 
= (Provider)config.Providers["ChineseTokenizeProvider"];

            _defaultInstance 
= Activator.CreateInstance(Type.GetType(chineseProviders.Type)) as ChineseTokenizeProvider;
        }

        
        
#endregion


        
public abstract string[] ChineseTokenize(string input);

    }
//class
}

代码沿袭了CS中处理数据提供者的方式,只是这里使用的是分词提供方法,建立了基类后让我们建立一个扩展的代理层吧,这里我命名为Felix.NET.ChineseTokenWraper的项目其实就一个方法,也就是重写继承基类的抽象函数。如下:

Felix.NET.ChineseTokenWraper
using System;
using System.Collections.Generic;
using System.Text;
using CommunityServer.Components;
using Sj110.Com.Chinese;

namespace Felix.NET.ChineseTokenWraper
{
    
public class ChineseTokenizer : ChineseTokenizeProvider
    
{

        
ChineseTokenizeProvider 成员#region ChineseTokenizeProvider 成员

        
public override string[] ChineseTokenize(string input)
        
{
            List
<string> resultList = Tokenizer.Tokenize(input);
            
string[] mystring = new string[resultList.Count];
            resultList.CopyTo(mystring);
            
return mystring;
        }


        
#endregion

    }

}

当然完成了这些后还需要在配置文件加上这个Provider的配置,打开CommunityServer.config文件,在Providers配置节里添加如下代码:

<!-- Edit by lf加入对中文分词的支持 -->
        
< add 
                    
name  = "ChineseTokenizeProvider"
                    type 
= "Felix.NET.ChineseTokenWraper.ChineseTokenizer, Felix.NET.ChineseTokenWraper"
                
/>

         处理完这些后就可以说大功告成了一半了,只是还有很多语言方面的问题需要我们在调试的时候处理,具体的处理细节我就不细说了,自己调试调试就ok了,我也放上自己的CommunityServerSearchBarrel项目,主要修改也就是这个项目了。
        发现写这个文档怎么这么费力,好像还没说得很清楚,不过实在精力有限,还有很多事情等着我做,要完善这个功能还需要你细心调试,这里也就起到引个路子的作用,如果它能帮到你,我花这些功夫就没白费了。

[相关代码下载]

Update:这里有对搜索的更详细的解释http://googlechinablog.com/2006/05/blog-post_10.html

相关文章:

  • 激情黄健翔
  • NHibernate学习笔记(二):one-to-one关系映射
  • 从客户端检测到有潜在危险的Request.Form 值
  • [整理]asp.net 上传大文件解决方案
  • 暑期学习时间安排
  • 测试用例的有效维护
  • 模块化小议
  • Passport 的跨域认证解决方法
  • 用命令检查自己电脑是否中***
  • 对比.NET PetShop和Duwamish来探讨Ado.NET的数据库编程模式
  • 使用GWT开发AJAX应用程序
  • 批处理的高级运用
  • 配色方案--构图必学
  • CIO烦恼之一:系统孤岛林立,数据传输不畅
  • TC(HTB)+iptables作流量控制
  • 【140天】尚学堂高淇Java300集视频精华笔记(86-87)
  • axios请求、和返回数据拦截,统一请求报错提示_012
  • co模块的前端实现
  • js作用域和this的理解
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • MYSQL 的 IF 函数
  • 汉诺塔算法
  • 精彩代码 vue.js
  • 如何合理的规划jvm性能调优
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 入门级的git使用指北
  • 网络应用优化——时延与带宽
  • 问题之ssh中Host key verification failed的解决
  • ​HTTP与HTTPS:网络通信的安全卫士
  • # include “ “ 和 # include < >两者的区别
  • (C语言)共用体union的用法举例
  • (Java)【深基9.例1】选举学生会
  • (八)Flask之app.route装饰器函数的参数
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (编译到47%失败)to be deleted
  • (定时器/计数器)中断系统(详解与使用)
  • (附源码)spring boot车辆管理系统 毕业设计 031034
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (论文阅读31/100)Stacked hourglass networks for human pose estimation
  • (免费领源码)Java#Springboot#mysql农产品销售管理系统47627-计算机毕业设计项目选题推荐
  • (转)http协议
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .NET 8 中引入新的 IHostedLifecycleService 接口 实现定时任务
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .NET Reactor简单使用教程
  • .Net 访问电子邮箱-LumiSoft.Net,好用
  • .sh
  • @hook扩展分析
  • @synthesize和@dynamic分别有什么作用?
  • [ C++ ] STL---仿函数与priority_queue
  • [ Linux ] Linux信号概述 信号的产生
  • [android] 看博客学习hashCode()和equals()
  • [BZOJ5125]小Q的书架(决策单调性+分治DP+树状数组)
  • [C puzzle book] types