ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
下载并运行ElasticSearch
ElasticSearch可以从elasticsearch.org下载对应的文件格式,如ZIP
状语从句:TAR.GZ
。下载并提取一个运行它的软件包之后不会容易得多,需要提前安装的Java运行时环境。
在的Windows上运行ElasticSearch
在本文章中,所使用的环境是Windows中,所以这里只介绍在的Windows上运行ElasticSearch,从柯林斯命令窗口显示运行位于bin
文件夹数中的elasticsearch.bat
。这将会启动ElasticSearch在控制台的前台运行,这意味着我们可在控制台中看到运行信息或一些错误信息,并可以使用CTRL + C停止或关闭它。
当前版本是:elasticsearch-5.2.0
下载链接:http: //artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.2.0.zip
把下载好的文件elasticsearch-5.2.0.zip
解压到D:\software\elasticsearch-5.2.0
,其目录结构如下所示 -
启动ElasticSearch -
Microsoft Windows [版本 10.0.10586]
(c) 2015 Microsoft Corporation。保留所有权利。
C:\Users\Administrator>d:
D:\>cd software\elasticsearch-5.2.0
D:\software\elasticsearch-5.2.0>cd bin
D:\software\elasticsearch-5.2.0\bin>elasticsearch.bat
[2017-01-28T14:10:32,177][INFO ][o.e.n.Node ] [] initializing ...
[2017-01-28T14:10:32,670][INFO ][o.e.e.NodeEnvironment ] [SnafGWM] using [1] data paths, mounts [[Software (D:)]], net usable_space [61.6gb], net total_space [139gb], spins? [unknown], types [NTFS]
[2017-01-28T14:10:32,686][INFO ][o.e.e.NodeEnvironment ] [SnafGWM] heap size [1.9gb], compressed ordinary object pointers [true]
[2017-01-28T14:10:32,686][INFO ][o.e.n.Node ] node name [SnafGWM] derived from node ID [SnafGWMWRzmfwTKP6VJClA]; set [node.name] to override
[2017-01-28T14:10:32,717][INFO ][o.e.n.Node ] version[5.2.0], pid[9724], build[24e05b9/2017-01-24T19:52:35.800Z], OS[Windows 10/10.0/amd64], JVM[Oracle Corporation/Java HotSpot(TM) 64-Bit Server VM/1.8.0_65/25.65-b01]
[2017-01-28T14:10:35,271][INFO ][o.e.p.PluginsService ] [SnafGWM] loaded module [aggs-matrix-stats]
[2017-01-28T14:10:35,271][INFO ][o.e.p.PluginsService ] [SnafGWM] loaded module [ingest-common]
[2017-01-28T14:10:35,271][INFO ][o.e.p.PluginsService ] [SnafGWM] loaded module [lang-expression]
[2017-01-28T14:10:35,271][INFO ][o.e.p.PluginsService ] [SnafGWM] loaded module [lang-groovy]
[2017-01-28T14:10:35,271][INFO ][o.e.p.PluginsService ] [SnafGWM] loaded module [lang-mustache]
[2017-01-28T14:10:35,287][INFO ][o.e.p.PluginsService ] [SnafGWM] loaded module [lang-painless]
[2017-01-28T14:10:35,287][INFO ][o.e.p.PluginsService ] [SnafGWM] loaded module [percolator]
[2017-01-28T14:10:35,288][INFO ][o.e.p.PluginsService ] [SnafGWM] loaded module [reindex]
[2017-01-28T14:10:35,290][INFO ][o.e.p.PluginsService ] [SnafGWM] loaded module [transport-netty3]
[2017-01-28T14:10:35,291][INFO ][o.e.p.PluginsService ] [SnafGWM] loaded module [transport-netty4]
[2017-01-28T14:10:35,292][INFO ][o.e.p.PluginsService ] [SnafGWM] no plugins loaded
[2017-01-28T14:10:41,394][INFO ][o.e.n.Node ] initialized
[2017-01-28T14:10:41,397][INFO ][o.e.n.Node ] [SnafGWM] starting ...
[2017-01-28T14:10:42,657][INFO ][o.e.t.TransportService ] [SnafGWM] publish_address {127.0.0.1:9300}, bound_addresses {127.0.0.1:9300}, {[::1]:9300}
[2017-01-28T14:10:46,439][INFO ][o.e.c.s.ClusterService ] [SnafGWM] new_master {SnafGWM}{SnafGWMWRzmfwTKP6VJClA}{vG5mFSENST6eo-yl_O8HuA}{127.0.0.1}{127.0.0.1:9300}, reason: zen-disco-elected-as-master ([0] nodes joined)
[2017-01-28T14:10:48,628][INFO ][o.e.h.HttpServer ] [SnafGWM] publish_address {127.0.0.1:9200}, bound_addresses {127.0.0.1:9200}, {[::1]:9200}
[2017-01-28T14:10:48,628][INFO ][o.e.n.Node ] [SnafGWM] started
[2017-01-28T14:10:48,928][INFO ][o.e.g.GatewayService ] [SnafGWM] recovered [0] indices into cluster_state
在启动过程中,ElasticSearch的实例运行会占用大量的内存,所以在这一过程中,电脑会变得比较慢,需要耐心等待,启动加载完成后电脑就可以正常使用了。
如果您没有安装Java运行时或没有正确配置,应该不会看到像上面的输出,而是一个消息说“ JAVA_HOME环境变量必须设置! ”要解决这个问题,首先下载并安装Java,其次,确保已正确配置
JAVA_HOME
环境变量(或参考 - Java JDK安装和配置)。
使用REST API与Sense
当ElasticSearch的实例并运行,您可以使用localhost:9200
,基于JSON的REST API与ElasticSearch进行通信。在ElasticSearch自己的文档中,所有示例都使用curl。但是,当使用API时也可使用图形客户端(如Fiddler或RESTClient),这样操作起更方便直观一些。
更方便的是Chrome插件Sense。感觉提供了一个专门用于使用ElasticSearch的REST API的简单用户界面。它还具有许多方便的功能,例如:ElasticSearch的查询语法的自动完成功能以及curl格式的复制和粘贴请求,从而可以方便地在文档中运行示例。
我们将在本教程中使用感来执行卷曲请求,建议安装检测并使用它学习后续文章内容。
安装完成后,在Chrome的右上角找到Sense的图标。第一次单击它运行Sense时,会为您准备一个非常简单的示例请求。如下图所示 -
上述请求将执行最简单的搜索查询,匹配服务器上所有索引中的所有文档。针对ElasticSearch运行,检测提供的最简单的查询,在响应结果的数据中并没有查询到任何数据,因为没有任何索引。如下所示 -
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 0,
"successful": 0,
"failed": 0
},
"hits": {
"total": 0,
"max_score": 0,
"hits": []
}
}
下一步我们来学习添加一些数据和索引,来修复这个问题。
文档管理(CRUD)
想要使用ElasticSearch,用于搜索第一步就是使用一些数据填充来索引,CRUD表“创建”或者“索引”。我们还将学习如何更新,读取和删除文档。
创建索引
在ElasticSearch索引中,对应于CRUD中的“创建”和“更新” - 如果对具有给定类型的文档进行索引,并且要插入原先不存在的ID。如果具有相同类型和ID的文档已存在,则会被覆盖。
要索引第一个JSON对象,我们对REST API创建一个PUT请求到一个由索引名称,类型名称和ID组成的URL。也就是:http://localhost:9200/<index>/<type>/[<id>]
。
索引和类型是必需的,而id
部分是可选的。如果不指定ID
,ElasticSearch会为我们生成一个ID
。但是,如果不指定id,应该使用HTTP的POST
而不是PUT
请求。
索引名称是任意的。如果服务器上没有此名称的索引,则将使用默认配置来创建一个索引。
至于类型名称,它也是任意的。它有几个用途,包括:
- 每种类型都有自己的ID空间。
- 不同类型具有不同的映射(“模式”,定义属性/字段应如何编制索引)。
- 搜索多种类型是可以的,并且也很常见,但很容易搜索一种或多种指定类型。
现在我们来索引一些内容!可以把任何东西放到索引中,只要它可以表示为单个JSON对象。在本教程中,使用索引和搜索电影的一个示例。这是一个经典的电影对象信息:
{
"title": "The Godfather",
"director": "Francis Ford Coppola",
"year": 1972
}
要创建一个索引,这里使用索引的名称为“电影”,类型名称(“电影”)和ID(“1”),并按照上述模式使用JSON对象在正文中进行请求。
curl -XPUT "http://localhost:9200/movies/movie/1" -d'
{
"title": "The Godfather",
"director": "Francis Ford Coppola",
"year": 1972
}'
可以使用卷曲来执行它,也可以使用感。这里使用感,可以自己填充网址,方法和请求正文,或者您以复制上述卷曲示例,将光标置于检测中的正文字段中写入上面的Json的对象,然后按点击绿色小箭头来执行创建索引操作。如下图所示 -
执行请求后,可以看到接收到来自ElasticSearch响应的JSON对象。如下所示 -
{
"_index": "movies",
"_type": "movie",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": true
}
响应对象包含有关索引操作的信息,例如它是否成功(“OK”)和文档ID,如果不指定则ElasticSearch会自己生成一个。
如果运行检测提供的默认搜索请求(可以使用感中的“ 历史记录 ”按钮访问,因为确实已执行它)过了,就会看到返回有数据的结果。
{
"took": 146,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "movies",
"_type": "movie",
"_id": "1",
"_score": 1,
"_source": {
"title": "The Godfather",
"director": "Francis Ford Coppola",
"year": 1972
}
}
]
}
}
在上面返回结果中,看到的是搜索结果而不是错误或是空的结果。
更新索引
现在,在索引中有了一部电影信息,接下来来了解如何更新它,添加一个类型列表。要做到这一点,只需使用相同的ID索引它。使用与之前完全相同的索引请求,但类型扩展了JSON对象。
curl -XPUT "http://localhost:9200/movies/movie/1" -d'
{
"title": "The Godfather",
"director": "Francis Ford Coppola",
"year": 1972,
"genres": ["Crime", "Drama"]
}'
ElasticSearch的响应结果与前面的大体上一样,但有一点区别,结果对象中的_version
属性的值为2
,而不是1
。响应结果如下 -
{
"_index": "movies",
"_type": "movie",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": false
}
版本号(_version
)可用于跟踪文档已编入索引的次数。它的主要目的是允许乐观的并发控制,因为可以在索引请求中提供一个版本,如果提供的版本高于索引中的版本,ElasticSearch将只覆盖文档内容,ID值不变,版本号自动添加。
由ID获取文档/索引
上面已经学习了索引新文档以及更新存在的文档。还看到了一个简单搜索请求的示例。如果只是想检索一个具有已知ID的索引,一个方法是搜索索引中的文档。另一个简单而快速的方法是通过ID
,使用GET
来检索它。
简单的做法是向同一个URL发出一个GET请求,URL的ID部分是强制性的。通过ID从ElasticSearch中检索文档可以发出URL的GET请求:http://localhost:9200/<index>/<type>/<id>
。
使用以下请求尝试获取电影信息:
curl -XGET "http://localhost:9200/movies/movie/1" -d''
执行结果如下所示 -
正如下图所看到的,结果对象包含与索引时所看到的类似的元数据,如索引,类型和版本信息。最后最重要的是,它有一个名称为“ _source
”的属性,它包含实际获取的文档信息。
关于GET没有什么可说的,因为它很简单,求最后继续删除操作。
删除文档
为了通过ID从索引中删除单个指定的文档,使用与获取索引文档相同的URL,只是这里将HTTP方法更改为DELETE
。
curl -XDELETE "http://localhost:9200/movies/movie/1" -d''
响应对象包含元数据方面的一些常见数据字段,以及名为“ _found
”的属性,表示文档确实已找到并并操作成功。
执行在
DELETE
调用后回e月刊GET
,可以验证文档是否确实已删除。
搜索
在前面,已经介绍了在ElasticSearch索引中处理数据的基础知识,现在是时候进行核心功能的学习了。考虑到之前我们删除索引中的所有文档,所以,在进行搜索学习之前,需要一些添加一些示例数据。使用以下这些请求和数据对象来创建索引。
curl -XPUT "http://localhost:9200/movies/movie/1" -d'
{
"title": "The Godfather",
"director": "Francis Ford Coppola",
"year": 1972,
"genres": ["Crime", "Drama"]
}'
curl -XPUT "http://localhost:9200/movies/movie/2" -d'
{
"title": "Lawrence of Arabia",
"director": "David Lean",
"year": 1962,
"genres": ["Adventure", "Biography", "Drama"]
}'
curl -XPUT "http://localhost:9200/movies/movie/3" -d'
{
"title": "To Kill a Mockingbird",
"director": "Robert Mulligan",
"year": 1962,
"genres": ["Crime", "Drama", "Mystery"]
}'
curl -XPUT "http://localhost:9200/movies/movie/4" -d'
{
"title": "Apocalypse Now",
"director": "Francis Ford Coppola",
"year": 1979,
"genres": ["Drama", "War"]
}'
curl -XPUT "http://localhost:9200/movies/movie/5" -d'
{
"title": "Kill Bill: Vol. 1",
"director": "Quentin Tarantino",
"year": 2003,
"genres": ["Action", "Crime", "Thriller"]
}'
curl -XPUT "http://localhost:9200/movies/movie/6" -d'
{
"title": "The Assassination of Jesse James by the Coward Robert Ford",
"director": "Andrew Dominik",
"year": 2007,
"genres": ["Biography", "Crime", "Drama"]
}'
值得指出的是,ElasticSearch具有和端点(_bulk
)用于用单个请求索引多个文档,但是这超出了本教程的范围,这里只保持简单,使用六个单独的请求学习。
_search端点
现在已经把一些电影信息放入了索引,可以通过搜索看看是否可找到它们。为了使用ElasticSearch进行搜索,我们使用_search
端点,可选择使用索引和类型。也就是说,按照以下模式向URL发出请求:<index>/<type>/_search
。其中,index
状语从句:type
都是可选的。
换句话说,为了搜索电影,可以对以下任一网址进行POST请求:
- http:// localhost:9200 / _search - 搜索所有索引和所有类型。
- http:// localhost:9200 / movies / _search - 在电影索引中搜索所有类型
- http:// localhost:9200 / movies / movie / _search - 在电影索引中显示搜索电影类型的文档。
因为我们只有一个单一的索引和单一的类型,所以怎么使用都不会有什么问题。为了简洁起见使用第一个URL。
搜索请求正文和ElasticSearch查询DSL
如果只是发送一个请求到上面的URL,我们会得到所有的电影信息。为了创建更有用的搜索请求,还需要向请求正文中提供查询。请求正文是一个JSON对象,除了其它属性以外,它还要包含一个名称为“ query
”的属性,这就可使用ElasticSearch的查询DSL。
{
"query": {
//Query DSL here
}
}
你可能想知道查询DSL是什么。它是ElasticSearch自己基于JSON的域特定语言,可以在其中表达查询和过滤器。想象ElasticSearch它像关系数据库的SQL。这里是ElasticSearch自己的文档解释它的一部分(英文好自己撸吧):
将Query DSL视为查询的AST。某些查询可以包含其他查询(如bool查询),其他查询可以包含过滤器(如constant_score),有些查询可以包含查询和过滤器(如过滤)。其中每个都可以包含查询列表的任何查询或过滤器列表中的任何过滤器,从而能够构建非常复杂(和有趣)的查询。查看更多:http://www.elasticsearch.org/guide/reference/query-dsl/
基本自由文本搜索
查询DSL具有一长列不同类型的查询可以使用。对于“普通”自由文本搜索,最有可能想使用一个名称为“查询字符串查询”。
字符串查询查询的英文一个高级查询,有很多不同的选项,ElasticSearch将解析和转换为更简单的查询树。如果忽略了所有的可选参数,并且只需要给它一个字符串用于搜索,它可以很容易使用。
现在尝试在两部电影的标题中搜索有“杀”这个词的电影信息:
curl -XPOST "http://localhost:9200/_search" -d'
{
"query": {
"query_string": {
"query": "kill"
}
}
}'
执行上面的请求并查看结果,如下所示 -
正如预期的,得到两个命中结果,每个电影的标题中都带有“杀”单词。再看看另一种情况,在特定字段中搜索。
指定搜索的字段
query
如前所述,查询字符串查询有一些可以指定设置,如果不使用,它将会使用默认在前面的例子中,使用了一个非常简单的查询的设置值。
这样的设置称为“fields”,可用于指定要搜索的字段列表。如果不使用“fields”字段,ElasticSearch查询将默认自动生成的名为“ _all
”的特殊字段,来基于所有文档中的各个字段匹配搜索。
为了做到这一点,修改以前的搜索请求正文,查询以便查询字符串有一个fields
属性用来要搜索的字段数组:
curl -XPOST "http://localhost:9200/_search" -d'
{
"query": {
"query_string": {
"query": "ford",
"fields": ["title"]
}
}
}'
执行上面查询它,看看会有什么结果(只应该匹配到1
行数据):
正如预期的得到一个命中,电影的标题中的单词“ ford
”。现在,从查询中移除fields
属性,应该能匹配到3
行数据:
过滤
前面已经介绍了几个简单的自由文本搜索查询。现在来看看另一个示例,搜索“ drama
”,不明确指定字段,如下查询 -
curl -XPOST "http://localhost:9200/_search" -d'
{
"query": {
"query_string": {
"query": "drama"
}
}
}'
因为在索引中有五部电影在_all
字段(从类别字段)中包含单词“ drama
”,所以得到了上述查询的5
个命中。现在,想象一下,如果我们想限制这些命中为只是1962
年发布的电影。要求到这点,需要应用一个过滤器,要求“ year
”字段等于1962
。
要添加过滤器,修改搜索请求正文,以便当前的顶级查询(查询字符串查询)包含在过滤的查询中:
{
"query": {
"filtered": {
"query": {
"query_string": {
"query": "drama"
}
},
"filter": {
//Filter to apply to the query
}
}
}
}
过滤的查询是具有两个属性(query
和filter
)的查询。执行时,它使用过滤器过滤查询的结果。要完成这样的查询还需要添加一个过滤器,要求year
字段的值为1962
。
ElasticSearch查询DSL有各种各样的过滤器可供选择。对于这个简单的情况,某个字段应该匹配一个特定的值,一个条件过滤器就能很好地完成工作。
"filter": {
"term": { "year": 1962 }
}
完整的搜索请求如下所示:
curl -XPOST "http://localhost:9200/_search" -d'
{
"query": {
"filtered": {
"query": {
"query_string": {
"query": "drama"
}
},
"filter": {
"term": { "year": 1962 }
}
}
}
}'
当执行上面请求,只得到两个命中,两个这个命中的数据的year
字段的值都是等于1962
。
无需查询即可进行过滤
在上面的示例中,使用过滤器限制查询字符串查询的结果。如果想要做的是应用一个过滤器呢?也就是说,我们希望所有电影符合一定的标准。
在这种情况下,我们仍然在搜索请求正文中使用“ query
”属性。但是,我们不能只是添加一个过滤器,需要将它包装在某种查询中。
一个解决方案是修改当前的搜索请求,查询替换字符串query
过滤查询中的match_all
查询,这是一个查询,只是匹配一切类似下面这个:
curl -XPOST "http://localhost:9200/_search" -d'
{
"query": {
"filtered": {
"query": {
"match_all": {
}
},
"filter": {
"term": { "year": 1962 }
}
}
}
}'
另一个更简单的方法是使用常数分数查询:
curl -XPOST "http://localhost:9200/_search" -d'
{
"query": {
"constant_score": {
"filter": {
"term": { "year": 1962 }
}
}
}
}'
<br>
<p style="text-align:center;float:left;width:100%;margin-top:24px;margin-bottom:48px;">
<button class="layui-btn layui-btn-danger" id="btn-reward"><font style="vertical-align: inherit;"><font style="vertical-align: inherit;"> ¥我要打赏 </font></font></button>
<button class="btn" id="article-correction" data-url="https://www.yiibai.com/article/correction/9594">
<i class="layui-icon layui-icon-edit" style="color: #1E9FFF;"></i><font style="vertical-align: inherit;"><font style="vertical-align: inherit;"> 教程纠错
</font></font></button>
</p>
<p>
<b style="color:#f57e42;"><font style="vertical-align: inherit;"><font style="vertical-align: inherit;">易百教程移动端</font></font></b><font style="vertical-align: inherit;"><font style="vertical-align: inherit;">:请扫描本页面底部(右侧)二维码并关注微信公众号,回复:“ </font></font><b><font style="vertical-align: inherit;"><font style="vertical-align: inherit;">教程</font></font></b><font style="vertical-align: inherit;"><font style="vertical-align: inherit;"> ”选择相关教程阅读或直接访问:http://m.yiibai.com。
</font></font></p>
<div style="width:100%;margin-bottom: 16px; height: 32px;margin-top:18px;float:left;">
<span style="float:left;"><font style="vertical-align: inherit;"><font style="vertical-align: inherit;">上一篇:</font></font><a href="https://www.yiibai.com/elasticsearch/elasticsearch_testing.html#article-start" title="Elasticsearch测试"><i class="layui-icon"></i><font style="vertical-align: inherit;"><font style="vertical-align: inherit;">
Elasticsearch测试</font></font></a></span>
<span style="float:right;"><font style="vertical-align: inherit;"><font style="vertical-align: inherit;">下一篇:哥,这回真没有了</font></font></span>
<div id="googlead" style="float:left;margin-top:8px;">
<script async="" src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<!-- yiibai下方 -->
<ins class="adsbygoogle" style="display:inline-block;width:728px;height:90px" data-ad-client="ca-pub-1090193214637198" data-ad-slot="6494738921"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
</div>
</div>
<div style="width:100%;margin-top:18px;float:left;">
<h4 style="color: #f57e42;padding-top:6px;padding-bottom: 4px;float: left;width: 100%;margin-bottom:12px;"><font style="vertical-align: inherit;"><font style="vertical-align: inherit;">加QQ群啦,易百教程官方技术学习群</font></font></h4>
<blockquote style="width:100%;float:left;margin-bottom:6px;padding-bottom:4px;"><font style="vertical-align: inherit;"><font style="vertical-align: inherit;">
注意:建议每个人选自己的技术方向加群,同一个QQ最多限加3个群。
</font></font></blockquote>
<ul style="width:100%;float:left;">
<li>
<b>Java技术群:</b> 227270512 (人数:2000,免费:否)
</li>
<li>
<b>Go开发者群(新):</b> 851549018 (人数:1000,<span style="color:red;">免费</span>)
</li>
<li>
<b>PHP开发者群:</b> 460153241 (人数:2000,<span style="color:red;">免费</span>)
</li>
<li>
<b>MySQL/SQL群:</b> 418407075 (人数:2000,免费:否)
</li>
<li>
<b>大数据开发群:</b> 655154550 (人数:2000,免费:否)
</li>
<li>
<b>Python技术群:</b> 287904175 (人数:2000,免费:否)
</li>
<li>
<b>人工智能深度学习:</b> 456236082 (人数:2000,免费:否)
</li>
<li>
<b>测试工程师群:</b> 415553199 (人数:2000,免费:否)
</li>
<li>
<b>前端开发者群:</b> 410430016 (人数:2000,免费:否)
</li>
<li>
<b>C/C++技术群(新):</b> 629264796 (人数:2000,<span style="color:red;">免费</span>)
</li>
<li>
<b>Node.js技术群(新):</b> 621549808 (人数:2000,<span style="color:red;">免费</span>)
</li>
<li>
<b>PostgreSQL数据库群:</b> 539504187 (人数:1000,<span style="color:red;">免费</span>)
</li>
<li>
<b>Linux运维技术群:</b> 479429477 (人数:2000,免费:否)
</li>
<li>
<b>Oracle数据库:</b> 175248146 (人数:2000,免费:否)
</li>
<li>
<b>C#/ASP.Net开发者:</b> 579821706 (人数:2000,<span style="color:red;">免费</span>)
</li>
<li>
<b>数据分析师群:</b> 397883996 (人数:2000,免费:否)
</li>
</ul>