文章转载自:https://mp.weixin.qq.com/s/uqchdrkhdFsof0ZFtECujg

我们经常在网站搜索输入时,会帮我们提醒自动完成的功能,比如:
图片 当我们在百度上搜索 Elasticsearch 时,它会自动弹出一些可以让我们进行搜索的条目。在很多的情况下,用户可能直接选择其中的一个进行输入,而不需要打入全部的文字。 在我之前的文章里,有关 autocomplete,也即自动补全的内容,我有几篇文章可以供大家来进行参考: Elasticsearch:Search-as-you-type 字段类型 Elasticsearch:使用 search_analyzer 及 edge ngram 来实现 Search-As-You-Type Elasticsearch:定制分词器(analyzer)及相关性 在今天的文章中,我将使用几种方法来展示自动完成是如何实现的。为了方便大家理解下面的代码,请在 github 上下载我的代码: git clone https://github.com/liu-xiao-guo/AutoComplete-Input-Elastic-Search-Python $ pwd
/Users/liuxg/python/AutoComplete-Input-Elastic-Search-Python
$ tree -L 2
.
├── Backend
│ └── api.py
├── Frontend
│ ├── app.py
│ └── templates
├── README.md
└── games.json 整个项目的代码非常简单。 Backend:处理前端发送来的请求,并转发至 Elasticsearch Frontend:处理网页发送的搜索请求 games.json:这是一个实验的数据 准备数据 我们首先把 games.json 这个 JSON 数据摄入到 Elasticsearch 中:
图片
图片 我们接下来选择下载的 games.json 文件:
图片 我们输入索引的名称为 games:
图片 在上面,我们需要修改 mappings 为: {
"properties": {
"critic_score": {
"type": "long"
},
"developer": {
"type": "text"
},
"genre": {
"type": "keyword"
},
"global_sales": {
"type": "double"
},
"id": {
"type": "keyword"
},
"image_url": {
"type": "keyword"
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"platform": {
"type": "keyword"
},
"publisher": {
"type": "keyword"
},
"user_score": {
"type": "long"
},
"year": {
"type": "long"
}
}
} 把 name 字段修改为一个 multi-field 的字段。点击上面的 Import。这样就完成了我们的 games 索引的摄入。
图片 运行 Backend 我们接下来运行 Backend 应用。这是一个基于 Flask 的 Python 应用。我们需要安装它所需要的 Python 包: pip3 install flask
pip3 install flask_restful
pip3 install Api
pip3 install reqparse
pip3 install Elasticsearch 我们的 api.py 的设计非常简单: api.py try:
from flask import app,Flask
from flask_restful import Resource, Api, reqparse
import elasticsearch
from elasticsearch import Elasticsearch
import datetime
import concurrent.futures
import requests
import json
except Exception as e:
print("Modules Missing {}".format(e)) app = Flask(__name__)
api = Api(app) #------------------------------------------------------------------------------------------------------------ INDEX_NAME = 'games'
es = Elasticsearch([{'host': 'localhost', 'port': 9200, 'http_auth':('elastic', 'password')}]) #------------------------------------------------------------------------------------------------------------ class Controller(Resource):
def __init__(self):
self.query = parser.parse_args().get("query", None)
self.baseQuery ={
"_source": [],
"size": 0,
"min_score": 0.5,
"query": {
"bool": {
"must": [
{
"match_phrase_prefix": {
"name": {
"query": "{}".format(self.query)
}
}
}
],
"filter": [],
"should": [],
"must_not": []
}
},
"aggs": {
"auto_complete": {
"terms": {
"field": "name.keyword",
"order": {
"_count": "desc"
},
"size": 25
}
}
}
} def get(self):
res = es.search(index=INDEX_NAME, size=0, body=self.baseQuery)
return res parser = reqparse.RequestParser()
parser.add_argument("query", type=str, required=True, help="query parameter is Required ") api.add_resource(Controller, '/autocomplete') if __name__ == '__main__':
app.run(debug=True, port=4000) 在上面,我的集群的访问用户名及密码为:elastic/password。在上面,它做了一个很简单的 match_phrase_prefix 搜索: GET games/_search
{
"size": 0,
"query": {
"bool": {
"must": [
{
"match_phrase_prefix": {
"name": "final fan"
}
}
],
"must_not": [],
"filter": [],
"should": []
}
},
"aggs": {
"auto_complete": {
"terms": {
"field": "name.keyword",
"size": 25
}
}
}
} 它的返回值为: {
"took" : 3,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 11,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"auto_complete" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "Crisis Core: Final Fantasy VII",
"doc_count" : 1
},
{
"key" : "Dissidia: Final Fantasy",
"doc_count" : 1
},
{
"key" : "Final Fantasy IX",
"doc_count" : 1
},
{
"key" : "Final Fantasy Tactics",
"doc_count" : 1
},
{
"key" : "Final Fantasy VII",
"doc_count" : 1
},
{
"key" : "Final Fantasy VIII",
"doc_count" : 1
},
{
"key" : "Final Fantasy X",
"doc_count" : 1
},
{
"key" : "Final Fantasy X-2",
"doc_count" : 1
},
{
"key" : "Final Fantasy XII",
"doc_count" : 1
},
{
"key" : "Final Fantasy XIII",
"doc_count" : 1
},
{
"key" : "Final Fantasy XIII-2",
"doc_count" : 1
}
]
}
}
} 从上面的结果中,我们可以看出来搜索的结果。 我们使用如下的命令来运行 Backend 的应用: $ pwd
/Users/liuxg/python/AutoComplete-Input-Elastic-Search-Python/Backend
$ python api.py
* Serving Flask app "api" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:4000/ (Press CTRL+C to quit)
* Restarting with fsevents reloader
* Debugger is active!
* Debugger PIN: 119-780-958 这样我们的 Backend 就运行起来了。我们在下面来运行 Frontend 的应用。 运行 Frontend 我们进入到 Frondend 的子目录中,并使用如下的命令来进行运行: $ pwd
/Users/liuxg/python/AutoComplete-Input-Elastic-Search-Python/Frontend
$ ls
app.py templates
$ python app.py
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with fsevents reloader
* Debugger is active!
* Debugger PIN: 119-780-958 如上所示,前端运行于地址 http://127.0.0.1:5000/。我们在浏览器中打开它:
图片
图片
图片 当我们在输入框中打入 final 时,我们可以看到候选的名单出现了。它可以让我们很方便地进行输入。我们甚至可以选择其中的一个进行输入:
图片 在客户端的设计中,它使用了 ajax 技术。当我们的输入发生改变时自动补全窗口的里的名单也会自动发生变化。 更进一步改进 在某种程度上,上面的设计还是不错的。它满足了许多情况下的需求。接下来,我们来使用 search-as-you-type 字段类型来完成我们的设计。我们可以参考我之前的文章 “ Elasticsearch:Search-as-you-type 字段类型”。我们首先来删除之前导入的 games 索引以及被创建的索引模式 games*。然后,我们在摄入数据时:
图片 点击当前页面的 Import 按钮,并完成 games 索引的创建:
图片 这样我们的 name 字段为 search_as_you_type 类型。由于一些原因,search_as_you_type 类型的数据目前还不能定义 multi-field,我们不能为这个字段添加 keyword 字段。 https://github.com/elastic/elasticsearch/issues/56326issue 里,有一个解决方案就是把 search_as_you_type 作为一个 multi-field,而把 keyword 作为一个主要的字段。在本文章中就不做展开了。留给开发者自己研究。
图片 请注意在下面的练习中,我使用的不是这个含有 multi-field 的定义。 我们需要修改我们的 Backend 才能使得它起作用。我们修改 api.py 如下: Backend/api.py try:
from flask import app,Flask
from flask_restful import Resource, Api, reqparse
import elasticsearch
from elasticsearch import Elasticsearch
import datetime
import concurrent.futures
import requests
import json
except Exception as e:
print("Modules Missing {}".format(e)) app = Flask(__name__)
api = Api(app) #------------------------------------------------------------------------------------------------------------ INDEX_NAME = 'games'
es = Elasticsearch([{'host': 'localhost', 'port': 9200, 'http_auth':('elastic', 'password')}]) #------------------------------------------------------------------------------------------------------------ class Controller(Resource):
def __init__(self):
self.query = parser.parse_args().get("query", None)
print(self.query)
self.baseQuery ={
# "_source": [],
"size": 10,
"min_score": 0.5,
"query": {
"bool": {
"must": [
{
"match_phrase_prefix": {
"name": {
"query": "{}".format(self.query)
}
}
}
],
"filter": [],
"should": [],
"must_not": []
}
}
} def get(self):
res = es.search(index=INDEX_NAME, size=25, body=self.baseQuery)
return res parser = reqparse.RequestParser()
parser.add_argument("query", type=str, required=True, help="query parameter is Required ") api.add_resource(Controller, '/autocomplete') if __name__ == '__main__':
app.run(debug=True, port=4000) 在上面,我们使用了 match_phrase_prefix 来完成我们的搜索。它相当于如下的搜索: GET games/_search
{
"query": {
"match_phrase_prefix": {
"name": "final fan"
}
}
} 由于我们没有使用 aggs 来返回结果,取而代之的是搜索的文档,那么我们需要修改相应的 home.html 文档: Frontend/templates/home.html 我们把 typeHandler 修改为: const typeHandler = function(e) {
$result.innerHTML = e.target.value;
console.log(e.target.value); $.ajax({
url: "/pipe",
type : 'POST',
cache: false,
data:{'data': e.target.value},
success: function(html)
{
console.log(html)
var data = html.hits.hits
var _ = [] $.each(data, (index, value)=>{
_.push(value._source.name)
});
console.log(_)
$( "#source" ).autocomplete({
source: _
});
}
});
} 这个是由于我们的响应格式的变化:
图片 我们重新运行 Backend 和 Frontend,那么我们可以看到和之前一模一样的结果:
图片 你是不是觉得把 name 字段的类型修改后也没有什么特别的,对吧? 但是我们可以尝试一下如下的搜索:
图片 在上面,我们输入 fi 及 fan,我们没有看到任何的结果。我们没有充分利用 search_as_you_type 给我们带来的好处。 我们重新修改 Backend 中的 api.py 为如下的代码: Backend/api.py try:
from flask import app,Flask
from flask_restful import Resource, Api, reqparse
import elasticsearch
from elasticsearch import Elasticsearch
import datetime
import concurrent.futures
import requests
import json
except Exception as e:
print("Modules Missing {}".format(e)) app = Flask(__name__)
api = Api(app) #------------------------------------------------------------------------------------------------------------ INDEX_NAME = 'games'
es = Elasticsearch([{'host': 'localhost', 'port': 9200, 'http_auth':('elastic', 'password')}]) #------------------------------------------------------------------------------------------------------------
class Controller(Resource):
def __init__(self):
self.query = parser.parse_args().get("query", None)
print(self.query)
self.baseQuery ={
"_source": [],
"size": 0,
"min_score": 0.5,
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "{}".format(self.query),
"type": "bool_prefix",
"operator": "or",
"fields": [
"name",
"name._2gram",
"name._3gram"
]
}
}
],
"filter": [],
"should": [],
"must_not": []
}
}
} def get(self):
res = es.search(index=INDEX_NAME, size=25, body=self.baseQuery)
return res parser = reqparse.RequestParser()
parser.add_argument("query", type=str, required=True, help="query parameter is Required ") api.add_resource(Controller, '/autocomplete') if __name__ == '__main__':
app.run(debug=True, port=4000) 在上面,我使用了 multi-match。上面的搜索相当于这样的命令: GET games/_search
{
"query": {
"multi_match": {
"query": "fi fan",
"type": "bool_prefix",
"operator": "or",
"fields": [
"name",
"name._2gram",
"name._3gram"
]
}
}
} 上面的命令,可以搜索出来前缀为 fi 及 fan 的文档。 我们也同时把 typeHandler 修改为: const typeHandler = function(e) {
$result.innerHTML = e.target.value;
console.log(e.target.value); $.ajax({
url: "/pipe",
type : 'POST',
cache: false,
data:{'data': e.target.value},
success: function(html)
{
console.log(html)
var data = html.hits.hits
var _ = [] console.log("nice")
$.each(data, (index, value)=>{
_.push(value._source.name)
});
console.log("list:")
console.log(_)
$( "#source" ).autocomplete({
source: _
}); $( "#result" ).text(_)
}
});
} 在上面,我们使用 result 来显示结果。我们重新运行 Backend 及 Frontend:
图片 尽管画面不是很美,但是,当我们输入诸如 "fi fan" 这样的词,我们可以看到我们想要的搜索的结果。

创建一个 autocomplete 输入系统 - 前端 + 后端的更多相关文章

  1. Spring Boot+Jpa(MYSQL)做一个登陆注册系统(前后端数据库一站式编程)

    Spring Boot最好的学习方法就是实战训练,今天我们用很短的时间启动我们第一个Spring Boot应用,并且连接我们的MySQL数据库. 我将假设读者为几乎零基础,在实战讲解中会渗透Sprin ...

  2. 动手实践记录(利用django创建一个博客系统)

    1.添加一个分类的标签,和主表的关系是 外键 class Category(models.Model): """ 分类 """ name = ...

  3. ASP.NET Core模块化前后端分离快速开发框架介绍之2、快速创建一个业务模块

    源码地址 GitHub:https://github.com/iamoldli/NetModular 演示地址 地址:https://nm.iamoldli.com 账户:admin 密码:admin ...

  4. 利用BitLocker和vhdx创建一个有加密的Win10系统

    如果电脑不支持TPM加密BitLocker,就无法对系统盘进行全盘加密. 可以采用一个变通的方法:创建一个vhdx,将这个虚拟磁盘进行BitLocker加密,然后在这个盘里安装操作系统,最后把vhdx ...

  5. UE4编程之C++创建一个FPS工程(一)创建模式&角色&处理输入

    转自:http://blog.csdn.net/u011707076/article/details/44180951 从今天开始,我们一起来学习一下,如何使用C++将一个不带有任何初学者内容的空模板 ...

  6. 基于gulp编写的一个简单实用的前端开发环境好了,安装完Gulp后,接下来是你大展身手的时候了,在你自己的电脑上面随便哪个地方建一个目录,打开命令行,然后进入创建好的目录里面,开始撸代码,关于生成的json文件请点击这里https://docs.npmjs.com/files/package.json,打开的速度看你的网速了注意:以下是为了演示 ,我建的一个目录结构,你自己可以根据项目需求自己建目

    自从Node.js出现以来,基于其的前端开发的工具框架也越来越多了,从Grunt到Gulp再到现在很火的WebPack,所有的这些新的东西的出现都极大的解放了我们在前端领域的开发,作为一个在前端领域里 ...

  7. springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑。为了区别不同的异常通常根据异常类型自定义异常类,这里我们创建一个自定义系统异常,如果controller、service、dao抛出此类异常说明是系统预期处理的异常信息。

    springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑. 1.1 异常处理思路 系统中异常包括两类:预期异常和运行时异常RuntimeEx ...

  8. 10.4 android输入系统_框架、编写一个万能模拟输入驱动程序、reader/dispatcher线程启动过程源码分析

    1. 输入系统框架 android输入系统官方文档 // 需FQhttp://source.android.com/devices/input/index.html <深入理解Android 卷 ...

  9. 无废话Android之listview入门,自定义的数据适配器、采用layoutInflater打气筒创建一个view对象、常用数据适配器ArrayAdapter、SimpleAdapter、使用ContentProvider(内容提供者)共享数据、短信的备份、插入一条记录到系统短信应用(3)

    1.listview入门,自定义的数据适配器 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/and ...

随机推荐

  1. Linux快捷方式创建模板

    1.创建快捷方式文件 sudo gedit /usr/share/applications/Navicat.desktop 模板: [Desktop Entry] Name=Navicat Exec= ...

  2. 近期碰到的一些面试题--WPF、C#、数据库

    最近想换工作的念头特别强烈,面了几家公司没有拿到满意的offer,心仪的公司面完锁HC,有点无奈,感觉今年有点卷,把碰到过的面试题总结下. WPF相关: 1.定义依赖属性需要注意哪些地方? (1)依赖 ...

  3. 如何用车辆历史违章查询API接口进行快速开发

    最近公司项目有一个车辆的历史违章查询显示的小功能,想着如果用现成的API就可以大大提高开发效率,所以在网上的API商店搜索了一番,发现了 APISpace,它里面的车辆历史违章查询API非常符合我的开 ...

  4. 企业运维实践-还不会部署高可用的kubernetes集群?使用kubeadm方式安装高可用k8s集群v1.23.7

    关注「WeiyiGeek」公众号 设为「特别关注」每天带你玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 文章目录: 0x00 前言简述 ...

  5. vscode无法调试python2.7版本

    概述 好久没有用python2.7版本了,最近有个老的脚本要优化,但是发现vscode无法对脚本调试,特此记录下解决方法. 本地安装有python2和python3,开发过程中,vscode可以随时调 ...

  6. mysql防SQL注入搜集

    SQL注入 例:脚本逻辑 $sql = "SELECT * FROM user WHERE userid = $_GET[userid] "; 案例1:SELECT * FROM ...

  7. 『叶问』#41,三节点的MGR集群,有两个节点宕机后还能正常工作吗

    『叶问』#41,三节点的MGR集群,有两个节点宕机后还能正常工作吗 每周学点MGR知识. 1. 三节点的MGR集群,有两个节点宕机后还能正常工作吗 要看具体是哪种情况. 如果两个节点是正常关闭的话,则 ...

  8. 无痕模式下 this.StorageManager.setItem) 本地存储丢失

    在无痕模式下,存的this.StorageManager.setItem("recharge", JSON.stringify(recharge))本地存储会丢失,所以我们改成使用 ...

  9. ASP.NET Core依赖注入系统学习教程:关于服务注册使用到的方法

    在.NET Core的依赖注入框架中,服务注册的信息将会被封装成ServiceDescriptor对象,而这些对象都会存储在IServiceCollection接口类型表示的集合中,另外,IServi ...

  10. SP6779 GSS7 - Can you answer these queries VII(线段树,树链剖分)

    水题,只是坑点多,\(tag\)为\(0\)时可能也要\(pushdown\),所以要\(bool\)标记是否需要.最后树链剖分询问时注意线段有向!!! #include <cstring> ...