Manticore Search 3.1.0 版引入了一种基于ICU 文本分割算法的中文文本分割新方法,该算法遵循第二种方法 - 基于字典的分割。
ICU 是一组开源库,为软件应用程序提供 Unicode 和全球化支持。与许多其他功能一起,它解决了文本边界确定的任务。 ICU 算法在文本范围内定位单词、句子、段落的位置,或者在显示文本时识别适合换行的位置。
本文截稿时,在 Manticore 的讨论区很多人反馈 ICU 似乎没有 jieba 分词器对中文分词处理的更好,Manticore作者也已经将 jieba 分词器的集成纳入后续的支持计划。
Manticore 中 ICU 分词的算法可以简单描述如下:
-
原始文本被视为符号数组。
-
然后 Manticore 遍历数组,如果找到一组 * 中文符号,它会将其传递给 ICU 库进行处理。
-
中文文本的分段部分替换了原始的未分段部分。
-
其他自然语言处理算法(charset_table、wordforms等)适用于修改后的文本,就像在常见的分割工作流程中一样。
要启用 ICU-Chinese 分词,必须在创建表时添加以下索引配置选项:
-
morphology = ‘icu_chinese’
-
charset_table = ‘chinese’ 或者 charset_table = ‘non_cjk,chinese’
当一篇文章包含中文和英文时可以考虑设置 charset_table = ‘non_cjk,chinese’
示例步骤如下
1、连接到 manticore
mysql -P 9306 -h0
2、创建表
# 1、创建表
mysql> CREATE TABLE test_record (form_id bigint, form_data json, author string attribute indexed, content text) charset_table = 'chinese' morphology = 'icu_chinese';
# 2、查看表结构
mysql> desc test_record;
+-----------+--------+-------------------+
| Field | Type | Properties |
+-----------+--------+-------------------+
| id | bigint | |
| author | string | indexed attribute |
| content | text | indexed stored |
| form_id | bigint | |
| form_data | json | |
+-----------+--------+-------------------+
中英文混合时候参考选用
charset_table = 'non_cjk,chinese'
更多字段类型详见官方文档,地址:https://manual.manticoresearch.com/Creating_a_table/Data_types#Character-data-types
3、插入测试数据
mysql> INSERT INTO test_record (form_id, form_data, author, content) VALUES ( 226810975896997888, '{"number_1600348358244":"23347","input_1600348411983":"","input_1600348225548":"6224439990006413028","time_1600348183508":"14:36:46","input_1600348282096":"昆山市人民法院","date_1600348389097":"2022-01-14","textarea_1600348453979":"","input_1600348238915":"GADJ00020211182885","input_1600348211548":"","date_1600348174740":"2021-01-14","input_1600348428388":"","radio_1600348126943":"1","input_1600348336862":"冻结"}', '张三', '清华大学' );
mysql> INSERT INTO test_record (form_id, form_data, author, content) VALUES ( 226810975896997888, '{"date_1600345666309":"2017-08-16","input_1610335861888":"3052261","input_1600345921986":"孙善广","textarea_1600345727801":"撤销","input_1602568493757":"昆山德立亚企业管理有限公司","input_1600345853347":"1673","input_1600345648905":"274552978554269699","input_1600345839985":"3052261012019000001673","input_1600345935496":"15298824131"}', '李四', '一位牙医和一只狗在中国古代杀死一个疯子的惊人书信' );
mysql> INSERT INTO test_record (form_id, form_data, author, content) VALUES ( 226810975896997888, '{"number_1604892340550":"20","input_1604891602279":"2005","input_1604891603314":"王海燕","date_1604891845251":"2021-01-14","input_1604891608805":"蒋琮(2222)","input_1604891603984":"371311198305043420","input_1604891608483":"B1","date_1604891858785":"2021-01-14","input_1604891608176":"HR65528845","input_1604891599203":"A0020","number_1604892324841":"1"}', '李佳', '一个相扑摔跤手和一个必须在Baloon追捕法医心理学家的猎人的难以置信的插曲' );
mysql> INSERT INTO test_record (form_id, form_data, author, content) VALUES ( 269501011993878528, '{"date_1600345666309":"2019-11-14","input_1610335861888":"3052261","input_1600345921986":"郭云磊","textarea_1600345727801":"撤销","input_1602568493757":"昆山市康跃可喜安医疗器械有限公司","input_1600345853347":"2145","input_1600345648905":"274552905254612995","input_1600345839985":"3052261012017000002145","input_1600345935496":"13776098096"}', '小单', '买新的Apple电脑' );
4、分词测试
# 使用 call keywords,第一个参数为内容,第二个参数为表名
mysql> call keywords ('买新的Apple电脑', 'test_record');
+------+-----------+------------+
| qpos | tokenized | normalized |
+------+-----------+------------+
| 1 | 买 | 买 |
| 2 | 新的 | 新的 |
| 3 | 电脑 | 电脑 |
+------+-----------+------------+
mysql> call keywords ('一位牙医和一只狗在中国古代杀死一个疯子的惊人书信', 'test_record');
+------+-----------+------------+
| qpos | tokenized | normalized |
+------+-----------+------------+
| 1 | 一位 | 一位 |
| 2 | 牙医 | 牙医 |
| 3 | 和 | 和 |
| 4 | 一只 | 一只 |
| 5 | 狗 | 狗 |
| 6 | 在 | 在 |
| 7 | 中国 | 中国 |
| 8 | 古代 | 古代 |
| 9 | 杀死 | 杀死 |
| 10 | 一个 | 一个 |
| 11 | 疯子 | 疯子 |
| 12 | 的 | 的 |
| 13 | 惊人 | 惊人 |
| 14 | 书信 | 书信 |
+------+-----------+------------+
mysql> call keywords ('清华大学', 'test_record');
+------+--------------+--------------+
| qpos | tokenized | normalized |
+------+--------------+--------------+
| 1 | 清华大学 | 清华大学 |
+------+--------------+--------------+
注:从测试
买新的Apple电脑
分词结果我们可以看到,Apple
消失了,那是因为我们设定的charset_table
只是chinese
,如果你希望中文和英文都被分词,则你需要设置charset_table = 'non_cjk,chinese'
,可以自行测试验证。
5、查询数据
5.1、普通字段查询
mysql> select id, form_id, author from test_record where form_id = 226810975896997888;
+---------------------+--------------------+--------+
| id | form_id | author |
+---------------------+--------------------+--------+
| 3461355430494601233 | 226810975896997888 | 李四 |
| 3461355430494601234 | 226810975896997888 | 李佳 |
| 3461355430494601232 | 226810975896997888 | 张三 |
+---------------------+--------------------+--------+
5.2、对 string 字段进行正则查询
mysql> select id, form_id, author from test_record where form_id = 226810975896997888 and regex(author, '李.*');
+---------------------+--------------------+--------+
| id | form_id | author |
+---------------------+--------------------+--------+
| 3461355430494601234 | 226810975896997888 | 李佳 |
| 3461355430494601233 | 226810975896997888 | 李四 |
+---------------------+--------------------+--------+
注:如官方文档中对 REGEX函数的描述
The REGEX(attr,expr) function returns 1 if a regular expression matches the attribute's string, and 0 otherwise. It works with both string and JSON attributes.
所示,regex 目前只能用于 string 和 json 类型的字段。
5.3、对索引字段使用 match 进行搜索
只有被索引的字段才可以使用 match
进行搜索,text
类型的字段默认为 stored + indexed
,string
字段默认为 attribute
,但是我们可以手工为 string 字段设置 indexed
属性使之也被索引,这样 string 也可以使用 match 匹配搜索。
5.3.1、从所有索引字段搜索
# match 不指定具体字段的时,会从所有支持索引的字段中去匹配(一般包含明确指定indexed的string字段和text字段)
mysql> select id, form_id, author, content from test_record where match('牙医');
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
| id | form_id | author | content |
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
| 3461355430494601233 | 226810975896997888 | 李四 | 一位牙医和一只狗在中国古代杀死一个疯子的惊人书信 |
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
mysql> select id, form_id, author, content from test_record where match('李');
+---------------------+--------------------+--------+--------------------------------------------------------------------------------------------------------+
| id | form_id | author | content |
+---------------------+--------------------+--------+--------------------------------------------------------------------------------------------------------+
| 3461355430494601234 | 226810975896997888 | 李佳 | 一个相扑摔跤手和一个必须在Baloon追捕法医心理学家的猎人的难以置信的插曲 |
| 3461355430494601233 | 226810975896997888 | 李四 | 一位牙医和一只狗在中国古代杀死一个疯子的惊人书信 |
+---------------------+--------------------+--------+--------------------------------------------------------------------------------------------------------+
mysql> select id, form_id, author, content from test_record where match('小单');
+---------------------+--------------------+--------+----------------------+
| id | form_id | author | content |
+---------------------+--------------------+--------+----------------------+
| 3461355430494601235 | 269501011993878528 | 小单 | 买新的Apple电脑 |
+---------------------+--------------------+--------+----------------------+
mysql> select id, form_id, author, content from test_record where match('清华');
mysql> select id, form_id, author, content from test_record where match('清华大学');
+---------------------+--------------------+--------+--------------+
| id | form_id | author | content |
+---------------------+--------------------+--------+--------------+
| 3461355430494601232 | 226810975896997888 | 张三 | 清华大学 |
+---------------------+--------------------+--------+--------------+
5.3.2、从明确指定的索引字段搜索
# 语法:match('@field keyworld') 或者 match('@(field1,field2,...) keyworld')
mysql> select id, form_id, author, content from test_record where match('@author 牙医');
mysql> select id, form_id, author, content from test_record where match('@(content) 牙医');
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
| id | form_id | author | content |
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
| 3461355430494601233 | 226810975896997888 | 李四 | 一位牙医和一只狗在中国古代杀死一个疯子的惊人书信 |
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
# match 指定从多个字段中匹配
mysql> select id, form_id, author, content from test_record where match('@(content,author) 张三');
+---------------------+--------------------+--------+--------------+
| id | form_id | author | content |
+---------------------+--------------------+--------+--------------+
| 3461355430494601232 | 226810975896997888 | 张三 | 清华大学 |
+---------------------+--------------------+--------+--------------+
5.3.3、同时匹配多个关键字
mysql> select id, form_id, author, content from test_record where match('@(content,author) 张三|牙医');
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
| id | form_id | author | content |
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
| 3461355430494601233 | 226810975896997888 | 李四 | 一位牙医和一只狗在中国古代杀死一个疯子的惊人书信 |
| 3461355430494601232 | 226810975896997888 | 张三 | 清华大学 |
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
5.3.4、多个字段并且关系搜索
# 检索contnet包含“一个”并且author包含“李四”的内容
mysql> select id, form_id, author, content from test_record where match('@content 一个 @author 李四');
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
| id | form_id | author | content |
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
| 3461355430494601233 | 226810975896997888 | 李四 | 一位牙医和一只狗在中国古代杀死一个疯子的惊人书信 |
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
更多关于全文索引的查询语法,详见官方文档中的 Full text operators 章节。
5.3.4、对 json 字段进行查询
# 查询json字段中某一个属性大于指定值的(数字类型比较轻注意正确选用 INTEGER、DOUBLE)
mysql> SELECT * FROM test_record WHERE INTEGER(form_data.input_1600345853347) > 1673;
# 对json中的具体字段使用精确查询
mysql> SELECT * FROM test_record WHERE form_data.input_1600345921986 = '郭云磊';
# 对json中的字段使用正则匹配查询
mysql> SELECT * FROM test_record WHERE regex(form_data.input_1600345921986, '孙.*');
官方提供了JSON字段查询的仿真教学环境,地址为 https://play.manticoresearch.com/json/,更多内容请探索官方手册。
5.4、查看查询的扩展信息
每执行一个查询后,都可以使用 SHOW META
查看执行过的查询的扩展信息,如下所示:
mysql> select id, form_id, author, content from test_record where match('@(content,author) 张三|牙医');
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
| id | form_id | author | content |
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
| 3461355430494601233 | 226810975896997888 | 李四 | 一位牙医和一只狗在中国古代杀死一个疯子的惊人书信 |
| 3461355430494601232 | 226810975896997888 | 张三 | 清华大学 |
+---------------------+--------------------+--------+--------------------------------------------------------------------------+
mysql> SHOW META;
+----------------+--------+
| Variable_name | Value |
+----------------+--------+
| total | 2 |
| total_found | 2 |
| total_relation | eq |
| time | 0.000 |
| keyword[0] | 张三 |
| docs[0] | 1 |
| hits[0] | 1 |
| keyword[1] | 牙医 |
| docs[1] | 1 |
| hits[1] | 1 |
+----------------+--------+
总结
实现中文全文搜索看似通过中间件很容易,让分词结果达到自己实际业务期望度并非易事。虽然 Manticore Search 使用 ICU 已经可以轻松的应付大部分中文场景。但是当你深度使用后,可能会发现它对很多自定义词组和灵活组合变幻的中文的支持度仍然达不到我们的预期,对分词的扩展支持目前来说还不够。
不过如文初所述,作者已经计划集成 jieba
分词器(issue),相信在不久的将来支持 jieba 后我们能更灵活的自定义中文词库,来尽可能更准确的满足我们对中文分词的实际业务需求。
中文分词的官方互动教程地址:https://play.manticoresearch.com/icu-chinese/
(END)