山坡网的用户抱怨“为什么搜索‘二鬼子李富贵’找不到‘二鬼子汉奸李富贵’?我用百度搜都能找到。”

当时我就滴汗了,用户说的有道理,应该要能搜索到。

之前的方案很简单,用户输入的字串会在数据库里做正则表达式匹配,以便用“二鬼子”能搜到“二鬼子汉奸李富贵”。事实证明,我想当然了,即便是这么简单的一个书名搜索,也不能马虎。

那就来分析一下怎么做吧,即便不是专业做搜索的,思路上也可以先YY一下。按照本能,先把问题大而化小。

1. 先把搜索字符串进行中文分词

2. 用词组在数据库里做 or 包含匹配。

3. 搜索出来的结果按与搜索条件相关度排序。

看起来也不难(玩笑话,每一条水都很深),一条一条来解决。

1. 中文分词。

我找了一下,免费的不多,选择了盘古分词Go语言Port。从Github看到,代码是四个月以前的了,字典文件有些老。所以我在盘古网站上下载了最新的字典,测试了一下Go的代码,运行结果良好。

这个Port可能蛮久没有更新过,代码的结构不能直接go get,需要自己下载src里面的segment文件夹出来使用。

我把新的字典文件全都放到了revel app的conf文件夹里,如下图所示。

然后在Controller.Init方法里加上初始化代码。

revel.OnAppStart(func() {
   segHandler = segment.NewSegment()
   err := segHandler.Init(path.Join(revel.ConfPaths[0], "dicts"))
   if err != nil {
     glog.Fatalln("Failed to init segment handler", err)
   }

然后在需要的地方就可以开始用它分词了。

//对搜索的字符串进行分词
searchKey := "(" + key + "|"

segs := segHandler.DoSegment(key)
for cur := segs.Front(); cur != nil; cur = cur.Next() {
  word := cur.Value.(*dict.WordInfo)
  if word.Word != "的" {
    searchKey += word.Word + "|"
  }
}

searchKey = strings.TrimRight(searchKey, "|") + ")"

searchResults, pageSum, err := d.findBookBy(M{"$or": []M{
  M{"title": M{"$regex": searchKey}},
  M{"author": M{"$regex": key}},
  M{"category": M{"$regex": key}}}}, "-score", pageNum, numPerPage)
if err != nil {
  return nil, pageSum, err
}

思路是把搜索条件分成词组,再组合成正则表达式,比如“二鬼子李富贵”变成“(二|鬼子|李富贵)”,然后使用mongodb的正则查询。

这里我把“的”字去掉了,因为中文里面“的”字用的太多了,基本没有查询价值。

2. 搜索出来的结果按与搜索条件相关度排序。

搜索引擎里,这部分是技术含量最大的。我这边只是牛刀小试,所以方案简单很多,把搜索出来的书籍标题与搜索条件比对相似度。

正好,字符串比对相似度的库我之前Port过一个,叫做simhash(当时为什么port我都忘了,哈,工具箱里东西多还是有好处的!)。算法具体就不多说了,免得跑题。看用法吧。

needle := "Reading bytes into structs using reflection"
hayStack := "Golang - mapping an variable length array to a struct" likeness := GetLikenessValue(needle, hayStack)
fmt.Println("Likeness:", likeness)
就一个函数,输入两个字符串,输出一个从0到1的浮点数,代表相似百分比。
为了方便计算,我在SearchResult结构中加入了一个新的字段,OriginalQueryString,存储原始搜索条件,之后实现一下Sort接口。

type SearchResult struct {

  Id                bson.ObjectId "_id"

  Title             string

  OriginQueryString string //原始的搜索条件,用于排序

}

type SearchResults []SearchResult

func (srs SearchResults) Len() int {

  return len(srs)

}

func (srs SearchResults) Less(i, j int) bool {

  likenessI := simhash.GetLikenessValue(srs[i].Title, srs[i].OriginQueryString)

  likenessJ := simhash.GetLikenessValue(srs[j].Title, srs[j].OriginQueryString)

  return likenessI < likenessJ

}

func (srs SearchResults) Swap(i, j int) {

  srs[i], srs[j] = srs[j], srs[i]

}

就可以在搜索出来之后按照相关性排序了。

//为searchResult的OriginQueryString赋值,以便按照搜索相关性排序

for i, _ := range searchResults {

  searchResults[i].OriginQueryString = key

}

sort.Sort(sort.Reverse(SearchResults(searchResults)))

我的实现到这里就完成了。

但其实有一部分很重要的东西我取巧了。由于使用模糊搜索,结果集的大小是无法预料的,全部取的话随时可能把内存用完。分批的话怎么保证相关性排序的准确性呢?好问题,这里是非常关键又很难做的部分,我取巧的方式是把书籍按评分排序,然后取前20个出来,仅仅在这20本书中做相似度排序。这并不是完美的方案,仅仅只是够用。

后期如果有时间,可以用mongodb的游标做一个即省内存又靠谱的实现。

Go语言实战 - 我需要站内搜索的更多相关文章

  1. Compass实战 站内搜索

    今天早上打算对这两天学习的Lucene以及Compass总结一下,想来想去,还是写个小项目来验证最好了.于是就有了今天的这篇文章.难易程度适合对于Compass或者Lucene刚入门的童鞋,大牛看到后 ...

  2. 完整的站内搜索实战应用(Lucene.Net+盘古分词)

    首先自问自答几个问题,以让各位看官了解写此文的目的 什么是站内搜索?与一般搜索的区别? 多网站都有搜索功能,很多都是用SQL语句的Like实现的,但是Like无法做到模糊匹配(例如我搜索". ...

  3. 一分钟加入google站内搜索代码

    一分钟加入google站内搜索代码| 一分钟加入google站内搜索代码|只有7行最精简.网上有很多 google 站内搜索代码,但是出于某些目的,很多都加入了多余的代码,从seo的角度来讲,是很不优 ...

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

    Lucene.net是一个搜索引擎的框架,它自身并不能实现搜索.须要我们自己在当中实现索引的建立,索引的查找.全部这些都是依据它自身提供的API来实现.Lucene.net本身是基于java的,可是经 ...

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

    前言 这次开发的博客主要功能或特点:    第一:可以兼容各终端,特别是手机端.    第二:到时会用到大量html5,炫啊.    第三:导入博客园的精华文章,并做分类.(不要封我)    第四:做 ...

  6. 利用Solr服务建立的站内搜索雏形---solr1

    最近看完nutch后总感觉像好好捯饬下solr,上次看到老大给我展现了下站内搜索我便久久不能忘怀.总觉着之前搭建的nutch配上solr还是有点呆板,在nutch爬取的时候就建立索引到solr服务下, ...

  7. Lucene.net站内搜索—6、站内搜索第二版

    目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...

  8. Lucene.net站内搜索—5、搜索引擎第一版实现

    目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...

  9. Lucene.net站内搜索—4、搜索引擎第一版技术储备(简单介绍Log4Net、生产者消费者模式)

    目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...

随机推荐

  1. Java多线程

    一:进程与线程 概述:几乎任何的操作系统都支持运行多个任务,通常一个任务就是一个程序,而一个程序就是一个进程.当一个进程运行时,内部可能包括多个顺序执行流,每个顺序执行流就是一个线程.   进程:进程 ...

  2. Tomcat shutdown执行后无法退出进程问题排查及解决

    问题定位及排查 上周无意中调试程序在Linux上ps -ef|grep tomcat发现有许多tomcat的进程,当时因为没有影响系统运行就没当回事.而且我内心总觉得这可能是tomcat像nginx一 ...

  3. ASP.NET MVC开发:Web项目开发必备知识点

    最近加班加点完成一个Web项目,使用Asp.net MVC开发.很久以前接触的Asp.net开发还是Aspx形式,什么Razor引擎,什么MVC还是这次开发才明白,可以算是新手. 对新手而言,那进行A ...

  4. IdentityServer4 使用OpenID Connect添加用户身份验证

    使用IdentityServer4 实现OpenID Connect服务端,添加用户身份验证.客户端调用,实现授权. IdentityServer4 目前已更新至1.0 版,在之前的文章中有所介绍.I ...

  5. 太多选择——企业如何选择合适的BI工具?

    在没认清现状前,企业当然不能一言不合就上BI. BI不同于一般的企业管理软件,不能简单归类为类似用于提高管理的ERP和WMS,或用于提高企业效率的OA.BPM.BI的本质应该是通过展现数据,用于加强企 ...

  6. MySQL全文索引 FULLTEXT索引和like的区别

    1.概要 InnoDB引擎对FULLTEXT索引的支持是MySQL5.6新引入的特性,之前只有MyISAM引擎支持FULLTEXT索引.对于FULLTEXT索引的内容可以使用MATCH()-AGAIN ...

  7. 2016年中国微信小程序专题研究报告

    2016年12月29日,全球领先的移动互联网第三方数据挖掘和分析机构iiMedia Research(艾媒咨询)权威首发<2016年中国微信小程序专题研究报告>. 报告显示,82.6%手机 ...

  8. babel-loader-presets

    babel-loader的presets的设置有一定的顺序.es2015必须出现在stage-0前面,我记得这是因为es2015是ES6的标准,state-0等是对ES7一些提案的支持, state- ...

  9. ASP.NET Aries JSAPI 文档说明:AR.Form、AR.Combobox

    AR.Form 文档 1:对象或属性: 名称 类型 说明 data 属性 编辑页根据主键请求回来的数据 method 属性 用于获取数据的函数指向,默认值Get objName 属性 用于拦截form ...

  10. .NET深入实战系列—Linq to Sql进阶

    最近在写代码的过程中用到了Linq查询,在查找资料的过程中发现网上的资料千奇百怪,于是自己整理了一些关于Linq中容易让人困惑的地方. 本文全部代码基于:UserInfo与Class两个表,其中Cla ...