Elasticsearch 如何实现一个搜索引擎网站

研究Elasticsearch有一段时间了,之前仅仅限于日志的存储、查询,最近想用Elasticsearch来做一个搜索引擎网站。参考了网上的实现,能直接用的极少,于是只能动手自己来实现一个简单易上手的搜索引擎网站,便于大家一起学习。

这篇文章使用asp.net core 2.2做的,用户也可以尝试别的开发语言,.net core是官方直接支持的,使用非常简单,官方又有文档介绍。

最近,官方本已经发布了7.0的版本,docker版本也更新了最新的版本,那安装部署就太容易了,这篇文章将不介绍如何安装部署,也不介绍,如何将数据库的数据迁移到Elasticsearch,我以前的文章已经介绍过了,细心的网友应该很容易找到。

在开始之前,我们先看看我完成后的效果图,一个是包含了前端展示,一个是纯json数据的输出,细心的朋友不难发现,下面两种图片,搜索出来的数据是一一对应的,因为前端的展示,就是用到了后端返回的json数据



上图感觉有没有百度的感觉了,没错,我也参考了百度的前端展示。下图是对应的json数据,本文我仅仅提供了json数据输出的代码,包含了高亮显示的<em>标签,剩下的前端显示的代码用户自己实现,这个非常简单


下面我们来看看源码的实现,首先看看我的项目目录结构,它仅仅包含极少的几个文件,前端代码不在这个项目,这个项目负责返回搜索结果,不负责展示


仅仅只新建了2个cs文件,一个Mirroring.cs,一个SearchHelpers.cs,其余是API模版自动生成的,其中ValuesController.cs、Startup.cs做了一点点修改,我见给出源码
Mirroring.cs
[ElasticsearchType(Name = "mirroring")]
public class MirroringBase
{
public string Url { get; set; }
public string Title { get; set; }
public string Body { get; set; }
}

[ElasticsearchType(Name = "mirroring")]
public class Mirroring : MirroringBase
{
public string Id { get; set; }
public string Domain { get; set; }
public string Host { get; set; }
}

SearchHelpers.cs
这个文件的代码稍微多点,我先不着急全部贴出,先收起来,看个截图,会有大概的理解,然后在看具体每个方法,我想效果会更好,它包含一个public的方法,三个私有方法,一个负责高亮词分析,一个负责标题高亮,一个负责描述高亮


搜索的方法,只有这个方法是对外访问,它从title,body两个字段去搜索匹配的内容,使用了dis_max查询,大概意思是:假如我搜索“中国”,那么它会在标题,或者内容中选择都包含“中”又包含“国”的优先,如果标题包含“中”内容包含“国”的排后。更加详细精准的描述请看官方的介绍:https://www.elastic.co/guide/cn/elasticsearch/guide/current/_best_fields.html
public static IEnumerable<MirroringBase> Search(string query, int pageSize, int pageIndex)
{
var settings = new ConnectionSettings(new Uri("http://192.168.111.140:9200")).DefaultIndex("asy").RequestTimeout(TimeSpan.FromMinutes(1));
ElasticClient elasticClient = new ElasticClient(settings);

var request = new SearchRequest<MirroringBase>
{
Source = new SourceFilter
{
Includes = "title,url,body"
},
From = (pageIndex - 1) * pageSize,
Size = pageSize,
Query = new DisMaxQuery()
{
Boost = 1.1,
TieBreaker = 1.11,
Queries = new QueryContainer[] { new MatchQuery { Field = "title", Query = query, Boost = 2 }, new MatchQuery { Field = "body", Query = query, Boost = 1 } }
}
};

var result = elasticClient.Search<MirroringBase>(request);
IEnumerable<MirroringBase> list = result.Documents;

if(list.Any())
{
var tokens = Highlight(query);
foreach (var item in list)
{
item.Title = MakeTitle(item.Title, tokens);
item.Body = MakeDisc(item.Body, tokens);
}
}

return list;
}

高亮出需要高亮的词,比如搜索“中国”,那么“中”和“国”这些字不论是在标题还是在内容,将都被包上一个<em>标签,用户可以在前端对这个标签使用相应的样式即可
private static IEnumerable<string> Highlight(string query)
{
var settings = new ConnectionSettings(new Uri("http://192.168.111.140:9200")).DefaultIndex("asy").RequestTimeout(TimeSpan.FromMinutes(1));
ElasticClient elasticClient = new ElasticClient(settings);

var analyzeRequest = new AnalyzeRequest("asy")
{
Analyzer = "standard",
Text = new[] { query }
};

var analyzeResponse = elasticClient.Analyze(analyzeRequest);
return analyzeResponse.Tokens.Select(p => p.Token);
}

标题高亮
private static string MakeTitle(string title, IEnumerable<string> tokens)
{
foreach (var item in tokens)
{
if (Regex.IsMatch(title, item, RegexOptions.IgnoreCase))
{
title = Regex.Replace(title, $"({item})", "<em>$1</em>", RegexOptions.IgnoreCase);
}
}

return title;
}

描述高亮,这里使用HtmlAgilityPack来处理html代码,我们要从正文里面提取120个字来做简要显示,同时我们必须确保它包含用户搜索的关键字。
public static string MakeDisc(string body, IEnumerable<string> tokens)
{
var parser = new HtmlDocument();
parser.LoadHtml(body);
HtmlNode node = parser.DocumentNode;
body = node.InnerText;
List<string> list = new List<string>();
StringReader sr = new StringReader(body);
int total = 0;
bool isContains = false;
string brief = string.Empty;
while (true)
{
var line = sr.ReadLine();
if (line == null)
{
break;
}
else
{
if(string.IsNullOrWhiteSpace(line))
{
continue;
}
else
{
line = line.Trim();
line = line.Replace("&nbsp;", " ");
line = Regex.Replace(line, "[\\s]+", " ");
int count = line.Count();
foreach (var item in tokens)
{
if(Regex.IsMatch(line, item, RegexOptions.IgnoreCase))
{
isContains = true;
line = Regex.Replace(line, $"({item})", "<em>$1</em>", RegexOptions.IgnoreCase);
}
}

if(isContains)
{
brief += line;
total += count;

if (total >= 120)
{
break;
}
}
}
}
}

return brief;
}

ValuesController.cs
public class ValuesController : ControllerBase
{
[Route("query")]
public IEnumerable<MirroringBase> Query(string query, int pageSize = 5, int pageIndex = 1)
{
query = query ?? "docker";
query = query.Length > 50 ? query.Substring(0, 50) : query;
var result = SearchHelpers.Search(query, pageSize, pageIndex);
return result;
}
}

Startup.cs
API模版里面没有包含路由,这里添加上
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
。。。。。。省略代码
。。。。。。
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}

到此,有用的代码都已经全部给出,用户组装一下,很快就可以搭建一个搜索引擎网站。
Posted by 何敏
on 2019/04/18 07:04:10
Copyright ©2018 程序员网址导航 粤ICP备14091834号