Python操作elasticsearch
Python操作elasticsearch
一、elasticsearch for Python之连接篇
前言
现在,我们来学习Python如何操作elasticsearch。
依赖下载
首先,我们必须拥有Python的环境,如何搭建Python环境,请参阅。
要用Python来操作elasticsearch,首先安装Python的elasticsearch包:
pip install elasticsearch
pip install elasticsearch==6.3.1
# 豆瓣源
pip install -i https://pypi.doubanio.com/simple/ elasticsearch
Python连接elasticsearch
Python连接elasticsearch有以下几种连接方式:
from elasticsearch import Elasticsearch
# es = Elasticsearch() # 默认连接本地elasticsearch
# es = Elasticsearch(['127.0.0.1:9200']) # 连接本地9200端口
es = Elasticsearch(
["192.168.1.10", "192.168.1.11", "192.168.1.12"], # 连接集群,以列表的形式存放各节点的IP地址
sniff_on_start=True, # 连接前测试
sniff_on_connection_fail=True, # 节点无响应时刷新节点
sniff_timeout=60 # 设置超时时间
)
配置忽略响应状态码
es = Elasticsearch(['127.0.0.1:9200'],ignore=400) # 忽略返回的400状态码
es = Elasticsearch(['127.0.0.1:9200'],ignore=[400, 405, 502]) # 以列表的形式忽略多个状态码
一个简单的示例
from elasticsearch import Elasticsearch
es = Elasticsearch() # 默认连接本地elasticsearch
print(es.index(index='py2', doc_type='doc', id=1, body={'name': "张开", "age": 18}))
print(es.get(index='py2', doc_type='doc', id=1))
第1个print为创建py2
索引,并插入一条数据,第2个print查询指定文档。
查询结果如下:
{'_index': 'py2', '_type': 'doc', '_id': '1', '_version': 1, 'result': 'created', '_shards': {'total': 2, 'successful': 1, 'failed': 0}, '_seq_no': 0, '_primary_term': 1}
{'_index': 'py2', '_type': 'doc', '_id': '1', '_version': 1, 'found': True, '_source': {'name': '张开', 'age': 18}}
see also:[API文档-Elasticsearch6.3.1文档](https://elasticsearch-py.readthedocs.io/en/master/api.html#global-options)
二、elasticsearch for Python之操作篇
前言
Python中关于elasticsearch的操作,主要集中一下几个方面:
- 结果过滤,对于返回结果做过滤,主要是优化返回内容。
- Elasticsearch(简称es),直接操作elasticsearch对象,处理一些简单的索引信息。一下几个方面都是建立在es对象的基础上。
- Indices,关于索引的细节操作,比如创建自定义的
mappings
。 - Cluster,关于集群的相关操作。
- Nodes,关于节点的相关操作。
- Cat API,换一种查询方式,一般的返回都是json类型的,cat提供了简洁的返回结果。
- Snapshot,快照相关,快照是从正在运行的Elasticsearch集群中获取的备份。我们可以拍摄单个索引或整个群集的快照,并将其存储在共享文件系统的存储库中,并且有一些插件支持S3,HDFS,Azure,Google云存储等上的远程存储库。
- Task Management API,任务管理API是新的,仍应被视为测试版功能。API可能以不向后兼容的方式更改。
结果过滤
print(es.search(index='py2', filter_path=['hits.total', 'hits.hits._source'])) # 可以省略type类型
print(es.search(index='w2', doc_type='doc')) # 可以指定type类型
print(es.search(index='w2', doc_type='doc', filter_path=['hits.total']))
filter_path
参数用于减少elasticsearch返回的响应,比如仅返回hits.total
和hits.hits._source
内容。
除此之外,filter_path
参数还支持*
通配符以匹配字段名称、任何字段或者字段部分:
print(es.search(index='py2', filter_path=['hits.*']))
print(es.search(index='py2', filter_path=['hits.hits._*']))
print(es.search(index='py2', filter_path=['hits.to*'])) # 仅返回响应数据的total
print(es.search(index='w2', doc_type='doc', filter_path=['hits.hits._*'])) # 可以加上可选的type类型
Elasticsearch(es对象)
- es.index,向指定索引添加或更新文档,如果索引不存在,首先会创建该索引,然后再执行添加或者更新操作。
# print(es.index(index='w2', doc_type='doc', id='4', body={"name":"可可", "age": 18})) # 正常
# print(es.index(index='w2', doc_type='doc', id=5, body={"name":"卡卡西", "age":22})) # 正常
# print(es.index(index='w2', id=6, body={"name": "鸣人", "age": 22})) # 会报错,TypeError: index() missing 1 required positional argument: 'doc_type'
print(es.index(index='w2', doc_type='doc', body={"name": "鸣人", "age": 22})) # 可以不指定id,默认生成一个id
- es.get,查询索引中指定文档。
print(es.get(index='w2', doc_type='doc', id=5)) # 正常
print(es.get(index='w2', doc_type='doc')) # TypeError: get() missing 1 required positional argument: 'id'
print(es.get(index='w2', id=5)) # TypeError: get() missing 1 required positional argument: 'doc_type'
- es.search,执行搜索查询并获取与查询匹配的搜索匹配。这个用的最多,可以跟复杂的查询条件。
index
要搜索的以逗号分隔的索引名称列表; 使用_all 或空字符串对所有索引执行操作。doc_type
要搜索的以逗号分隔的文档类型列表; 留空以对所有类型执行操作。body
使用Query DSL(QueryDomain Specific Language查询表达式)的搜索定义。_source
返回_source
字段的true或false,或返回的字段列表,返回指定字段。_source_exclude
要从返回的_source
字段中排除的字段列表,返回的所有字段中,排除哪些字段。_source_include
从_source
字段中提取和返回的字段列表,跟_source
差不多。
print(es.search(index='py3', doc_type='doc', body={"query": {"match":{"age": 20}}})) # 一般查询
print(es.search(index='py3', doc_type='doc', body={"query": {"match":{"age": 19}}},_source=['name', 'age'])) # 结果字段过滤
print(es.search(index='py3', doc_type='doc', body={"query": {"match":{"age": 19}}},_source_exclude =[ 'age']))
print(es.search(index='py3', doc_type='doc', body={"query": {"match":{"age": 19}}},_source_include =[ 'age']))
- es.get_source,通过索引、类型和ID获取文档的来源,其实,直接返回想要的字典。
print(es.get_source(index='py3', doc_type='doc', id='1')) # {'name': '王五', 'age': 19}
- es.count,执行查询并获取该查询的匹配数。比如查询年龄是18的文档。
body = {
"query": {
"match": {
"age": 18
}
}
}
print(es.count(index='py2', doc_type='doc', body=body)) # {'count': 1, '_shards': {'total': 5, 'successful': 5, 'skipped': 0, 'failed': 0}}
print(es.count(index='py2', doc_type='doc', body=body)['count']) # 1
print(es.count(index='w2')) # {'count': 6, '_shards': {'total': 5, 'successful': 5, 'skipped': 0, 'failed': 0}}
print(es.count(index='w2', doc_type='doc')) # {'count': 6, '_shards': {'total': 5, 'successful': 5, 'skipped': 0, 'failed': 0}}
- es.create,创建索引(索引不存在的话)并新增一条数据,索引存在仅新增(只能新增,重复执行会报错)。
print(es.create(index='py3', doc_type='doc', id='1', body={"name": '王五', "age": 20}))
print(es.get(index='py3', doc_type='doc', id='3'))
在内部,调用了index,等价于:
print(es.index(index='py3', doc_type='doc', id='4', body={"name": "麻子", "age": 21}))
但个人觉得没有index好用!
- es.delete,删除指定的文档。比如删除文章id为
4
的文档,但不能删除索引,如果想要删除索引,还需要es.indices.delete来处理
print(es.delete(index='py3', doc_type='doc', id='4'))
- es.delete_by_query,删除与查询匹配的所有文档。
index
要搜索的以逗号分隔的索引名称列表; 使用_all 或空字符串对所有索引执行操作。doc_type
要搜索的以逗号分隔的文档类型列表; 留空以对所有类型执行操作。body
使用Query DSL的搜索定义。
print(es.delete_by_query(index='py3', doc_type='doc', body={"query": {"match":{"age": 20}}}))
- es.exists,查询elasticsearch中是否存在指定的文档,返回一个布尔值。
print(es.exists(index='py3', doc_type='doc', id='1'))
- es.info,获取当前集群的基本信息。
print(es.info())
- es.ping,如果群集已启动,则返回True,否则返回False。
print(es.ping())
Indices(es.indices)
- es.indices.create,在Elasticsearch中创建索引,用的最多。比如创建一个严格模式、有4个字段、并为
title
字段指定ik_max_word
查询粒度的mappings
。并应用到py4
索引中。这也是常用的创建自定义索引的方式。
body = {
"mappings": {
"doc": {
"dynamic": "strict",
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"url": {
"type": "text"
},
"action_type": {
"type": "text"
},
"content": {
"type": "text"
}
}
}
}
}
es.indices.create('py4', body=body)
- es.indices.analyze,返回分词结果。
es.indices.analyze(body={'analyzer': "ik_max_word", "text": "皮特和茱丽当选“年度模范情侣”Brad Pitt and Angelina Jolie"})
- es.indices.delete,在Elasticsearch中删除索引。
print(es.indices.delete(index='py4'))
print(es.indices.delete(index='w3')) # {'acknowledged': True}
- es.indices.put_alias,为一个或多个索引创建别名,查询多个索引的时候,可以使用这个别名。
index
别名应指向的逗号分隔的索引名称列表(支持通配符),使用_all对所有索引执行操作。name
要创建或更新的别名的名称。body
别名的设置,例如路由或过滤器。
print(es.indices.put_alias(index='py4', name='py4_alias')) # 为单个索引创建别名
print(es.indices.put_alias(index=['py3', 'py2'], name='py23_alias')) # 为多个索引创建同一个别名,联查用
- es.indices.delete_alias,删除一个或多个别名。
print(es.indices.delete_alias(index='alias1'))
print(es.indices.delete_alias(index=['alias1, alias2']))
- es.indices.get_mapping,检索索引或索引/类型的映射定义。
print(es.indices.get_mapping(index='py4'))
- es.indices.get_settings,检索一个或多个(或所有)索引的设置。
print(es.indices.get_settings(index='py4'))
- es.indices.get,允许检索有关一个或多个索引的信息。
print(es.indices.get(index='py2')) # 查询指定索引是否存在
print(es.indices.get(index=['py2', 'py3']))
- es.indices.get_alias,检索一个或多个别名。
print(es.indices.get_alias(index='py2'))
print(es.indices.get_alias(index=['py2', 'py3']))
- es.indices.get_field_mapping,检索特定字段的映射信息。
print(es.indices.get_field_mapping(fields='url', index='py4', doc_type='doc'))
print(es.indices.get_field_mapping(fields=['url', 'title'], index='py4', doc_type='doc'))
- es.indices.delete_alias,删除特定别名。
- es.indices.exists,返回一个布尔值,指示给定的索引是否存在。
- es.indices.exists_type,检查索引/索引中是否存在类型/类型。
- es.indices.flus,明确的刷新一个或多个索引。
- es.indices.get_field_mapping,检索特定字段的映射。
- es.indices.get_template,按名称检索索引模板。
- es.indices.open,打开一个封闭的索引以使其可用于搜索。
- es.indices.close,关闭索引以从群集中删除它的开销。封闭索引被阻止进行读/写操作。
- es.indices.clear_cache,清除与一个或多个索引关联的所有缓存或特定缓存。
- es.indices.put_alias,为特定索引/索引创建别名。
- es.indices.get_uprade,监控一个或多个索引的升级程度。
- es.indices.put_mapping,注册特定类型的特定映射定义。
- es.indices.put_settings,实时更改特定索引级别设置。
- es.indices.put_template,创建一个索引模板,该模板将自动应用于创建的新索引。
- es.indices.rollove,当现有索引被认为太大或太旧时,翻转索引API将别名转移到新索引。API接受单个别名和条件列表。别名必须仅指向单个索引。如果索引满足指定条件,则创建新索引并切换别名以指向新别名。
- es.indices.segments,提供构建Lucene索引(分片级别)的低级别段信息。
Cluster(集群相关)
- es.cluster.get_settigns,获取集群设置。
print(es.cluster.get_settings())
- es.cluster.health,获取有关群集运行状况的非常简单的状态。
print(es.cluster.health())
- es.cluster.state,获取整个集群的综合状态信息。
print(es.cluster.state())
- es.cluster.stats,返回群集的当前节点的信息。
print(es.cluster.stats())
Node(节点相关)
- es.nodes.info,返回集群中节点的信息。
print(es.nodes.info()) # 返回所节点
print(es.nodes.info(node_id='node1')) # 指定一个节点
print(es.nodes.info(node_id=['node1', 'node2'])) # 指定多个节点列表
- es.nodes.stats,获取集群中节点统计信息。
print(es.nodes.stats())
print(es.nodes.stats(node_id='node1'))
print(es.nodes.stats(node_id=['node1', 'node2']))
- es.nodes.hot_threads,获取指定节点的线程信息。
print(es.nodes.hot_threads(node_id='node1'))
print(es.nodes.hot_threads(node_id=['node1', 'node2']))
- es.nodes.usage,获取集群中节点的功能使用信息。
print(es.nodes.usage())
print(es.nodes.usage(node_id='node1'))
print(es.nodes.usage(node_id=['node1', 'node2']))
Cat(一种查询方式)
- es.cat.aliases,返回别名信息。
name
要返回的以逗号分隔的别名列表。format
Accept标头的简短版本,例如json,yaml
print(es.cat.aliases(name='py23_alias'))
print(es.cat.aliases(name='py23_alias', format='json'))
- es.cat.allocation,返回分片使用情况。
print(es.cat.allocation())
print(es.cat.allocation(node_id=['node1']))
print(es.cat.allocation(node_id=['node1', 'node2'], format='json'))
- es.cat.count,Count提供对整个群集或单个索引的文档计数的快速访问。
print(es.cat.count()) # 集群内的文档总数
print(es.cat.count(index='py3')) # 指定索引文档总数
print(es.cat.count(index=['py3', 'py2'], format='json')) # 返回两个索引文档和
- es.cat.fielddata,基于每个节点显示有关当前加载的fielddata的信息。有些数据为了查询效率,会放在内存中,fielddata用来控制哪些数据应该被放在内存中,而这个
es.cat.fielddata
则查询现在哪些数据在内存中,数据大小等信息。
print(es.cat.fielddata())
print(es.cat.fielddata(format='json', bytes='b'))
bytes
显示字节值的单位,有效选项为:'b','k','kb','m','mb','g','gb','t','tb' ,'p','pb'
format
Accept标头的简短版本,例如json,yaml
- es.cat.health,从集群中
health
里面过滤出简洁的集群健康信息。
print(es.cat.health())
print(es.cat.health(format='json'))
- es.cat.help,返回
es.cat
的帮助信息。
print(es.cat.help())
- es.cat.indices,返回索引的信息;也可以使用此命令进行查询集群中有多少索引。
print(es.cat.indices())
print(es.cat.indices(index='py3'))
print(es.cat.indices(index='py3', format='json'))
print(len(es.cat.indices(format='json'))) # 查询集群中有多少索引
- es.cat.master,返回集群中主节点的IP,绑定IP和节点名称。
print(es.cat.master())
print(es.cat.master(format='json'))
- es.cat.nodeattrs,返回节点的自定义属性。
print(es.cat.nodeattrs())
print(es.cat.nodeattrs(format='json'))
- es.cat.nodes,返回节点的拓扑,这些信息在查看整个集群时通常很有用,特别是大型集群。我有多少符合条件的节点?
print(es.cat.nodes())
print(es.cat.nodes(format='json'))
- es.cat.plugins,返回节点的插件信息。
print(es.cat.plugins())
print(es.cat.plugins(format='json'))
- es.cat.segments,返回每个索引的Lucene有关的信息。
print(es.cat.segments())
print(es.cat.segments(index='py3'))
print(es.cat.segments(index='py3', format='json'))
- es.cat.shards,返回哪个节点包含哪些分片的信息。
print(es.cat.shards())
print(es.cat.shards(index='py3'))
print(es.cat.shards(index='py3', format='json'))
- es.cat.thread_pool,获取有关线程池的信息。
print(es.cat.thread_pool())
Snapshot(快照相关)
- es.snapshot.create,在存储库中创建快照。
repository
存储库名称。snapshot
快照名称。body
快照定义。
- es.snapshot.delete,从存储库中删除快照。
- es.snapshot.create_repository。注册共享文件系统存储库。
- es.snapshot.delete_repository,删除共享文件系统存储库。
- es.snapshot.get,检索有关快照的信息。
- es.snapshot.get_repository,返回有关已注册存储库的信息。
- es.snapshot.restore,恢复快照。
- es.snapshot.status,返回有关所有当前运行快照的信息。通过指定存储库名称,可以将结果限制为特定存储库。
- es.snapshot.verify_repository,返回成功验证存储库的节点列表,如果验证过程失败,则返回错误消息。
Task(任务相关)
- es.tasks.get,检索特定任务的信息。
- es.tasks.cancel,取消任务。
- es.tasks.list,任务列表。
see also:[API文档-Elasticsearch6.3.1文档](https://elasticsearch-py.readthedocs.io/en/master/api.html#global-options) | [Fielddata 的过滤](https://www.elastic.co/guide/cn/elasticsearch/guide/current/_fielddata_filtering.html) | [cat nodeattrs](https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-nodeattrs.html) | [cat nodes](https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-nodes.html#cat-nodes) | [任务管理API](https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html) | [napshot And Restore]() 欢迎斧正,that's all
三、elasticsearch之使用Python批量写入数据
顺序写入100条
现在我们如果有大量的文档(例如10000000万条文档)需要写入es的某条索引中,该怎么办呢?之前学过的一次插入一条肯定不行:
import time
from elasticsearch import Elasticsearch
es = Elasticsearch()
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
print('共耗时约 {:.2f} 秒'.format(time.time() - start))
return res
return wrapper
@timer
def create_data():
""" 写入数据 """
for line in range(100):
es.index(index='s2', doc_type='doc', body={'title': line})
if __name__ == '__main__':
create_data() # 执行结果大约耗时 7.79 秒
上例为顺序向es的s2
索引(该索引已存在)写入100条文档,而且值也仅是数字。却花费了大约7秒左右,这种速度在大量数据的时候,肯定不行。那怎么办呢?
批量写入100条
现在,来介绍一种批量写入的方式:
import time
from elasticsearch import Elasticsearch
from elasticsearch import helpers
es = Elasticsearch()
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
print('共耗时约 {:.2f} 秒'.format(time.time() - start))
return res
return wrapper
@timer
def create_data():
""" 写入数据 """
for line in range(100):
es.index(index='s2', doc_type='doc', body={'title': line})
@timer
def batch_data():
""" 批量写入数据 """
action = [{
"_index": "s2",
"_type": "doc",
"_source": {
"title": i
}
} for i in range(10000000)]
helpers.bulk(es, action)
if __name__ == '__main__':
# create_data()
batch_data() # MemoryError
我们通过elasticsearch模块导入helper
,通过helper.bulk
来批量处理大量的数据。首先我们将所有的数据定义成字典形式,各字段含义如下:
_index
对应索引名称,并且该索引必须存在。_type
对应类型名称。_source
对应的字典内,每一篇文档的字段和值,可有有多个字段。
首先将每一篇文档(组成的字典)都整理成一个大的列表,然后,通过helper.bulk(es, action)
将这个列表写入到es对象中。
然后,这个程序要执行的话——你就要考虑,这个一千万个元素的列表,是否会把你的内存撑爆(MemoryError
)!很可能还没到没到写入es那一步,却因为列表过大导致内存错误而使写入程序崩溃!很不幸,我的程序报错了。下图是我在生成列表的时候,观察任务管理器的进程信息,可以发现此时Python消耗了大量的系统资源,而运行es实例的Java虚拟机却没什么变动。
解决办法是什么呢?我们可以分批写入,比如我们一次生成长度为一万的列表,再循环着去把一千万的任务完成。这样, Python和Java虚拟机达到负载均衡。
下面的示例测试10万条数据分批写入的速度:
import time
from elasticsearch import Elasticsearch
from elasticsearch import helpers
es = Elasticsearch()
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
print('共耗时约 {:.2f} 秒'.format(time.time() - start))
return res
return wrapper
@timer
def batch_data():
""" 批量写入数据 """
# 分批写
# for i in range(1, 10000001, 10000):
# action = [{
# "_index": "s2",
# "_type": "doc",
# "_source": {
# "title": k
# }
# } for k in range(i, i + 10000)]
# helpers.bulk(es, action)
# 使用生成器
for i in range(1, 100001, 1000):
action = ({
"_index": "s2",
"_type": "doc",
"_source": {
"title": k
}
} for k in range(i, i + 1000))
helpers.bulk(es, action)
if __name__ == '__main__':
# create_data()
batch_data()
注释的内容是使用列表完成,然后使用生成器完成。结果耗时约93.53 秒。
较劲,我就想一次写入一千万条
经过洒家多年临床经验发现,程序员为什么掉头发?都是因为爱较劲!
上面的例子已经不错了,但是仔细观察,还是使用了两次for循环,但是代码可否优化,答案是可以的,我们直接使用生成器:
import time
from elasticsearch import Elasticsearch
from elasticsearch import helpers
es = Elasticsearch()
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
print('共耗时约 {:.2f} 秒'.format(time.time() - start))
return res
return wrapper
@timer
def gen():
""" 使用生成器批量写入数据 """
action = ({
"_index": "s2",
"_type": "doc",
"_source": {
"title": i
}
} for i in range(100000))
helpers.bulk(es, action)
if __name__ == '__main__':
# create_data()
# batch_data()
gen()
我们将生成器交给es去处理,这样,Python的压力更小了,你要说Java虚拟机不是压力更大了,无论是分批处理还是使用生成器,虚拟机的压力都不小,写入操作本来就耗时嘛!上例测试结果大约是耗时90秒钟,还行,一千万的任务还是留给你去测试吧!
欢迎斧正,that's all
四、python启动elasticsearch
前言
你肯能受够了,每天(如果你每天电脑都关机的话)都要手动打开目录去找到es和kibana的启动文件去手动启动两个程序。那现在有好的方式解决吗?答案是有的呀!我们可以写一个脚本去自动的执行嘛!
测试环境:
- windows 10
- elasticsearch6.5.4
- kibana6.5.4
制作Python启动es和kibana启动脚本
import os
import time
import random
elasticsearch = r'C:\elasticsearch-6.5.4\bin\elasticsearch.bat'
kibana = r'C:\elasticsearch-6.5.4\kibana-6.5.4-windows-x86_64\bin\kibana.bat'
def progress_bar(item):
for i in range(11, 0, -1):
if item == 'kibana':
time.sleep(random.random() + 0.8)
else:
time.sleep(random.random() + 0.4)
res = '\r%s正在加载:%s %s%%\n' % (item, ('████' * (12 - i)), (11 - i) * 10) if i == 1 else '\r%s正在加载:%s %s%%' % (
item,
(
'████' * (
12 - i)),
(11 - i) * 10)
print('\033[31m%s\033[0m' % res, end='')
def run():
for item in [(elasticsearch, 'elasticsearch'), (kibana, 'kibana')]:
os.system('start %s' % item[0])
progress_bar(item[1])
time.sleep(10)
if __name__ == '__main__':
run()
上面的简单脚本中,我们借助os.system
来完成脚本启动工作,并添加了进度条。
每次想要启动两个程序的时候,我们只需要在Python环境下执行该脚本即可。
制作一键启动脚本
那么,我们的系统环境或许没有Python环境,又想使用该脚本,这就要使该脚本能脱离Python环境独立运行,比如说将该脚本打包成exe可执行文件等。那么怎么做呢?你可能想到py2exe和pyinstaller,这里以pyinstaller为例,我们将Python脚本打包成exe可执行文件。
首先要下载pyinstaller:
pip install pyinstaller
然后,在终端中执行要打包的文件:
F:\>pyinstaller -F run.py
如上命令,比如我们终端中的路径在F
盘根目录,脚本文件如果也在这个目录下的话,可以直接使用上述命令执行。结果会生成如一个文件两个目录:
其中,build和run.spec为生成时的依赖文件。在执行完毕后,可以删掉,最终的可执行文件run.exe
在dist目录内,这个可执行文件,我们可以放到本机的任何地方,比如桌面,非常方便。