2021最新版 ElasticSearch 7.6.1 教程详解 爬虫jsoup+es模拟京东搜索(狂神说)
文章目录
- 一、ElasticSearch 简介
- 1.了解创始人 Doug Cutting
- 2.Lucene 简介
- 3.ElasticSearch 简介
- 4.ElasticSearch 和 Solr 的区别
- 5.了解ELK
- 二、软件安装
- 1.ElasticSearch
- 2.ElasticSearch Head
- 3.Kibana
- 三、ElasticSearch 使用详解
- 1.ES 核心概念
- 文档
- 索引
- 倒排索引
- ik分词器
- 2.命令模式的使用
- Rest风格说明
- cat命令
- 关于文档的基本操作(重点)
- 复杂操作查询
- 四、SpringBoot 集成 ElasticSearch
- 1.准备工作
- 2.API 使用
- 项目准备
- 项目初始化
- 源码分析
- API 索引操作
- API 文档操作
- 五、实现项目-京东搜索
- 1.项目搭建
- 2.爬取数据
- 3.业务编写
- 4.前端页面展示
- 5.高亮显示
相关了解:
- es搜索 6.x 7.x 差别很大,6.x的API(原生 API,RestFul高级)
- 应用领域很广,包括在大数据相关上面使用
一、ElasticSearch 简介
1.了解创始人 Doug Cutting
- 1998.9.4,谷歌成立,做搜索引擎起家
- 一位名叫 Doug Cutting 的工程师,也迷上搜索引擎,做了一个用于文本搜索的函数库,命名为 Lucene
- Lucene 是 Java 写的,目标是各种中小型应用加入全文检索功能,好用且开源,深受程序员喜爱
- 2001年底,Lucene 成为 Apache 软件基金会的一个子项目
- 2004年,Doug Cutting 在 Lucene 基础上开发了开源搜索引擎,Nutch
- Nutch 建立在 Lucene 基础上,增加了网络爬虫和网页相关功能,在业界影响力比Lucene 更大
- 随着时间推移,无论是 谷歌还是 Nutch 都面临搜索体积不断增加的问题,谷歌开始优化自己的算法,
- 2003年,谷歌公开介绍了自己的谷歌文件系统 GFS,这是谷歌为了存储海量搜索数据二设计的专用文件系统
- 2004年,Doug Cutting 基于谷歌的论文,实现了分布式文件存储系统,简称 NDFS
- 2004年,谷歌又发布论文,介绍自己的 MapReduce 编程模型,用于大规模数据集的并行分析运算
- 2005年,Doug Cutting 又基于谷歌的 MapReduce 模型,在Nutch上实现了该功能,大大提高速度
- 2006年,雅虎公司招了 Doug Cutting
- Doug Cutting 加盟雅虎之后,将 NDFS 和 MapReduce 进行改造,这就是大名鼎鼎的 Hadoop
- 2006年,谷歌又发论文了,BigTable,这是一种分布式数据存储系统,用来处理海量数据的非关系型数据库
- Doug Cutting 当然不会错过,在自己的Hadoop系统里面引入BigTable,命名 HBase
- 2008年1月,Hadoop 成功上位,正式成为 Apache 基金会的顶级项目
2.Lucene 简介
- Lucene 是一套信息检索工具包,是 jar 包,不是搜索引擎系统
- 包含:索引结构,读写索引的工具,排序,搜索规则,工具类
ElasticSearch 和 Lucene 的关系:
- ElasticSearch 是基于 Lucene 做了一些封装和增强,上手很简单
3.ElasticSearch 简介
ElasticSearch 简称 ES,是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储,检索数据;本身扩展性很好,可以扩展到上百台服务器上,处理PB级别的数据。ES也是用Java开发并使用Lucene作为其核心实现所有的索引和搜索的功能,但是他的目的是通过简单的 RestFul API来隐藏Lucene的复杂性,让全文搜索变得更简单
2016年1月,ElasticSearch 超过 Solr,成为排名第一的搜索引擎应用
谁在使用:
- 维基百科
- The Guardian 国外新闻网站
- Stack Over Flow
- GitHub
- 电商网站
- 日志数据分析,logstash采集日志,ES进行复杂数据分析,ELK技术,ElasticSearch+logstash-kibana
- 上平价格监控网站
- BI系统,商业智能,比如分析某些数据趋势
- 国内:站内搜索,电商,招聘,门户,IT系统搜索,数据分析(ES热门使用的场景)
4.ElasticSearch 和 Solr 的区别
ElasticSearch 用于 全文搜索、结构化搜索、分析
Solr 是 Apache 下的一个顶级开源项目,采用Java开发,基于 Lucene 的全文搜索服务器,Solr 提供了比 Lucene 更为丰富的查询语言,同时实现了可配置,可扩展,并对索引、搜索性能进行了优化
Solr 可以独立运行在 Jetty 等Servlet容器中,
Solr 是一个独立的企业级搜索应用服务器,实际上就是封装了 Lucene,对外提供类似于 Web-service的API接口,用户可以通过http请求,想搜索引擎服务器提交一定格式的文件,生成索引,也可以通过提出查找请求,得到结果
ElasticSearch vs Solr:
- ES 基本是开箱即用,解压就可以使用,非常简单,Solr 的使用略复杂
- Solr 利用 Zookeeper 进行分布式管理,ElasticSearch 自身带有分布式协调管理功能
- Solr 支持更多格式的数据,比如,JSON,XML,CSV ,ElasticSearch 仅支持 JSON 文件格式
- Solr 官网提供的功能更多,ElasticSearch 本身更专注核心功能,高级功能多由第三方插件提供,如图形化界面需要 kibana 友好支持
- Solr 查询快,但更新搜索引擎慢,适合用于电商扥个查询多的应用
- ES 建立搜索引擎模块,查询慢,即时性查询快,适用于facebook、新浪等实时搜索应用
- Solr 是传统搜索应用的有力解决方案,但ElasticSearch 更适用于新型的实时型搜索应用
- Solr比较成熟,有一个更大、更成熟的用户、开发贡献社区,而ElasticSearch 相对开发维护者较少,更新又太快,学习使用成本较高
- ElasticSearch 是未来的使用趋势
5.了解ELK
ELK 是 ElasticSearch、Logstash、Kibana 三大开源框架首字母大写的简称。市面上面也称作 Elastic Stack ,其中 ElasticSearch 是一个基于 Lucene、分布式、通过 ResrFul 方式进行交互的近实时搜索平台台框架。像类似百度、谷歌这种大数据全文搜索引擎的场景都可以使用ElasticSearch 作为底层支持框架,可见 ElasticSearch 提供的搜索功能很强大。
市面上我们简称ElasticSearch 为ES。Logstash 是ELK中央数据流引擎,用于从不同目标(文件/数据存储/MQ)收集的不同格式数据,经过过滤后支持输出到不同目的地(文件/MQ/redis/elastisearch/kafka等),Kibana 可以将 ElasticSearch 的收据通过友好的界面展示出来,提供实时分析的功能。
收集清洗数据 => 搜索、存储 => Kibana
市面上很多开发,只要提高ELK都能够说出他是一个日志分析架构技术栈的总称,但实际上ELK不仅仅适用于日志分析,它还可以支持其他任何数据分析和收集的场景,日志分析和收集只是更具有代表性,并非唯一性
大数据:选择
二、软件安装
最低要求 JDK 1.8,maven,web 项目需要有前端环境 node.js相关
官网(比较慢):https://www.elastic.co/cn/downloads/?elektra=home&storm=hero
ElasticSearch: https://mirrors.huaweicloud.com/elasticsearch/?C=N&O=D
logstash: https://mirrors.huaweicloud.com/logstash/?C=N&O=D
可视化界面:elasticsearch-head.https://github.com/mobz/elasticsearch-head
kibana: https://mirrors.huaweicloud.com/kibana/?C=N&O=D
ik分词器 https://github.com/medcl/elasticsearch-analysis-ik
实际生产环境使用 Linux 版本,这里我们学习使用,Windows版本即可
ELK 三件套解压即可使用,
1.ElasticSearch
解压压缩包,
熟悉目录
- bin 启动目录
- config 配置目录
log4j2 日志配置文件,jvm参数默认1g(内存小的需要自己配置),elasticsearch.yml 核心配置,其中默认端口9200
- lib 相关 jar 包
- modules 功能模块
- plugins 插件
- logs 日志
启动:
进入bin目录,双击启动 elasticsearch.bat
如果启动成功,命令行会显示,可以访问地址为 127.0.0.1:9200,
访问地址可以看到json数据,表示启动成功,访问成功
2.ElasticSearch Head
直接查看后台json数据并不友好,我们需要使用可视化工具
elasticSearch head:https://github.com/mobz/elasticsearch-head
查看目录结构可以得知,这是一个前端项目,需要提前安装node.js
如何启动这个前端项目?查看项目说明
解压缩文件,进入文件根目录,执行命令行 cnpm install
,安装相关依赖,npm 会根据package.json中配置的依赖进行安装
cnpm是淘宝的镜像,安装速度更快一些
依赖下载成功
启动项目 npm run start
启动成功,访问 http://localhost:9100
发现页面没有数据,因为存在跨域问题,9100端口访问9200端口
配置 elasticsearch.yml 核心配置文件,添加配置,注意yml语法空格缩进
http.cors.enabled: true
http.cors.allow-origin: "*"
表示开启可以跨域访问,允许所有都可以访问,保存,关闭文件
关闭 elasticsearch ,重新启动,
先访问测试 9200端口,可以看到数据,再访问 9100,可以看到访问成功
界面简介:
- 索引先简单理解为一个数据库,可以建立索引,索引中存放文档就相当于存放数据
未来的数据就存放在这里面
这个 head 我们就把他当做数据库连接工具即可,后续我们进行查询可以使用 kibana 工具,效率会更加提高很多
3.Kibana
Kibana 是一个针对 elasticsearch 的开源分析及可视化平台,用来搜索、查看交互存储在 elasticsearch 索引中的数据。使用 Kibana 可以通过各种图表进行高级数据分析及展示,Kibana 让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表盘(dashboard)实时显示 elasticsearch 查询动态,设置 Kibana 非常简单,无需编码或者额外的基础架构,几分钟内就可以完成 Kibana 安装并启动 elasticsearch 索引监测
官网:https://www.elastic.co/cn/downloads/?elektra=home&storm=hero
注意:Kibana 下载的版本一定要和 ElasticSearch 的版本一致
解压缩,可以看到这也是个完整的前端工程项目,
Kibana 已经包含了依赖,且有bin目录,进入bin目录,右键管理员运行 kibana.bat 文件,启动比较慢,
访问 5601 端口
访问页面测试可以使用 Postman、curl、elasticsearch-head、这里我们就使用 kibana
以后的搜索测试 请求语句 可以写在这里,通过kibana 来向搜索引擎 elasticsearch 进行查询请求
kibana 默认英文,如何汉化?
修改插件中的国际化设置,进入目录 kibana-7.6.1-windows-x86_64\x-pack\plugins\translations\translations,可以看到有中文的国家化文件,里面包含了中文翻译对照
我们修改 kibana 核心配置文件,在 kibana-7.6.1-windows-x86_64\config 目录下,kibana.yml 添加国际化配置,默认是英文
i18n.locale: "zh-CN"
重启 kibana,访问测试
三、ElasticSearch 使用详解
1.ES 核心概念
- 索引
- 字段类型(mapping)
- 文档(documents)
集群,节点,索引,类型,文档,分片,映射是什么?
elasticsearch是面向文档,关系型数据库和elasticsearch客观的对比!一切都是json
Relational DB | Elasticsearch |
---|---|
数据库(database) | 索引(indices) |
表(tables) | types |
行(rows) | documents |
字段(columns) | fields |
物理设计:
elasticsearch 在后台把每个索引划分成多个分片。每个分片可以在集群中的不同服务器间迁移
初始一个人就是一个集群,默认集群名字 elasticsearch
逻辑设计:
一个索引类型中,包含多个文档,当我们索引一篇文档时,可以通过这样的一个顺序找到它:索引 => 类型 => 文档id,通过这个组合我们就能索引到某个具体的文档。注意:ID不必是整数,实际上它是一个字符串。
文档
文档就是我们的一条条的数据
之前说elasticsearch是面向文档的,那么就意味着索弓和搜索数据的最小单位是文档,,elasticsearch中,文档有几个重要属性:
- 自我包含,一篇文档同时包含字段和对应的值,也就是同时包含 key:value !
- 可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这么来的! {就是一 个json对象! 可以用fastjson进行自动转换!}
- 灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,我们可以忽略某字段,或者动态的添加一个新的字段。
尽管我们可以随意的新增或者忽略某个字段,但每个字段的类型非常重要,比如一个年龄字段类型,可以是字符串也可以是整形。因为elasticsearch会保存字段和类型之间的映射及其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在elasticsearch中,类型有时候也称为映射类型。
索引
索引就是数据库!
索引是映射类型的容器,elasticsearch 中的索引是一个非常大的文档集合。索|存储了映射类型的字段和其他设置。然后它们被存储到了各个分片上了。我们来研究下分片是如何工作的。
物理设计:节点和分片如何工作
一个集群至少有一 个节点,而一个节点就是一个elasricsearch进程,节点可以有多个索引默认的,如果你创建索引,那么索引将会有5个分片(primary shard,又称主分片)构成,每一个主分片会有一个副本(replica shard,又称复制分片)
上图是一个有3个节点的集群,可以看到主分片P和对应的复制分片R都不会在同一个节点内,这样有利于某个节点挂掉了,数据也不至于丢失。实际上,一个分片是一个Lucene索引,一个包含倒排索引的文件目录,倒排索引的结构使得elasticsearch在不扫描全部文档的情况下,就能告诉你哪些文档包含特定的关键字。不过,等等,倒排索引是什么鬼?
集群、节点、索引、分片 之间的关系:
- 一个集群包含一个或多个节点,每个节点独立包含完整数据,节点也分主从节点
- 一个节点就是一个 Elasticsearch 服务(实例)
- 节点内创建索引,索引可以分成多个主分片,每个分片包含部分数据
- 每个主分片还会有对应的从分片(副本),与主分片数据相同,个数自定,主分片不会与他的副本在一个节点内,这是为了增加高可用
- Elasticsearch 中的分片其实就是 Lucene 索引
- 一个分片就是一个 Lucene 实例
详细关系参照:Elasticsearch 集群、节点、索引、分片、副本概念
倒排索引
elasticsearch使用的是一种称为倒排索引的结构,采用Lucene倒排索作为底层。这种结构适用于快速的全文搜索,一个索引由文
档中所有不重复的列表构成,对于每一个词,都有一个包含它的文档列表。例如,现在有两个文档,每个文档包含如下内容:
Study every day, good good up to forever # 文档1包含的内容
To forever, study every day,good good up # 文档2包含的内容
为为创建倒排索引,我们首先要将每个文档拆分成独立的词(或称为词条或者tokens),然后创建一个包含所有不重复的词条的排序列表,然后列出每个词条出现在哪个文档:
term | doc_1 | doc_2 |
---|---|---|
Study | √ | x |
To | x | √ |
every | √ | √ |
forever | √ | √ |
day | √ | √ |
study | x | √ |
good | √ | √ |
every | √ | √ |
to | √ | x |
up | √ | √ |
比如,我们搜索 to forever,只需要查看包含每个词条的文档
term | doc_1 | doc_2 |
---|---|---|
to | √ | x |
forever | √ | √ |
total | 2 | 1 |
两个文档都匹配,但是第一个文档比第二个匹配程度更高。如果没有别的条件,现在,这两个包含关键字的文档都将返回。
再来看一个示例,比如我们通过博客标签来搜索博客文章。那么倒排索引列表就是这样的一个结构:
博客文章(原始数据) | 博客文章(原始数据) | 索引列表(倒排索引) | 索引列表(倒排索引) |
---|---|---|---|
博客文章ID | 标签 | 标签 | 博客文章ID |
1 | python | python | 1,2,3 |
2 | python | linux | 3,4 |
3 | linux,python | ||
4 | linux |
如果要搜索含有python标签的文章,那相对于查找所有原始数据而言,查找倒排索引后的数据将会快的多。只需要查看标签这一栏,然后获取相关的文章ID即可。完全过滤掉无关的所有数据,提高效率!
当然,前提是要先生成索引列表
elasticsearch的索引和Lucene的索引对比
在elasticsearch中,索引(库)这个词被频繁使用,这就是术语的使用。在elasticsearch中,索引被分为多个分片,每份分片是一个Lucene的索引。所以一个elasticsearch索引是由多 个Lucene索引组成的。别问为什么,谁让elasticsearch使用Lucene作为底层呢!如无特指,说起索引都是指elasticsearch的索引。
接下来的一切操作都在kibana中Dev Tools下的Console里完成。基础操作!
ik分词器
什么是IK分词器 ?
分词:
- 即把一段中文或者别的划分成一个个的关键字,我们在搜索时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每个字看成一个词。
- 比如“我爱狂神”会被分为"我",“爱”,“狂”,“神” ,这显然是不符合要求的,所以我们需要安装中文分词器ik来解决这个问题。
如果要使用中文,建议使用ik分词器!
IK提供了两个分词算法:
- ik_ smart和ik_ max_ word
- ik_ smart为最少切分,ik_ max_ _word为最细粒度划分!
下载:https://github.com/medcl/elasticsearch-analysis-ik
版本不全到这里找:https://elasticsearch.cn/download/
下载完毕后,直接放到目录中 elasticsearch\elasticsearch-7.6.1\plugins 即可,注意将分词器的目录名改为 ik
重启 elasticsearch,在启动过程中可以看到,elasticsearch 加载了 ik 分词器
重启失败原因:
elasticsearch 与 ik 分词器版本一定要完全对应上,只对应大版本号不够
如何查看 ik版本号,查看目录下 plugin-descriptor.properties 文件,
解压后,压缩包删除,分词器目录取名ik
kabana 和 head 最好也关掉,一起重启
如何确认 elasticsearch 启动成功加载插件?启动 elasticsearch bin 目录中提供的工具 elasticsearch-plugin.bat
进入 elasticsearch bin 目录启动命令行 elasticsearch-plugin list
表示加载成功
测试使用
进入 kibana 界面 Dev Tools 菜单,发起请求,选择分词算法
【ik_smart】(最少切分)测试:
GET _analyze
{"analyzer": "ik_smart","text": "我是社会主义接班人"
}
发起请求,右侧可以看到分词结果
【ik_max_word】(最细粒度划分)测试
GET _analyze
{"analyzer": "ik_max_word","text": "我是社会主义接班人"
}
可以看到二者的拆分区别
思考:
- ik 分词器为什么知道怎么拆分呢?因为它需要参照分词器字典
- 如果拆分的词不符合我们的实际需求怎么办?需要自己加到分词器字典里
ik分词器如何增加配置
进入分词器目录 elasticsearch\elasticsearch-7.6.1\plugins\ik\config ,这个目录包含了各种字典,
我们可以创建的自己的字典,然后配置到 IKAnalyzer.cfg.xml 文件中
新建字典文件,比如,swy.dic,编辑内容,比如添加
泥萌好
swy
这样,泥萌好,swy,就会默认成为一个词,被ik分词器识别
在配置文件 IKAnalyzer.cfg.xml 中,添加
<entry key="ext_dict">swy.dic</entry>
重启es,查看启动日志可以看到,我们的新的分词字典已经被加载
开始测试,
可以看到,我们自定义的词已经被看做一个完整的词,以后工作,我们即可配置自己需要的词
2.命令模式的使用
Rest风格说明
一种软件架构风格,而不是标准。只是提供了一组设计原则和约束条件,它主要用于客户端可服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制
method | url地址 | 描述 |
---|---|---|
PUT | localhost:9200/索引名称/类型名称/文档id | 创建文档(指定文档id) |
POST | localhost:9200/索引名称/类型名称 | 创建文档(随机文档id) |
POST | localhost:9200/索引名称/类型名称/文档id/_update | 修改文档 |
DELETE | localhost:9200/索引名称/类型名称/文档id | 删除文档 |
GET | localhost:9200/索引名称/类型名称/文档id | 通过文档id查询文档 |
POST | localhost:9200/索引名称/类型名称/_search | 查询所有的数据 |
基础测试
- 创建一个索引,并添加数据
PUT /索引名/类型名/文档id
注意,PUT 后面有空格
查看 elasticsearch 后台数据
本质上是我们添加了一条数据
完成了自动增加索引,数据成功添加
需要注意的是,只要可以发送规定格式的请求就可完成操作,不一定使用kibana,postman,也可以,但是用kibana很专业,而且输入时有提示,降低出错率
如何给数据中的数据指定类型?
- 字符串类型:text、keyword
- 数值类型:long、integer、short、byte、double、float、half float、scaled float
- 日期类型:date
- 布尔类型:boolean
- 二进制类型:binary
- 等等
官方有详细讲解
指定字段类型,再次添加数据,这里我们先不添加数据,只是制定规则
查看后台控制,可以看到,新增加了一个索引test2,但是没有数据
- 获取索引规则信息,GET请求
- 查看默认信息
创建索引的时候,如果不指定字段属性类型,则会使用默认类型,当然也可以显示的声明
查看后台数据
发送请求查看数据,可以看到,age默认给了long类型,birthday默认给了date类型,name默认给了text类
- 修改操作,第一种,还是直接使用PUT即可,然后覆盖
但是有个缺点,对于修改来说,如果旧的数据有一个属性,新修改的指令里没有添加这个属性,那么修改后这个属性就会消失
由于之前我们己经创建这个数据,所以这次创建直接将原来的覆盖,而且version还显示了版本号增加了
第二种,使用正统的修改指令 POST
删除索引,使用 DELETE
cat命令
获取健康值
其实 elasticsearch-head后台管理页面就是不断地向服务器发送请求,获得的数据
查看所有信息
根据提示可以在 _cat后面添加很多指令进行查看
关于文档的基本操作(重点)
创建文档
- 添加数据
PUT /swy/user/1
{"name": "swy01","age": 25,"desc": "every will be ok","tags": ["高","富","帅"]
}
得到结果
{"_index" : "swy","_type" : "user","_id" : "1","_version" : 1,"result" : "created","_shards" : {"total" : 2,"successful" : 1,"failed" : 0},"_seq_no" : 0,"_primary_term" : 5
}
查看后台数据
同样的方法,在添加多条数据
- 获取数据 GET
- 更新数据
不推荐使用put,推荐使用post,只需该对应的字段即可,不必添加所有的字段
POST swy/user/1/_update
{"doc": {"name": "Mike"}
}
- 搜素操作(简单操作)
通过id查询
GET swy/user/_search?q=name:Mike
复杂操作查询
复杂操作查询:select(排序,分页,高亮,模糊查询,精准查询)
查询具体的参数使用 json
- 条件查询
GET swy/user/_search
{"query": {"match": {"name": "jack"}}
}
- 结果过滤,查询并输出的结果不需要那么多字段
GET swy/user/_search
{"query": {"match": {"name": "jack"}},"_source": ["name","desc"]
}
注意:之后使用Java操作es,所有的方法和对象就是这里的key
- 排序操作,sort 表示通过哪个字段排序
这里用的升序,
GET swy/user/_search
{"query": {"match": {"name": "swy"}},"sort": [{"age": {"order": "desc"}}]
}
- 分页查询,需要包含从哪里开始,以及页面大小
数据下标从0开始,与其他数据库是一样的
GET swy/user/_search
{"query": {"match": {"name": "swy"}},"sort": [{"age": {"order": "desc"}}],"from": 0,"size": 1
}
- 多条件查询,使用bool,
must(相当于and),should(相当于or),must_not(不等于xxx条件)
GET swy/user/_search
{"query": {"bool": {"must": [{"match": {"name": "swy"}},{"match": {"age": 250}}]}}
}
- 过滤器 filter,
这里附加了范围
GET swy/user/_search
{"query": {"bool": {"must": [{"match": {"name": "swy"}}],"filter": {"range": {"age": {"gte": 100,"lte": 300}}}}}
}
- 匹配多个条件,tags,比如根据性格标签,可以计算出权重
多个条件使用空格分开即可
GET swy/user/_search
{"query": {"match": {"tags": "白 富 美"}}
}
我们可以看到,匹配度越高,分值越高
- 精确查询
term查询是直接通过倒排索引指定的词条进程精确查找的
关于分词
- term,直接查询精确的
- match,会使用分词器解析,先分析文档,再通过分析的文档进行查询
关于两个类型
- text类型可以被分词解析
- keyword不会被分词解析
举个例子,创建索引
PUT testdb
{"mappings": {"properties": {"name": {"type": "text"},"desc": {"type": "keyword"}}}
}
添加数据
PUT testdb/_doc/1
{"name": "狂神说Java name","desc": "狂神说Java desc"
}
改变id,以及属性参数,添加多条数据
当成 keyword 方式分词,是看做一个整体
standard 普通方式,普通分词处理,当然我们也可以使用中文分词
开始查询
当我们搜索 name属性(text类型)时,由于这种数据默认可以被分词器解析,所以,即使我们搜索部分单词,也可以找到完整相关数据
GET testdb/_search
{"query": {"term": {"name": "狂"}}
}
当我们搜素 desc 属性(keyword类型)时,整个属性值被看做一个整体,搜索单个词无法找到,只有搜索整体才有结果
GET testdb/_search
{"query": {"term": {"desc": "狂"}}
}
GET testdb/_search
{"query": {"term": {"desc": "狂神说Java desc"}}
}
结论:keyword 属性的字段不会分词器解析,设置属性的时候需要注意
- 多个值匹配的精确查询
先添加一些数据,
PUT testdb/_doc/3
{"t1": "22","t2": "2020-04-06"
}
PUT testdb/_doc/4
{"t1": "33","t2": "2020-04-07"
}
开始查询
GET testdb/_search
{"query": {"bool": {"should": [{"term": {"t1": "22"}},{"term": {"t1": "33"}}]}}
}
- 高亮查询
搜索的结果中包含搜索词条的部分,用高亮显示
查询的时候,添加高亮属性,并设置需要高亮的字段
搜索相关的结果,会被自动加上标签
GET swy/user/_search
{"query": {"match": {"name": "swy"}},"highlight": {"fields": {"name": {}}}
}
自定义高亮属性,添加所需要的标签的前缀,后缀
小结:
- 匹配
- 按条件匹配
- 精确匹配
- 区间范围匹配
- 匹配字段过滤
- 多条件匹配
- 高亮查询
这些 MySQL 也可以做,只不过效率比较低
尤其在海量数据的情况下,es的性能优势非常明显
四、SpringBoot 集成 ElasticSearch
1.准备工作
官方文档:https://www.elastic.co/guide/index.html
找到客户端文档,进入Java Rest Client ,选择高级客户端,
客户端相关:
原生 maven 依赖
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.6.2</version>
</dependency>
初始化 Initialization
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("localhost", 9200, "http"),new HttpHost("localhost", 9201, "http")));
用完关闭
client.close();
2.API 使用
项目准备
创建maven项目作为父项目,删除多余文件,创建子模块springboot项目,添加项目依赖
检查springboot-starter下载的依赖,es依赖版本是否与我们安装的版本一致,如果不一致,将连接不上,一定要确保一致
添加与本地版本相匹配的依赖
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.6.1</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.70</version>
</dependency>
项目初始化
添加配置类,注入bean
@Configuration
public class ElasticSearchClientConfig {@Beanpublic RestHighLevelClient restHighLevelClient() {RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("127.0.0.1", 9200, "http")));return client;}
}
源码分析
查看依赖 spring-boot-autoconfigure,elasticsearch
源码中提供的rest对象
API 索引操作
使用前确保,elasticsearch 已经启动
编写测试类,有了之前通过 kibana 的使用,我们现在开始转为使用Java API进行操作
@SpringBootTest
class EsApiApplicationTests {@Autowired@Qualifier("restHighLevelClient")private RestHighLevelClient client;// 测试索引的创建@Testvoid testCreateIndex() throws IOException {// 1.创建索引的请求CreateIndexRequest request = new CreateIndexRequest("swy_index");// 2.客户端执行请求,请求后获得响应CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);System.out.println(response);}
}
运行测试,创建成功
查看后台数据,索引已经创建,默认为空
测试获取索引,判断是否存在
// 测试索引是否存在@Testvoid testExistIndex() throws IOException {// 1.创建索引的请求GetIndexRequest request = new GetIndexRequest("swy_index");// 2.客户端执行请求,请求后获得响应boolean exist = client.indices().exists(request, RequestOptions.DEFAULT);System.out.println("测试索引是否存在-----"+exist);}
测试删除索引
// 删除索引@Testvoid testDeleteIndex() throws IOException {DeleteIndexRequest request = new DeleteIndexRequest("swy_index");AcknowledgedResponse delete = client.indices().delete(request,RequestOptions.DEFAULT);System.out.println("删除索引--------"+delete.isAcknowledged());}
API 文档操作
创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class User {private String name;private int age;
}
编写测试类
添加文档
// 测试添加文档@Testvoid testAddDocument() throws IOException {User user = new User("swy",18);IndexRequest request = new IndexRequest("swy_index");request.id("1");// 设置超时时间request.timeout("1s");// 将数据放到json字符串request.source(JSON.toJSONString(user), XContentType.JSON);// 客户端发送请求,获取响应结果IndexResponse response = client.index(request,RequestOptions.DEFAULT);System.out.println("添加文档-------"+response.toString());System.out.println("返回状态-------"+response.status());}
获取文档,判断是否存在
// 测试文档是否存在@Testvoid testExistDocument() throws IOException {// 测试文档的 没有indexGetRequest request= new GetRequest("swy_index","1");// 没有indices()了boolean exist = client.exists(request, RequestOptions.DEFAULT);System.out.println("测试文档是否存在-----"+exist);}
获取文档信息
// 测试获取文档@Testvoid testGetDocument() throws IOException {GetRequest request= new GetRequest("swy_index","1");GetResponse response = client.get(request, RequestOptions.DEFAULT);System.out.println("测试获取文档-----"+response.getSourceAsString());System.out.println("测试获取文档-----"+response);}
返回的内容和命令得到的是一样的
更新文档信息
// 测试修改文档@Testvoid testUpdateDocument() throws IOException {User user = new User("托克马克", 200);// 修改是id为1的UpdateRequest request= new UpdateRequest("swy_index","1");request.timeout("1s");request.doc(JSON.toJSONString(user),XContentType.JSON);UpdateResponse response = client.update(request, RequestOptions.DEFAULT);System.out.println("测试修改文档-----"+response);System.out.println("测试修改文档-----"+response.status());}
删除文档
// 测试删除文档@Testvoid testDeleteDocument() throws IOException {DeleteRequest request= new DeleteRequest("swy_index","1");request.timeout("1s");DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);System.out.println("测试删除文档------"+response.status());}
真实的项目一般都会批量插入数据
//测试批量添加文档@Testvoid testBulkAddDocument() throws IOException {ArrayList<User> userlist = new ArrayList<User>();userlist.add(new User("swy1",5));userlist.add(new User("swy2",6));userlist.add(new User("swy3",40));userlist.add(new User("swy4",25));userlist.add(new User("swy5",15));userlist.add(new User("swy6",35));// 批量操作的RequestBulkRequest request = new BulkRequest();request.timeout("1s");// 批量处理请求for (int i = 0; i < userlist.size(); i++) {request.add(new IndexRequest("swy_index").id(""+(i+1)).source(JSON.toJSONString(userlist.get(i)),XContentType.JSON));}BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);// response.hasFailures()是否是失败的System.out.println("测试批量添加文档-----"+response.hasFailures());}
批量更新、批量删除 都同理
查询操作
- SearchRequest 搜索请求
- SearchSourceBuilder 请求条件构造,
- highlighter 设置高亮
- TermQueryBuilder 精确查询
- MatchAllQueryBuilder 匹配全部查询
- xxxQueryBuilder 对应了我们刚才看到的所有命令
// 测试查询文档@Testvoid testSearchDocument() throws IOException {SearchRequest request = new SearchRequest("swy_index");// 构建搜索条件SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();// 设置了高亮sourceBuilder.highlighter();// term name为swy1的TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "swy1");// 匹配所有// MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();sourceBuilder.query(termQueryBuilder);sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));request.source(sourceBuilder);SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 查询结果都封装在hit中System.out.println("测试查询文档-----" + JSON.toJSONString(response.getHits()));System.out.println("=====================");for (SearchHit documentFields : response.getHits().getHits()) {System.out.println("测试查询文档--遍历参数--" + documentFields.getSourceAsMap());}}
五、实现项目-京东搜索
1.项目搭建
新建模块,springboot,添加依赖
修改elasticsearch版本
<properties><java.version>1.8</java.version><elasticsearch.version>7.6.1</elasticsearch.version>
</properties>
添加依赖
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.70</version>
</dependency>
springboot核心配置
server.port=9090
# 关闭thymeleaf缓存
spring.thymeleaf.cache=false
导入页面素材
链接:https://pan.baidu.com/s/1M5uWdYsCZyzIAOcgcRkA_A
提取码:qk8p
下载链接:https://download.csdn.net/download/weixin_47257749/18351882
把 template、static文件复制到resources目录下
创建 controller
@Controller
public class IndexController {@RequestMapping({"/","/index"})public String index() {return "index";}
}
启动项目,访问测试:http://localhost:9090/
2.爬取数据
ElasticSearch 搜索的数据从哪里获取?
- 数据库、消息队列、爬虫
爬取数据:获取请求返回的页面信息,筛选出我们想要的数据,jsoup 包可以实现,
jsoup可以解析网页,不可以解析媒体
导入依赖
<dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.10.2</version>
</dependency>
创建实体类,用于封装商品信息
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Content {private String title;private String img;private String price;// 可以自己添加属性完善功能
}
写一个工具类,解析网页
@Component
public class HtmlParseUtil {// 测试一下public static void main(String[] args) throws IOException {new HtmlParseUtil().parseJD("vue").forEach(System.out::println);}public static List<Content> parseJD(String keyword) throws IOException {/// 使用前需要联网// 请求urlString url = "http://search.jd.com/search?keyword=" + keyword;// 1.解析网页(jsoup 解析返回的对象是浏览器Document对象)Document document = Jsoup.parse(new URL(url), 30000);// 使用document可以使用在js对document的所有操作// 2.获取元素(通过id),id自己查网页Element j_goodsList = document.getElementById("J_goodsList");// 3.获取J_goodsList ul 每一个Elements lis = j_goodsList.getElementsByTag("li");// System.out.println(lis);// 4.获取li下的 img、price、name// list存储所有li下的内容List<Content> contents = new ArrayList<Content>();for (Element li : lis) {// 由于网站图片使用懒加载,将src属性替换为data-lazy-imgString img = li.getElementsByTag("img").eq(0).attr("data-lazy-img");// 获取li下 第一张图片String name = li.getElementsByClass("p-name").eq(0).text();String price = li.getElementsByClass("p-price").eq(0).text();// 封装为对象Content content = new Content(name,img,price);// 添加到list中contents.add(content);}// System.out.println(contents);// 5.返回 listreturn contents;}
}
抓取的数据交给 elasticsearch 即可用于 es 实现搜索
PS:好好珍惜,爬了一次,再就爬不进去了,getElementById 拿到的只剩 null 了
3.业务编写
添加 elasticsearch 配置类
@Configuration
public class ElasticSearchClientConfig {@Beanpublic RestHighLevelClient restHighLevelClient() {RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("127.0.0.1", 9200, "http")));return client;}
}
在 elasticsearch 后台管理页面创建一个空索引 jd_goods
创建业务service
@Service
public class ContentService {@Autowiredprivate RestHighLevelClient restHighLevelClient;// 1.解析数据放入 es 中public Boolean parseContent(String keywords) throws Exception {List<Content> contents = new HtmlParseUtil().parseJD(keywords);// 把查询的数据放入es中BulkRequest bulkRequest = new BulkRequest();bulkRequest.timeout("2s");for (int i = 0;i < contents.size();i++) {bulkRequest.add(new IndexRequest("jd_goods").source(JSON.toJSONString(contents.get(i)), XContentType.JSON));}BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);return !bulk.hasFailures();}// 2.到es中查询获取这些数据public List<Map<String, Object>> searchPage(String keyword, int pageNo, int pageSize) throws IOException {if (pageNo <= 1) {pageNo = 1;}// 条件搜索SearchRequest searchRequest = new SearchRequest("jd_godds");SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();// 分页sourceBuilder.from(pageNo);sourceBuilder.size(pageSize);// 精准匹配TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);sourceBuilder.query(termQueryBuilder);sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));// 执行搜索searchRequest.source(sourceBuilder);SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);// 解析结果ArrayList<Map<String, Object>> list = new ArrayList<>();for (SearchHit documentFields : searchResponse.getHits().getHits()) {list.add(documentFields.getSourceAsMap());}return list;}
}
创建业务controller
@RestController
public class ContentController {@Autowiredprivate ContentService contentService;// 先从京东抓取数据并存入es中@GetMapping("/parse/{keyword}")public Boolean parse(@PathVariable("keyword") String keywords) throws Exception {return contentService.parseContent(keywords);}// 在es中搜索数据返回结果@GetMapping("/search/{keyword}/{pageNo}/{pageSize}")public List<Map<String, Object>> search(@PathVariable("keyword") String keyword,@PathVariable("pageNo") int pageNo,@PathVariable("pageSize") int pageSize) throws IOException {return contentService.searchPage(keyword, pageNo, pageSize);}
}
测试成功后,现拿到了数据,还差前端页面展示,使用 Vue
4.前端页面展示
下载vue相关文件
随便找一处空目录,进入目录cmd,执行命令
npm install vue
npm install axios
找到 node_modules\vue\dist 目录下 vue.js,node_modules\axios\dist 目录下 axios.js
复制到我们项目的静态目录中
改造我们 index.html 页面,实现前后端分离
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="utf-8"/><title>狂神说Java-ES仿京东实战</title><link rel="stylesheet" th:href="@{/css/style.css}"/><script th:src="@{/js/jquery.min.js}"></script>
</head>
<body class="pg">
<div class="page"><div id="app" class=" mallist tmall- page-not-market "><!-- 头部搜索 --><div id="header" class=" header-list-app"><div class="headerLayout"><div class="headerCon "><!-- Logo--><h1 id="mallLogo"><img th:src="@{/images/jdlogo.png}" alt=""></h1><div class="header-extra"><!--搜索--><div id="mallSearch" class="mall-search"><form name="searchTop" class="mallSearch-form clearfix"><fieldset><legend>天猫搜索</legend><div class="mallSearch-input clearfix"><div class="s-combobox" id="s-combobox-685"><div class="s-combobox-input-wrap"><input v-model="keyword" type="text" autocomplete="off" id="mq"class="s-combobox-input" aria-haspopup="true"></div></div><button type="submit" @click.prevent="searchKey" id="searchbtn">搜索</button></div></fieldset></form><ul class="relKeyTop"><li><a>狂神说Java</a></li><li><a>狂神说前端</a></li><li><a>狂神说Linux</a></li><li><a>狂神说大数据</a></li><li><a>狂神聊理财</a></li></ul></div></div></div></div></div><!-- 商品详情页面 --><div id="content"><div class="main"><!-- 品牌分类 --><form class="navAttrsForm"><div class="attrs j_NavAttrs" style="display:block"><div class="brandAttr j_nav_brand"><div class="j_Brand attr"><div class="attrKey">品牌</div><div class="attrValues"><ul class="av-collapse row-2"><li><a href="#"> 狂神说 </a></li><li><a href="#"> Java </a></li></ul></div></div></div></div></form><!-- 排序规则 --><div class="filter clearfix"><a class="fSort fSort-cur">综合<i class="f-ico-arrow-d"></i></a><a class="fSort">人气<i class="f-ico-arrow-d"></i></a><a class="fSort">新品<i class="f-ico-arrow-d"></i></a><a class="fSort">销量<i class="f-ico-arrow-d"></i></a><a class="fSort">价格<i class="f-ico-triangle-mt"></i><i class="f-ico-triangle-mb"></i></a></div><!-- 商品详情 --><div class="view grid-nosku" ><div class="product" v-for="result in results"><div class="product-iWrap"><!--商品封面--><div class="productImg-wrap"><a class="productImg"><img :src="result.img"></a></div><!--价格--><p class="productPrice"><em v-text="result.price"></em></p><!--标题--><p class="productTitle"><a v-html="result.title"></a></p><!-- 店铺名 --><div class="productShop"><span>店铺: 狂神说Java </span></div><!-- 成交信息 --><p class="productStatus"><span>月成交<em>999笔</em></span><span>评价 <a>3</a></span></p></div></div></div></div></div></div>
</div>
<script th:src="@{/js/vue.js}"></script>
<script th:src="@{/js/axios.js}"></script>
<script>new Vue({el:"#app",data:{"keyword": '', // 搜索的关键字"results":[] // 后端返回的结果},methods:{searchKey(){var keyword = this.keyword;console.log(keyword);axios.get('search/'+keyword+'/0/20').then(response=>{console.log(response.data);this.results=response.data;})}}});
</script>
</body>
</html>
5.高亮显示
修改 service业务层,基于方法2的代码,我们创建方法3
// 3、 在2的基础上进行高亮查询public List<Map<String, Object>> highlightSearch(String keyword, Integer pageNo, Integer pageSize) throws IOException {SearchRequest searchRequest = new SearchRequest("jd_goods");SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();// 精确查询,添加查询条件TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));searchSourceBuilder.query(termQueryBuilder);// 分页searchSourceBuilder.from(pageNo);searchSourceBuilder.size(pageSize);// 高亮 =========HighlightBuilder highlightBuilder = new HighlightBuilder();highlightBuilder.field("title");highlightBuilder.preTags("<span style='color:red'>");highlightBuilder.postTags("</span>");searchSourceBuilder.highlighter(highlightBuilder);// 执行查询searchRequest.source(searchSourceBuilder);SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);// 解析结果 ==========List<Map<String, Object>> list = new ArrayList<>();for (SearchHit documentFields : searchResponse.getHits().getHits()) {// 使用新的字段值(高亮),覆盖旧的字段值Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();// 高亮字段Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();HighlightField title = highlightFields.get("title");// 替换if (title != null){Text[] fragments = title.fragments();StringBuilder new_title = new StringBuilder();for (Text text : fragments) {new_title.append(text);}sourceAsMap.put("title",new_title.toString());}list.add(sourceAsMap);}return list;}
}
controller 将接受方法修改为高亮业务
// 在es中搜索数据返回结果@GetMapping("/search/{keyword}/{pageNo}/{pageSize}")public List<Map<String, Object>> search(@PathVariable("keyword") String keyword,@PathVariable("pageNo") int pageNo,@PathVariable("pageSize") int pageSize) throws IOException {return contentService.highlightSearch(keyword, pageNo, pageSize);}
修改 index.html 页面,使其可以解析高亮标签
<!--标题-->
<p class="productTitle"><a v-html="result.title"></a>
</p>
2021最新版 ElasticSearch 7.6.1 教程详解 爬虫jsoup+es模拟京东搜索(狂神说)相关推荐
- Spring MVC 教程详解 个人总结 复习必备 面试宝典 狂神笔记
文章目录 一.MVC 模式 1.什么是 MVC 2.Servlet MVC 小结 二.Spring MVC 1.Spring MVC 概念 为什么学习 Spring MVC 中央控制器 Dispatc ...
- es springboot 不设置id_es(elasticsearch)整合SpringCloud(SpringBoot)搭建教程详解
注意:适用于springboot或者springcloud框架 1.首先下载相关文件 2.然后需要去启动相关的启动文件 3.导入相关jar包(如果有相关的依赖包不需要导入)以及配置配置文件,并且写一个 ...
- 【JavaEE】 IntelliJ IDEA 2022.2最新版Tomcat导入依赖详细教程全解及创建第一个Servlet程序
目录 一.软件资源 二.放置settings.xml文件 三.创建项目 四.引入依赖 五.创建目录 六.编写代码 写在前面:☞What is Servlet? Servlet其实是一种实现动态页面的 ...
- Windows系统下nodejs、npm、express的下载和安装教程详解
这篇文章主要介绍了Windows系统下nodejs.npm.express的下载和安装教程详解,非常不错,具有参考借鉴价值,需要的朋友可以参考下 1. node.js下载 首先进入http://nod ...
- linux添加nginx,linux下安装Nginx1.16.0的教程详解
因为最近在倒腾linux,想安装新版本的nginx,找了一圈教程没有找到对应的教程,在稍微倒腾了一会之后终于成功的安装了最新版. 服务器环境为centos,接下来是详细步骤: 安装必要依赖插件 ? 创 ...
- mysql5.7.14安装版_MySql5.7.14安装教程详解(解压版)_MySQL
下面进入正式的教程: 第一步:下载最近的MySQL文件并且解压: 下载最新版的MySQL–mysql-5.7.12下载地址 将下载到的文件解压缩到自己喜欢的位置,例如我自己的位置是D:\MySQL\m ...
- centos 编译安装 mysql_CentOS7编译安装MySQL5.7.24的教程详解
安装依赖 (1)cmake是新版MySQL的编译工具 sudo yum install gcc gcc-c++ pcre pcre-devel openssl openssl-devel sudo y ...
- 戴尔台式计算机怎么安装的,戴尔Dell电脑U盘安装台式机win10系统教程详解
最近有位戴尔Dell电脑用户,在使用电脑的时候,因为操作失误导致Windows文件出现问题,需要重装系统才可以解决.因此,大白菜整理了一些u盘重装系统的资料,下面就来看看戴尔Dell电脑U盘安装台式机 ...
- linux surface pro 4 driver,重置出错?微软Win10平板Surface Pro 4重装系统教程详解
Surface Pro 4系统重置出错该怎么办? Surface Pro 4无法启动该怎办? Surface Pro 4平板如何重装Win10系统? 在Win10刚刚发布时,很多用户在升级Window ...
最新文章
- linux和java_java内存和linux关系
- 4 在vCenter Server安装View Composer组件
- 深入浅出Yolov5之自有数据集训练超详细教程
- jsp jdbc mysql增删改查_使用JSP+SERVLET+JDBC实现对数据库的增删改查(详细)
- 【牛客 - 1080B】tokitsukaze and Hash Table(STLset,并查集,Hash)
- p2p-如何拯救k8s镜像分发的阿喀琉斯之踵?
- Linux第二次作业
- BZOJ 2660 (BJOI 2012) 最多的方案
- Linux部署安装JDK
- SQL语言:DQL,DML,DDL,DCL
- matlab寻找直线_matlab寻找直线_Matlab 霍夫变换 ( Hough Transform) 直线检测
- ckfinder php 漏洞,编辑器漏洞
- pushplus通过企业微信应用给微信发送消息教程
- Linux操作系统的基本命令
- c语言课程设计--打飞碟源代码,飞碟游戏(飞碟游戏规则)
- 网络协议-语义、语法和时序
- Ultra-Fast Mathematician
- Linux usb设备驱动(2)--- usbmouse.c 源码分析
- 磁盘性能分析Disk
- PLSQL存储过程定时作业(DBMS_JOB)