Python全文搜索引擎库之redisearch-py
redisearch-py
是一个使用RediSearch Redis 模块API 的 Python 搜索引擎库。它是 RediSearch
的“官方”客户端,应该被视为其规范的客户端实现。
创建redisearch-py
实例
当您创建 redisearch-py
客户端实例时,唯一需要的参数是索引的名称。
1 | from redisearch import Client |
要使用用户名和/或密码进行连接,请将这些选项传递给客户端初始化程序。
1 | client = Client("my-index", host='localhost', port='6666', password="my-password") |
检查 RediSearch
索引是否存在
要检查 RediSearch
索引是否存在,请使用命令并在索引不存在时FT.INFO
捕获引发的错误。ResponseError
1 | from redis import ResponseError |
但是实际开发中我们通常会在创建索引前或先删除已有的索引(类比sql
建表)
1 | from redisearch.client import Client, Query |
索引
创建索引
您只需要在创建索引时,使用IndexDefinition
实例来定义一个搜索索引。
RediSearch
索引通过观察键前缀来跟踪 Redis
数据库中的hash。如果从 Redis
添加、更新或删除以客户端配置的键前缀开头的hash,RediSearch
将在索引中进行同步变更。您可以在初始化RediSearch
客户端时,在IndexDefinition
参数中配置键前缀。
注意:创建索引后,RediSearch
会在这些键的哈希值发生变化时持续索引这些键。
IndexDefinition
也需要一个SCHEMA。以指定要从索引遵循的哈希中索引哪些字段。字段类型有:
TextField
TagField
NumericField
GeoField
有关这些字段类型含义的更多信息,请参阅有关命令的RediSearch 文档FT.CREATE
。
使用 redisearch-py
,SCHEMA是一个可迭代的Field
实例。拥有IndexDefinition
实例后,您可以通过将字段列表传递给create_index()
方法来创建实例。
1 | from redis import ResponseError |
修改索引
修改索引的方法,redisearch-py
说明中并没有提到,但我从源码中看到其封装了一个FT.ALTER
的命令,而RediSearch
修改索引的命令就是用的这个。
需要说明的是,该修改并非无限制的修改,首先索引必须存在是前提,其次已存在的属性是不允许修改的,仅能新增属性。
1 | # 为索引增加国家字段 |
删除索引
1 | # 默认删除索引下的文档 |
从源码中发现提供了两个删除文档的方法,并且作者建议在RediSearch 2.0
使用dropindex
替换drop_index
,但是如果你仍然想在删除索引时删除对应的文档,可以添加参数,dropindex(delete_documents=True)
即可以同步删除文档(其实drop_index自带了默认值为True的参数,所以暂时可用的)。
查看索引信息
1 | client = Client('idx:test', host='localhost', port='6666') |
结果:
1 | {'index_name': 'idx:test', 'index_options': [], 'index_definition': ['key_type', 'HASH', 'prefixes', ['test:'], 'default_score', '1'], 'attributes': [['identifier', 'name', 'attribute', 'name', 'type', 'TEXT', 'WEIGHT', '1'], ['identifier', 'age', 'attribute', 'age', 'type', 'NUMERIC']], 'num_docs': '0', 'max_doc_id': '0', 'num_terms': '0', 'num_records': '0', 'inverted_sz_mb': '0', 'vector_index_sz_mb': '0', 'total_inverted_index_blocks': '7074', 'offset_vectors_sz_mb': '0', 'doc_table_size_mb': '0', 'sortable_values_size_mb': '0', 'key_table_size_mb': '0', 'records_per_doc_avg': '-nan', 'bytes_per_record_avg': '-nan', 'offsets_per_term_avg': '-nan', 'offset_bits_per_record_avg': '-nan', 'hash_indexing_failures': '0', 'total_indexing_time': '0', 'indexing': '0', 'percent_indexed': '1', 'number_of_uses': 2, 'gc_stats': ['bytes_collected', '0', 'total_ms_run', '0', 'total_cycles', '0', 'average_cycle_time_ms', '-nan', 'last_run_time_ms', '0', 'gc_numeric_trees_missed', '0', 'gc_blocks_denied', '0'], 'cursor_stats': ['global_idle', 0, 'global_total', 0, 'index_capacity', 128, 'index_total', 0], 'dialect_stats': ['dialect_1', 0, 'dialect_2', 0, 'dialect_3', 0]} |
文档
创建文档
RediSearch 2.0
索引会跟踪具有您定义的键前缀的hash(创建索引时也要设置好前缀),因此如果您想将文档添加到索引,您只需要创建一个具有这些前缀的hash。
1 | # RediSearch 2.0 创建文档的方式. |
RediSearch
的过去版本要求您调用该add_document()
方法。此方法已弃用(实际官方并没有废弃,只是没有了该方法的文档,但方法依然可用),但我们将其用法包含在此处以供参考。
1 | # RediSearch 1.x 中创建文档的方式 |
修改文档
修改文档依然可以使用add_document方法,参数replace代表是否替换原来文档;如果只是想要更新已有文档中的某些字段,而不是替换整个文档,这时就需要用到 partial 选项,
例如我们仅仅更新指定电影发行国家:
1 | client.add_document("movie:020001", country="中国", replace=True, partial=True) |
注意:partial为True时,replace必须为True,否则无效
另外你也可以使用redis
哈希修改的方式进行文档修改:client.redis.hset
删除文档
删除文档我们使用delete_document方法,但是默认只会将文档移出索引,并不会删除该文档,还是可以用client.get(docid)
的方式来获取
如果想要在移除索引文档的同时将文档一并删除,就需要在执行函数的时候将参数delete_actual_document
设为True
即可。
如:强制删除某文档:
1 | client.delete_document("movie:0001", delete_actual_document=True) |
查询文档
基本查询
使用该search()
方法执行全文text字段的搜索。此方法不采用 RediSearch
命令可用的许多选项FT.SEARCH
。
1 | # 全文搜索wizards(仅TextField字段中) |
结果对象
结果包装在一个Result
对象中,该对象包括结果数和匹配文档列表。
1 | >> print(res.total) |
构建复杂查询
后续示例我们约定按照已建好的索引进行演示,演示数据后续会放出来:
1 | from redisearch import Client, TextField, NumericField, TagField, Query, IndexDefinition |
您可以使用该Query
对象来构建复杂的查询:
1 | q = Query("夏天").verbatim().no_content().language("chinese").with_scores().paging(0, 5) |
结果:
1 | Result{2 total, docs: [Document {'id': 'movie:1300299', 'payload': None, 'score': 4.0}, Document {'id': 'movie:1858711', 'payload': None, 'score': 3.0}]} |
有关这些选项的说明,请参阅命令的RediSearch 文档FT.SEARCH
。
查询语法
查询的默认行为是在索引中的所有字段中运行全文搜索(所有TextField
字段中)以查找查询中所有术语的交集。
client.search("evil wizards")
表示过滤所有TextField
字段中包含“evil”和“wizard”的所有文档,Query()初始化程序的字符串具有 RediSearch
中可用的全部查询语法。
全文搜索
查询所有包含波兰
的文档
1 | q = Query("波兰").language("chinese").highlight() |
结果:
1 | Result{3 total, docs: [Document {'id': 'movie:1303418', 'payload': None, 'rating': '9.2', 'votes': '27133', 'alias': '生存还是毁灭 / 戏谍人生 / 扮嘢奇兵 / 生死攸关 / 嗨!我的元首 / 生或死 / 生死问题,是死?是活? / 生死大逃亡', 'description': '<b>波兰</b>华沙有一个剧团,其中有一名演员Bronski(Tom Dugan 饰)极其擅长模仿德国纳粹领袖希特勒。Maria Tura(卡洛·朗白 Carole Lombard 饰)和Joseph Tura(...', 'genre': '喜剧', 'duration': '5940', 'lang': '英语', 'name': '你逃我也逃', 'original_name': 'To Be or Not to Be', 'date_released': '1942-03-06', 'year': '1942', 'poster': 'https://wmdb.querydata.org/movie/poster/1607014476724-f79d22.jpg', 'country': '美国'}, Document {'id': 'movie:1296736', 'payload': None, 'rating': '9.2', 'votes': '409576', 'alias': '钢琴战曲(港) / 战地琴人(台) / 战地琴声 / 钢琴师', 'description': '史标曼(艾德里安•布洛迪 Adrien Brody 饰)是<b>波兰</b>一家电台的钢琴师。二战即将爆发之时,他们全家被迫被赶进华沙的犹太区。在战争的颠沛流离中,家人和亲戚最终被纳粹杀害,而标曼本人也受尽种种羞辱...', 'genre': '剧情', 'duration': '9000', 'lang': '英语', 'name': '钢琴家', 'original_name': 'The Pianist', 'date_released': '2002-05-24', 'year': '2002', 'poster': 'https://wmdb.querydata.org/movie/poster/1606347998445-205f46.jpg', 'country': '法国'}, Document {'id': 'movie:1295124', 'payload': None, 'rating': '9.5', 'votes': '843435', 'alias': '舒特拉的名单(港) / 辛德勒名单', 'description': '1939年,<b>波兰</b>在纳粹德国的统治下,党卫军对犹太人进行了隔离统治。德国商人奥斯卡·辛德勒(连姆·尼森 Liam Neeson 饰)来到德军统治下的克拉科夫,开设了一间搪瓷厂,生产军需用品。凭着出众的社...', 'genre': '历史', 'duration': '11700', 'lang': '英语', 'name': '辛德勒的名单', 'original_name': 'Schindler's List', 'date_released': '1993-11-30', 'year': '1993', 'poster': 'https://wmdb.querydata.org/movie/poster/1606049156251-837fb3.jpg', 'country': '美国'}]} |
查询所有包含战争
且包含美国
的文档
1 | q = Query("战争 美国").language("chinese").highlight() |
结果:
1 | Result{1 total, docs: [Document {'id': 'movie:1292849', 'payload': None, 'alias': '雷霆救兵(港) / 抢救雷恩大兵(台) / 拯救大兵雷恩', 'description': '瑞恩(马特•达蒙 Matt Damon饰 )是二战期间的<b>美国</b>伞兵,被困在了敌人后方。更不幸的是,他的三个兄弟全部在<b>战争</b>中死亡,如果他也遇难,家中的老母亲将无依无靠。<b>美国</b>作战总指挥部知道了这个情况,毅...', 'votes': '506703', 'rating': '9.0', 'original_name': 'Saving Private Ryan', 'name': '拯救大兵瑞恩', 'date_released': '1998-07-24', 'duration': '10140', 'lang': '英语', 'genre': '剧情', 'year': '1998', 'poster': 'https://wmdb.querydata.org/movie/poster/1611403093938-02aacc.jpg', 'country': '美国'}]} |
查询所有包含战争
或包含美国
的文档
1 | q = Query("战争|美国").language("chinese").highlight() |
结果:
1 | Result{37 total, docs: [Document {'id': 'movie:1292062', 'payload': None, 'rating': '8.5', 'votes': '279900', 'alias': '美丽有罪(港) / <b>美国</b>心·玫瑰情(台) / <b>美国</b>大美人 / <b>美国</b>美人 / <b>美国</b>少女 / 红蔷薇', 'description': '莱斯特(凯文·史派西 Kevin Spacey 饰)跟许多中年男人一样,遇到了各种各样的人生难题。他在一个广告公司工作,成绩平平,但是妻子却比他混得出色,一派女强人作风。这个平凡的男人还有一个未成年的...', 'genre': '剧情', 'duration': '7320', 'lang': '英语', 'name': '<b>美国</b>丽人', 'original_name': 'American Beauty', 'date_released': '1999-09-08', 'year': '1999', 'poster': 'https://wmdb.querydata.org/movie/poster/1607520913673-7gf31a.jpg', 'country': '美国'}, Document {'id': 'movie:1419005', 'payload': None, 'rating': '8.5', 'votes': '5842', 'alias': '阿尔及利亚的<b>战争</b> / The Battle of Algiers', 'description': '一九五四年十月一日,以法属阿尔及利亚首都阿尔及耳的卡斯巴为中心,爆发了阿尔及利亚人反抗运动。这是由于阿拉伯人憎恨法国人长期统治而引起的。人们四处搞破坏,法国政府发觉事态严重,便于一九五七年十月七日,派...', 'genre': '历史', 'duration': '7260', 'lang': '法语', 'name': '阿尔及尔之战', 'original_name': 'La battaglia di Algeri', 'date_released': '1966-09-08', 'year': '1966', 'poster': 'https://wmdb.querydata.org/movie/poster/1606734542298-9cge87.jpg', 'country': '意大利'}, Document {'id': 'movie:1296736', 'payload': None, 'rating': '9.2', 'votes': '409576', 'alias': '钢琴战曲(港) / 战地琴人(台) / 战地琴声 / 钢琴师', 'description': '史标曼(艾德里安•布洛迪 Adrien Brody 饰)是波兰一家电台的钢琴师。二战即将爆发之时,他们全家被迫被赶进华沙的犹太区。在<b>战争</b>的颠沛流离中,家人和亲戚最终被纳粹杀害,而标曼本人也受尽种种羞辱...', 'genre': '剧情', 'duration': '9000', 'lang': '英语', 'name': '钢琴家', 'original_name': 'The Pianist', 'date_released': '2002-05-24', 'year': '2002', 'poster': 'https://wmdb.querydata.org/movie/poster/1606347998445-205f46.jpg', 'country': '法国'}, Document {'id': 'movie:1422186', 'payload': None, 'rating': '8.6', 'votes': '6461', 'alias': '见证(台) / 炎628 / 过来瞧瞧 / 屠杀安魂曲 / 炙628 / Come And See / Idi i smotri', 'description': '这是一部很特殊的<b>战争</b>题材电影,它真实地描写了德占区人民的悲惨遭遇和场面,以及人们面对突如其来的灾难的恐惧,反映了<b>战争</b>的真实面目。它既不同于《斯大林格勒保卫战》、《攻占柏林》这些正面战场的血肉横飞、排山...', 'genre': '战争', 'duration': '8520', 'lang': '白俄罗斯语', 'name': '自己去看', 'original_name': 'Иди и смотри', 'date_released': '1985-10-17', 'year': '1985', 'poster': 'https://wmdb.querydata.org/movie/poster/1613161487535-5931f8.jpg', 'country': '苏联'}, ……]} |
查询所有包含战争
但不包含美国
的文档
1 | q = Query("战争 -美国").language("chinese").highlight() |
结果:
1 | Result{10 total, docs: [Document {'id': 'movie:1419005', 'payload': None, 'alias': '阿尔及利亚的<b>战争</b> / The Battle of Algiers', 'description': '一九五四年十月一日,以法属阿尔及利亚首都阿尔及耳的卡斯巴为中心,爆发了阿尔及利亚人反抗运动。这是由于阿拉伯人憎恨法国人长期统治而引起的。人们四处搞破坏,法国政府发觉事态严重,便于一九五七年十月七日,派...', 'votes': '5842', 'rating': '8.5', 'original_name': 'La battaglia di Algeri', 'name': '阿尔及尔之战', 'date_released': '1966-09-08', 'duration': '7260', 'lang': '法语', 'genre': '历史', 'year': '1966', 'poster': 'https://wmdb.querydata.org/movie/poster/1606734542298-9cge87.jpg', 'country': '意大利'}, Document {'id': 'movie:1422186', 'payload': None, 'alias': '见证(台) / 炎628 / 过来瞧瞧 / 屠杀安魂曲 / 炙628 / Come And See / Idi i smotri', 'description': '这是一部很特殊的<b>战争</b>题材电影,它真实地描写了德占区人民的悲惨遭遇和场面,以及人们面对突如其来的灾难的恐惧,反映了<b>战争</b>的真实面目。它既不同于《斯大林格勒保卫战》、《攻占柏林》这些正面战场的血肉横飞、排山...', 'votes': '6461', 'rating': '8.6', 'original_name': 'Иди и смотри', 'name': '自己去看', 'date_released': '1985-10-17', 'duration': '8520', 'lang': '白俄罗斯语', 'genre': '战争', 'year': '1985', 'poster': 'https://wmdb.querydata.org/movie/poster/1613161487535-5931f8.jpg', 'country': '苏联'}, Document {'id': 'movie:3592854', 'payload': None, 'alias': '末日先锋:战甲飞车(港) / 疯狂麦斯:愤怒道(台) / 冲锋飞车队4 / 迷雾追魂手4 / 冲锋追魂手4 / 疯狂麦克斯4 / 疯狂迈斯:怒途 / Mad Max 4: Fury Road', 'description': '未来世界,水资源短缺引发了连绵的<b>战争</b>。人们相互厮杀,争夺有限的资源,地球变成了血腥十足的杀戮死战场。面容恐怖的不死乔在戈壁山谷建立了难以撼动的强大武装王国,他手下的战郎驾驶装备尖端武器的战车四下抢掠,...', 'votes': '396919', 'rating': '8.6', 'original_name': 'Mad Max: Fury Road', 'name': '疯狂的麦克斯4:狂暴之路', 'date_released': '2015-05-14', 'duration': '7200', 'lang': '英语', 'genre': '冒险', 'year': '2015', 'poster': 'https://wmdb.querydata.org/movie/poster/1605981675337-a01c84.jpg', 'country': '澳大利亚'}, ……]} |
模糊搜索
前置匹配
1 | q = Query("*国").language("chinese").highlight().return_fields("alias", "name", "description") |
结果
1 | Result{54 total, docs: [Document {'id': 'movie:1309115', 'payload': None, 'alias': '希特拉的最后十二夜(港) / <b>帝国</b>毁灭(台) / <b>帝国</b>陷落 / Downfall / The Downfall: Hitler and the End of the Third Reich', 'name': '<b>帝国</b>的毁灭', 'description': '这是一部纪实性电影,逼真地反映了希特勒人生的最后12天,第三<b>帝国</b>最后的日子。苏联红军已经攻入柏林,希特勒(布鲁诺·甘茨 Bruno Ganz 饰)和情妇爱娃(茱莉安·柯勒 Juliane Köhle...'}, Document {'id': 'movie:1296528', 'payload': None, 'alias': '星球大战第五集:<b>帝国</b>反击战 / 星际大战五部曲:<b>帝国</b>大反击 / 星球大战5:<b>帝国</b>反击战', 'name': '星球大战2:<b>帝国</b>反击战', 'description': '维德勋爵(大卫•普劳斯 David Prowse 饰)把抵抗力量赶出基地,并派出数以千计的探针寻找卢克•天行者(马克•哈米尔 Mark Hamill 饰)。抵抗力量埋伏到冰天雪地的哈斯星,卢克在一次巡...'}, Document {'id': 'movie:1292969', 'payload': None, 'alias': '光荣之路', 'name': '光荣之路', 'description': '1916年,第一次世界大战期间的<b>法国</b>,德法两军的战争如火如荼。值此关键时刻,<b>法国</b>陆军将军布洛拉德(Adolphe Menjou 饰)向陆军上尉达克斯(柯克·道格拉斯 Kirk Douglas 饰)率领...'}, Document {'id': 'movie:1293663', 'payload': None, 'alias': 'Tengoku to jigoku / High and Low', 'name': '<b>天国</b>与地狱', 'description': '在权藤今吾(三船敏郎 Toshiro Mifune)家里,举行了一场关于民族鞋业的董事会议,讨论关于新鞋的质量问题。权藤发表的意见慷慨激昂,他觉得不应该只顾降低成本提高利润,而忽视了鞋子本身的质量。此...'}, Document {'id': 'movie:1294958', 'payload': None, 'alias': '桂河桥', 'name': '桂河大桥', 'description': '二战期间,日军占领了缅甸边境的一个战俘营。出于战略需要,日军将在缅甸与<b>泰国</b>交界修建一条大桥,同时希望战俘营里战俘出力,但英军战俘代表尼科森上校(亚利克·基尼斯 Alec Guinness 饰)认为这违...'}, Document {'id': 'movie:1292589', 'payload': None, 'alias': '纽伦堡大审判 / 劫后升平', 'name': '纽伦堡的审判', 'description': '讲述二战后在纽伦堡提审<b>德国</b>纳粹计划的法律关系者,三个被告提审的原因是给犹太人施行断种手术。担任主审判长的是美国人赫鲁特,他主张其中两个被告无罪;而<b>德国</b>司法部长亚林克竟对此事保持沉默,但检查官罗森上校却...'}, Document {'id': 'movie:1292260', 'payload': None, 'alias': '当代启示录', 'name': '现代启示录', 'description': '越战后期,美军上尉威拉德(马丁•辛 Martin Sheen 饰)奉命沿湄公河而上,搜寻脱离美军在柬埔寨建立了自己的<b>王国</b>的科茨(马龙•白兰度 Marlon Brando 饰)上校,将他带回或杀死。科茨...'}, Document {'id': 'movie:1292062', 'payload': None, 'alias': '美丽有罪(港) / <b>美国</b>心·玫瑰情(台) / <b>美国</b>大美人 / <b>美国</b>美人 / <b>美国</b>少女 / 红蔷薇', 'name': '<b>美国</b>丽人', 'description': '莱斯特(凯文·史派西 Kevin Spacey 饰)跟许多中年男人一样,遇到了各种各样的人生难题。他在一个广告公司工作,成绩平平,但是妻子却比他混得出色,一派女强人作风。这个平凡的男人还有一个未成年的...'}, Document {'id': 'movie:1303418', 'payload': None, 'alias': '生存还是毁灭 / 戏谍人生 / 扮嘢奇兵 / 生死攸关 / 嗨!我的元首 / 生或死 / 生死问题,是死?是活? / 生死大逃亡', 'name': '你逃我也逃', 'description': '波兰华沙有一个剧团,其中有一名演员Bronski(Tom Dugan 饰)极其擅长模仿<b>德国</b>纳粹领袖希特勒。Maria Tura(卡洛·朗白 Carole Lombard 饰)和Joseph Tura(...'}, Document {'id': 'movie:1300299', 'payload': None, 'alias': '谋杀回忆 / 杀手回忆录 / Salinui chueok / Memories of Murder', 'name': '杀人回忆', 'description': '1986年,<b>韩国</b>京畿道华城郡,热得发昏的夏天,在田野边发现一具女尸,早已发臭。小镇警察朴探员(宋康昊饰)和汉城来的苏探员(金相庆饰)接手案件,唯一可证实的是这具女尸生前被强奸过。线索的严重缺乏让毫无经...'}]} |
后置匹配
1 | q = Query("美*").language("chinese").highlight().return_fields("alias", "name", "description") |
结果
1 | Result{46 total, docs: [Document {'id': 'movie:1292260', 'payload': None, 'alias': '当代启示录', 'name': '现代启示录', 'description': '越战后期,<b>美军</b>上尉威拉德(马丁•辛 Martin Sheen 饰)奉命沿湄公河而上,搜寻脱离<b>美军</b>在柬埔寨建立了自己的王国的科茨(马龙•白兰度 Marlon Brando 饰)上校,将他带回或杀死。科茨...'}, Document {'id': 'movie:1306029', 'payload': None, 'alias': '有你终生<b>美丽</b>(港) / <b>美丽</b>境界(台) / 完美大脑', 'name': '<b>美丽</b>心灵', 'description': '本片是关于20世纪伟大数学家小约翰•福布斯-纳什的人物传记片。小约翰•福布斯-纳什(拉塞尔•克劳)在念研究生时,便发表了著名的博弈理论,该理论虽只有短短26页,却在经济、军事等领域产生了深远的影响。...'}, Document {'id': 'movie:1300055', 'payload': None, 'alias': '烈血焚城(港) / 金甲部队(台) / 金甲战士', 'name': '全金属外壳', 'description': '越战期间,<b>美军</b>大量征兵。大批年轻人应征入伍,在新兵营接受“残忍”的训练。“傻瓜”比尔运动神经不发达,常常犯错而连累所有人一起受罚。“小丑”(马修•莫迪恩 Matthew Modine 饰)奉命帮助比尔...'}, Document {'id': 'movie:1292270', 'payload': None, 'alias': '噩梦挽歌(台) / 迷上瘾(港) / 梦的挽歌', 'name': '梦之安魂曲', 'description': '哈瑞(杰瑞德·莱托 Jared Leto 饰)和玛丽安(詹妮弗·康纳利 Jennifer Connelly 饰)彼此相爱,梦想着开个服装店,梦想着有个<b>美好</b>的明天。然而他们都离不开毒品,并想着以销毒赚得...'}, Document {'id': 'movie:1297127', 'payload': None, 'alias': "华盛顿政客(港) / 华府风云(台) / 史密斯游<b>美</b>京 / 史密斯先生上<b>美</b>京 / 民主万岁 / Frank Capra's Mr. Smith Goes to Washington", 'name': '史密斯先生到华盛顿', 'description': '美国的一个小镇,Jefferson Smith(詹姆斯·斯图尔特 James Stewart饰)是当地的童子军的首领,深受青少年们的喜爱,被选为新的参议员,来到了华盛顿。遇到了他父亲的老朋友,同为参议...'}, Document {'id': 'movie:1299131', 'payload': None, 'alias': '教父续集(港) / 教父II', 'name': '教父2', 'description': '迈克尔(阿尔·帕西诺 饰)是<b>美利坚</b>黑手党科利昂家族的头目。 迈克尔的父亲维托·安多里尼(罗伯特·德尼罗 饰)出生于意大利科利昂镇。1901年,维托的父亲安东尼奥、兄长保罗、母亲(玛丽亚·卡塔 饰)都死...'}, Document {'id': 'movie:1294100', 'payload': None, 'alias': '我们生活的<b>美好</b>时代', 'name': '黄金时代', 'description': '故事发生在1945年,弗雷德(达纳·安德鲁斯 Dana Andrews 饰),艾尔(弗雷德里克·马奇 Fredric March 饰)和霍莫(哈罗德·拉塞尔 Harold Russell 饰)是三名刚...'}, Document {'id': 'movie:1294947', 'payload': None, 'alias': '龙虎榜(港) / 第三集中营(台) / 胜利大逃亡 / 绝处逢生 / 胜利逃亡', 'name': '大逃亡', 'description': '二战期间,德军的战俘营里,每个人都在渴望着自由。<b>美国人</b>希尔(史蒂夫•麦奎因 Steve McQueen 饰)在进入战俘营的第一天起,就一直计划着越狱。虽然他的十多次逃跑都以失败告终,但希尔从未放弃。这...'}, Document {'id': 'movie:1292065', 'payload': None, 'alias': '疤面人 / 疤脸人', 'name': '疤面煞星', 'description': '古巴难民青年托尼(阿尔•帕西诺 Al Pacino 饰)逃难来到了<b>美国</b>的迈阿密,成了一个典型的天不怕、地不怕的<b>美国</b>街头小混混。托尼在当地的一个毒枭手下干活,因其心狠手辣、胆大心细,十分出色地帮老大完成...'}, Document {'id': 'movie:1293749', 'payload': None, 'alias': "莫负少年头(港) / 风云人物(台) / 美满人生(澳) / <b>美好</b>人生 / 哀乐人生 / 美好生活 / Frank Capra's It's a Wonderful Life", 'name': '生活多美好', 'description': '乔治(詹姆斯•斯图尔特 James Stewart 饰)在圣诞节前准备自杀,这时上帝传来旨意,派天使拯救他,并让他了解到自己一生的使命——拯救那些不幸的人。乔治小时候左耳有疾,在贝德福德镇上的一家药店...'}]} |
模糊匹配
1 | q = Query("%美国%").language("chinese").highlight().return_fields("alias", "name", "description") |
结果:
1 | Result{68 total, docs: [Document {'id': 'movie:1292260', 'payload': None, 'alias': '当代启示录', 'name': '现代启示录', 'description': '越战后期,美军上尉威拉德(马丁•辛 Martin Sheen 饰)奉命沿湄公河而上,搜寻脱离美军在柬埔寨建立了自己的<b>王国</b>的科茨(马龙•白兰度 Marlon Brando 饰)上校,将他带回或杀死。科茨...'}, Document {'id': 'movie:1309115', 'payload': None, 'alias': '希特拉的最后十二夜(港) / <b>帝国</b>毁灭(台) / <b>帝国</b>陷落 / Downfall / The Downfall: Hitler and the End of the Third Reich', 'name': '<b>帝国</b>的毁灭', 'description': '这是一部纪实性电影,逼真地反映了希特勒人生的最后12天,第三<b>帝国</b>最后的日子。苏联红军已经攻入柏林,希特勒(布鲁诺·甘茨 Bruno Ganz 饰)和情妇爱娃(茱莉安·柯勒 Juliane Köhle...'}, Document {'id': 'movie:1296528', 'payload': None, 'alias': '星球大战第五集:<b>帝国</b>反击战 / 星际大战五部曲:<b>帝国</b>大反击 / 星球大战5:<b>帝国</b>反击战', 'name': '星球大战2:<b>帝国</b>反击战', 'description': '维德勋爵(大卫•普劳斯 David Prowse 饰)把抵抗力量赶出基地,并派出数以千计的探针寻找卢克•天行者(马克•哈米尔 Mark Hamill 饰)。抵抗力量埋伏到冰天雪地的哈斯星,卢克在一次巡...'}, Document {'id': 'movie:1306029', 'payload': None, 'alias': '有你终生<b>美丽</b>(港) / <b>美丽</b>境界(台) / 完美大脑', 'name': '<b>美丽</b>心灵', 'description': '本片是关于20世纪伟大数学家小约翰•福布斯-纳什的人物传记片。小约翰•福布斯-纳什(拉塞尔•克劳)在念研究生时,便发表了著名的博弈理论,该理论虽只有短短26页,却在经济、军事等领域产生了深远的影响。...'}, Document {'id': 'movie:1292969', 'payload': None, 'alias': '光荣之路', 'name': '光荣之路', 'description': '1916年,第一次世界大战期间的<b>法国</b>,德法两军的战争如火如荼。值此关键时刻,<b>法国</b>陆军将军布洛拉德(Adolphe Menjou 饰)向陆军上尉达克斯(柯克·道格拉斯 Kirk Douglas 饰)率领...'}, Document {'id': 'movie:1293663', 'payload': None, 'alias': 'Tengoku to jigoku / High and Low', 'name': '<b>天国</b>与地狱', 'description': '在权藤今吾(三船敏郎 Toshiro Mifune)家里,举行了一场关于民族鞋业的董事会议,讨论关于新鞋的质量问题。权藤发表的意见慷慨激昂,他觉得不应该只顾降低成本提高利润,而忽视了鞋子本身的质量。此...'}, Document {'id': 'movie:1294958', 'payload': None, 'alias': '桂河桥', 'name': '桂河大桥', 'description': '二战期间,日军占领了缅甸边境的一个战俘营。出于战略需要,日军将在缅甸与<b>泰国</b>交界修建一条大桥,同时希望战俘营里战俘出力,但英军战俘代表尼科森上校(亚利克·基尼斯 Alec Guinness 饰)认为这违...'}, Document {'id': 'movie:1300055', 'payload': None, 'alias': '烈血焚城(港) / 金甲部队(台) / 金甲战士', 'name': '全金属外壳', 'description': '越战期间,<b>美军</b>大量征兵。大批年轻人应征入伍,在新兵营接受“残忍”的训练。“傻瓜”比尔运动神经不发达,常常犯错而连累所有人一起受罚。“小丑”(马修•莫迪恩 Matthew Modine 饰)奉命帮助比尔...'}, Document {'id': 'movie:1292062', 'payload': None, 'alias': '<b>美丽</b>有罪(港) / 美国心·玫瑰情(台) / 美国大美人 / 美国美人 / 美国少女 / 红蔷薇', 'name': '美国丽人', 'description': '莱斯特(凯文·史派西 Kevin Spacey 饰)跟许多中年男人一样,遇到了各种各样的人生难题。他在一个广告公司工作,成绩平平,但是妻子却比他混得出色,一派女强人作风。这个平凡的男人还有一个未成年的...'}, Document {'id': 'movie:1292270', 'payload': None, 'alias': '噩梦挽歌(台) / 迷上瘾(港) / 梦的挽歌', 'name': '梦之安魂曲', 'description': '哈瑞(杰瑞德·莱托 Jared Leto 饰)和玛丽安(詹妮弗·康纳利 Jennifer Connelly 饰)彼此相爱,梦想着开个服装店,梦想着有个<b>美好</b>的明天。然而他们都离不开毒品,并想着以销毒赚得...'}]} |
模糊匹配是由很大限制的,他基于Levenshtein
距离(LD
)进行模糊匹配。术语的模糊匹配是通过在术语周围加“%”来实现的,模糊匹配的最大LD
为3,确切的说这只是一种相识度查询,并非一般意义上的模糊搜索,但是:
如果仔细观察会发现通过精确匹配时,不仅能够将完整value值查询出来,而且还查询出其他处于文档某个位置的key,请看以下例子:
1 | q = Query("美国").language("chinese").highlight().return_fields("name", "alias", "description").paging(0, 5) |
结果:
1 | Result{27 total, docs: [Document {'id': 'movie:1292062', 'payload': None, 'name': '<b>美国</b>丽人', 'alias': '美丽有罪(港) / <b>美国</b>心·玫瑰情(台) / <b>美国</b>大美人 / <b>美国</b>美人 / <b>美国</b>少女 / 红蔷薇', 'description': '莱斯特(凯文·史派西 Kevin Spacey 饰)跟许多中年男人一样,遇到了各种各样的人生难题。他在一个广告公司工作,成绩平平,但是妻子却比他混得出色,一派女强人作风。这个平凡的男人还有一个未成年的...'}, Document {'id': 'movie:1292065', 'payload': None, 'name': '疤面煞星', 'alias': '疤面人 / 疤脸人', 'description': '古巴难民青年托尼(阿尔•帕西诺 Al Pacino 饰)逃难来到了<b>美国</b>的迈阿密,成了一个典型的天不怕、地不怕的<b>美国</b>街头小混混。托尼在当地的一个毒枭手下干活,因其心狠手辣、胆大心细,十分出色地帮老大完成...'}, Document {'id': 'movie:1293527', 'payload': None, 'name': '<b>美国</b>X档案', 'alias': '野兽良民(港) / <b>美国</b>历史档案 / <b>美国</b>X历史', 'description': '德瑞克(爱德华•诺顿 Edward Norton 饰)的父亲在他很小的时候就被一名黑人毒贩射杀,从此给他幼小的心灵埋下了仇恨的种子。原来德瑞克功课很好,是老师眼中的好学生。但自从父亲遇难后,他就将一切...'}, Document {'id': 'movie:1292849', 'payload': None, 'name': '拯救大兵瑞恩', 'alias': '雷霆救兵(港) / 抢救雷恩大兵(台) / 拯救大兵雷恩', 'description': '瑞恩(马特•达蒙 Matt Damon饰 )是二战期间的<b>美国</b>伞兵,被困在了敌人后方。更不幸的是,他的三个兄弟全部在战争中死亡,如果他也遇难,家中的老母亲将无依无靠。<b>美国</b>作战总指挥部知道了这个情况,毅...'}, Document {'id': 'movie:1292262', 'payload': None, 'name': '<b>美国</b>往事', 'alias': '四海兄弟(台) / 义薄云天(港)', 'description': '1933年,纽约流氓Noodles(罗伯特·德·尼罗 饰)因向哈洛伦警司(布鲁斯·巴伦堡 饰)通风报信害死了三名同伙而被追杀。逃亡之前,他打开了存放帮派基金的手提箱,里面却只有报纸。 1968年,已改...'}]} |
之所以会出现这样的效果是因为redisearch
对文本进行了分词,其使用的工具是friso相比es的ik还是弱一些前者主要是对中文分词,体积小可移植性强。
字段查询
通过字段查询也可以实现模糊搜索,条件表达式可参考官网上给的sql
和 redisearch
的对照表
SQL 表达式 | RediSearch 等效表达式 | 注意 |
---|---|---|
WHERE x='foo' AND y='bar' | @x:foo @y:bar | 为减少歧义,建议使用 (@x:foo) (@y:bar) |
WHERE x='foo' AND y!='bar' | @x:foo -@y:bar | |
WHERE x='foo' OR y='bar' | (@x:foo)|(@y:bar) | |
WHERE x IN ('foo', 'bar','hello world') | @x:(foo|bar|"hello world") | 引号内表示确定(必须完全匹配)的短语 |
WHERE y='foo' AND x NOT IN ('foo','bar') | @y:foo (-@x:foo) (-@x:bar) | |
WHERE x NOT IN ('foo','bar') | -@x:(foo|bar) | |
WHERE num BETWEEN 10 AND 20 | @num:[10 20] | |
WHERE num >= 10 | @num:[10 +inf] | |
WHERE num > 10 | @num:[(10 +inf] | |
WHERE num < 10 | @num:[-inf (10] | |
WHERE num <= 10 | @num:[-inf 10] | |
WHERE num < 10 OR num > 20 | @num:[-inf (10] | @num:[(20 +inf] | |
WHERE name LIKE 'john%' | @name:john* |
标签过滤
主要过滤TagField
类型的字段
查询country为苏联或印度的电影
1 | q = Query("@country:{苏联|印度}").language("chinese").highlight().return_fields("name", "country",).paging(0, 15) |
结果:
1 | Result{7 total, docs: [Document {'id': 'movie:35652715', 'payload': None, 'name': '杰伊·比姆', 'country': '印度'}, Document {'id': 'movie:26387939', 'payload': None, 'name': '摔跤吧!爸爸', 'country': '印度'}, Document {'id': 'movie:3793023', 'payload': None, 'name': '三傻大闹宝莱坞', 'country': '印度'}, Document {'id': 'movie:1422186', 'payload': None, 'name': '自己去看', 'country': '苏联'}, Document {'id': 'movie:1306019', 'payload': None, 'name': '大地之歌', 'country': '印度'}, Document {'id': 'movie:1300034', 'payload': None, 'name': '德尔苏·乌扎拉', 'country': '苏联'}, Document {'id': 'movie:2363506', 'payload': None, 'name': '地球上的星星', 'country': '印度'}]} |
数字过滤
主要过滤NumericField
类型的字段
查询评分在9.4到9.5的电影(包含边界)
1 | q = Query("@rating:[9.4 9.5]").language("chinese").highlight().return_fields("name", "rating",).paging(0, 15).sort_by("rating") |
结果:
1 | Result{10 total, docs: [Document {'id': 'movie:1293182', 'payload': None, 'rating': '9.4', 'name': '十二怒汉'}, Document {'id': 'movie:3011091', 'payload': None, 'rating': '9.4', 'name': '忠犬八公的故事'}, Document {'id': 'movie:1291561', 'payload': None, 'rating': '9.4', 'name': '千与千寻'}, Document {'id': 'movie:1291567', 'payload': None, 'rating': '9.4', 'name': '千与千寻'}, Document {'id': 'movie:1295644', 'payload': None, 'rating': '9.4', 'name': '这个杀手不太冷'}, Document {'id': 'movie:1303408', 'payload': None, 'rating': '9.5', 'name': '福尔摩斯二世'}, Document {'id': 'movie:1295124', 'payload': None, 'rating': '9.5', 'name': '辛德勒的名单'}, Document {'id': 'movie:34961898', 'payload': None, 'rating': '9.5', 'name': '汉密尔顿'}, Document {'id': 'movie:1292063', 'payload': None, 'rating': '9.5', 'name': '美丽人生'}, Document {'id': 'movie:1292720', 'payload': None, 'rating': '9.5', 'name': '阿甘正传'}]} |
查询评分大于9.5且小于9.6的电影(不包含边界)
1 | q = Query("@rating:[(9.4 (9.6]").language("chinese").highlight().return_fields("name", "rating",).paging(0, 15).sort_by("rating") |
结果
1 | Result{5 total, docs: [Document {'id': 'movie:1303408', 'payload': None, 'rating': '9.5', 'name': '福尔摩斯二世'}, Document {'id': 'movie:1295124', 'payload': None, 'rating': '9.5', 'name': '辛德勒的名单'}, Document {'id': 'movie:34961898', 'payload': None, 'rating': '9.5', 'name': '汉密尔顿'}, Document {'id': 'movie:1292063', 'payload': None, 'rating': '9.5', 'name': '美丽人生'}, Document {'id': 'movie:1292720', 'payload': None, 'rating': '9.5', 'name': '阿甘正传'}]} |
查询评分大于等于9.6分的电影
1 | q = Query("@rating:[9.6 inf]").language("chinese").highlight().return_fields("name", "rating", ).paging(0,15).sort_by( |
结果:
1 | Result{2 total, docs: [Document {'id': 'movie:1296141', 'payload': None, 'rating': '9.6', 'name': '控方证人'}, Document {'id': 'movie:1292052', 'payload': None, 'rating': '9.7', 'name': '肖申克的救赎'}]} |
查询评分小于等于7.5的电影
1 | q = Query("@rating:[-inf 7.5]").language("chinese").highlight().return_fields("name", "rating", ).paging(0,15).sort_by( |
结果:
1 | Result{2 total, docs: [Document {'id': 'movie:6893932', 'payload': None, 'rating': '0', 'name': '壮志凌云2:独行侠'}, Document {'id': 'movie:1315316', 'payload': None, 'rating': '7.3', 'name': '无间道风云'}]} |
查询评分小于等于7.5或大于等于9.6的电影
1 | # q = Query("(@rating:[-inf 7.5])|@rating:[9.6 inf]").language("chinese").highlight().return_fields("name", "rating", ).paging(0,15).sort_by("rating") |
结果:
1 | Result{4 total, docs: [Document {'id': 'movie:6893932', 'payload': None, 'rating': '0', 'name': '壮志凌云2:独行侠'}, Document {'id': 'movie:1315316', 'payload': None, 'rating': '7.3', 'name': '无间道风云'}, Document {'id': 'movie:1296141', 'payload': None, 'rating': '9.6', 'name': '控方证人'}, Document {'id': 'movie:1292052', 'payload': None, 'rating': '9.7', 'name': '肖申克的救赎'}]} |
查询评分为9.5的电影
1 | from redisearch import NumericFilter |
结果:
1 | Result{5 total, docs: [Document {'id': 'movie:34961898', 'payload': None, 'name': '汉密尔顿', 'rating': '9.5'}, Document {'id': 'movie:1292720', 'payload': None, 'name': '阿甘正传', 'rating': '9.5'}, Document {'id': 'movie:1303408', 'payload': None, 'name': '福尔摩斯二世', 'rating': '9.5'}, Document {'id': 'movie:1292063', 'payload': None, 'name': '美丽人生', 'rating': '9.5'}, Document {'id': 'movie:1295124', 'payload': None, 'name': '辛德勒的名单', 'rating': '9.5'}]} |
多字段类型混合查询
查询简介中包含战争,但不包含美国,且国家为法国或日本的电影
1 | q = Query("(@description:(战争 -美国)) (@country:{法国|日本})").language("chinese").highlight().return_fields("name", "description", "country").paging(0, 10) |
结果:
1 | Result{2 total, docs: [Document {'id': 'movie:1293318', 'payload': None, 'name': '萤火虫之墓', 'description': '美日<b>战争</b>爆发,14岁的清太带着年幼的妹妹到处逃命,当他们到达防空洞的时候,母亲已身受重伤,没过多久便不久人世。两兄妹自此过着相依为命的日子。他们只好投靠了母亲的姐妹,纵使他们把家里所有的家当都送给了阿...', 'country': '日本'}, Document {'id': 'movie:1296736', 'payload': None, 'name': '钢琴家', 'description': '史标曼(艾德里安•布洛迪 Adrien Brody 饰)是波兰一家电台的钢琴师。二战即将爆发之时,他们全家被迫被赶进华沙的犹太区。在<b>战争</b>的颠沛流离中,家人和亲戚最终被纳粹杀害,而标曼本人也受尽种种羞辱...', 'country': '法国'}]} |
分组聚合查询
要进行聚合查询,需要借助类的实例传递AggregateRequest
给Client
的实例的方法search()
。
查询各种类型的对应的电影数量
RediSearch
命令行: FT.AGGREGATE idx:movie * GROUPBY 1 @genre REDUCE COUNT 0 AS genre_nums SORTBY 2 @genre_nums DESC LIMIT 0 5
1 | from redisearch.aggregation import Desc |
结果:
1 | [['genre', '剧情', 'genre_nums', '79'], |
Reducer
从示例中注意,我们使用了模块中的对象reducers
。 有关在对结果进行分组时可以使用的reducers函数的更多示例,请参阅官方RediSearch 文档。
Reducer 函数包括一个alias()
为 reducer 的结果指定特定名称的方法。如果您不提供名称,RediSearch
将生成一个。
1 | request = AggregateRequest("*").group_by("@genre", reducers.count()).limit(0, 5) |
结果:
1 | [['genre', '犯罪', '__generated_aliascount', '24'], |
按零个、一个或多个字段分组
该group_by
语句可以将零个、单个字段名称作为字符串,或将多个字段名称作为字符串列表。
查询所有电影投票数之和(即我们mysql
里的只聚合不分组)
1 | request = AggregateRequest("*").group_by([], reducers.sum("@votes").alias("votes_sums")) |
结果:
1 | [['votes_sums', '77890535']] |
查询每个国家每个年份电影数
等价RediSearch
命令行:FT.AGGREGATE idx:movie * GROUPBY 2 @country @year REDUCE COUNT 0 AS sums SORTBY 4 @sums DESC @year DESC LIMIT 0 10
1 | request = AggregateRequest("*").group_by(["@country", "@year"], reducers.count().alias("sums")).sort_by(Desc("@sums"), Desc("@year")).limit(0, 10) |
结果:
1 | [['country', '美国', 'year', '1999', 'sums', '6'], |
查询每个国家每个年份电影数之后,获取最多的电影数
等价RediSearch
命令行:FT.AGGREGATE idx:movie * GROUPBY 2 @country @year REDUCE COUNT 0 AS sums GROUPBY 0 REDUCE MAX 1 @sums AS country_year_max_nums
1 | request = AggregateRequest("*").group_by(["@country", "@year"], reducers.count().alias("sums")).group_by([], reducers.max("@sums").alias("country_year_max_nums")) |
结果:
1 | [['country_year_max_nums', '6']] |
聚合结果对象
聚合查询返回一个AggregateResult
对象,该对象包含为查询返回的行和一个游标(如果您使用的是游标 API)。
filter
在 reducer 函数运行后,使用filter进一步聚合查询的结果(听起来类似sql
中的having
)。例如,计算每年上映的电影数量,只返回上映数量高于 5 的年份:
等价RediSearch
命令行:FT.AGGREGATE idx:movie * GROUPBY 1 @year REDUCE COUNT 0 AS year_nums FILTER "@year_nums>5"
1 | request = AggregateRequest("*").group_by("@year", reducers.count().alias("year_nums")).filter("@year_nums>5") |
结果:
1 | [['year', '2004', 'year_nums', '7'], |
参考文献:
https://redis.io/docs/stack/search/
https://github.com/RediSearch/redisearch-py