使用的"teamtnt/laravel-scout-tntsearch-driver": "^8.1"版本。
按照参考文章TNTSearch - PHP 实现的全文索引引擎,已经完成了英文分词的搜索。中文就需要jieba-php,但苦于tntsearch的驱动已经跟进laravel7,但jieba并没有跟进,致使我参考TNTSearch 轻量级全文索引+中文分词一直报错,报vendor\fukuball\jieba-php\src\class\Jieba.php:265的index "HMM" 不存在。
查看信息栈发现,TNTIndexer.php调用jieba的tokenize(),传的参数对不上。
//vendor\teamtnt\tntsearch\src\Indexer\TNTIndexer.php:447
public function breakIntoTokens($text)
{
if ($this->decodeHTMLEntities) {
$text = html_entity_decode($text);
}
return $this->tokenizer->tokenize($text, $this->stopWords);
}
//vendor\fukuball\jieba-php\src\class\Jieba.php:263
public static function tokenize($sentence, $options = array("HMM" => true))
{
$seg_list = self::cut($sentence, false, array("HMM" => $options["HMM"]));
$tokenize_list = [];
$start = 0;
$end = 0;
foreach ($seg_list as $seg) {
$end = $start+mb_strlen($seg, 'UTF-8');
$tokenize = [
'word' => $seg,
'start' => $start,
'end' => $end
];
$start = $end;
$tokenize_list[] = $tokenize;
}
return $tokenize_list;
}
jiebe-php的tokenize()第二参数需要的是HMM算法是否使用,而TNTIndexer调用传入的第二参数是stopwords,空数组也是个正经变量,致使默认参数的HMM这个index没了,jieba就报错。
按 TNTSearch 轻量级全文索引 + 中文分词 里的是新建了 TokenizerHandler.php,但config/app.php里的配置可以依旧TeamTNT\Scout\TNTSearchScoutServiceProvider::class,并不需要改。新建的ScoutServiceProvider.php对于我来说是多余的,新建的 TokenizerHandler 继承了 TokenizerInterface,实际相当于 vendor\teamtnt\tntsearch\src\Support\Tokenizer.php
<?php
namespace TeamTNT\TNTSearch\Support;
class Tokenizer implements TokenizerInterface
{
public function tokenize($text, $stopwords = [])
{
$text = mb_strtolower($text);
$split = preg_split("/[^\p{L}\p{N}]+/u", $text, -1, PREG_SPLIT_NO_EMPTY);
return array_diff($split, $stopwords);
}
}
//vendor\teamtnt\tntsearch\src\Support\Tokenizer.php
而我们只需要更换tntsearch里的tokenizer成TokenizerHandler就能使用。
config\scout.php的tntsearch里tokenizer按新的tntsearch换成了只需填写class就可以,而不是数组。
//config\scout.php
'tntsearch' => [
'storage' => storage_path(),
'fuzziness' => env('TNTSEARCH_FUZZINESS', false),
'fuzzy' => [
'prefix_length' => 2,
'max_expansions' => 50,
'distance' => 2
],
'tokenizer' => App\Handlers\TokenizerHandler::class,
'asYouType' => false,
'searchBoolean' => env('TNTSEARCH_BOOLEAN', false),
],
最后TokenizerHandler.php内容如下
<?php
namespace App\Handlers;
use Fukuball\Jieba\Jieba;
use Fukuball\Jieba\Finalseg;
use TeamTNT\TNTSearch\Support\TokenizerInterface;
class TokenizerHandler implements TokenizerInterface
{
public function __construct(array $options = ['dict' => 'small'])
{
Jieba::init($options);
Finalseg::init($options);
}
public function tokenize($text, $stopwords = [])
{
if(is_numeric($text)){
return [];
}
if (preg_match("/[\x{4e00}-\x{9fff}]+/u", $text)) {
//含中文
return $this->getTokens($text, $stopwords);
} else {
return array_diff(preg_split("/[^\p{L}\p{N}]+/u", $text, -1, PREG_SPLIT_NO_EMPTY),$stopwords);
}
}
public function getTokens($text, $stopwords = [])
{
if(empty($stopwords)){
$stopwords=$this->getStopWords();
}
$text = $this->replaceWords($text,$stopwords);
$split = Jieba::cutForSearch($text);
return $split;
}
public function getStopWords()
{
$stopwords = [];
$fp = fopen(base_path('vendor/fukuball/jieba-php/src/dict/stop_words.txt'), 'rb');
if ($fp) {
while (!feof($fp)){
array_push($stopwords, str_replace("\n", "", fgets($fp)));
}
fclose($fp);
}
return array_merge($stopwords,[',','。',' ','br']);
}
public function replaceWords($doc, $stopwords = [], $replace = ' ')
{
return strtr($doc,array_combine($stopwords, array_fill(0, count($stopwords), $replace)));
}
}
自带的tokenizer可以切很多语言,我测试的除英语,韩,泰,希腊语都可以分,所以保留下来很有必要。而日语含有中文字所以用jieba切,但没切成功。
这样改动使得php artisan tntsearch:import App\***会此上的saveWordlist错误,使用scout没报错,项目里正常使用无报错,原本项目就是用scout管理索引,我就没修复这个tntsearch:import问题。如有解决访问的请告知我,谢谢。
php artisan scout:import "App\****"
自己这两星期才实践型基于laravel写个东西,tntsearch也是刚接触两三天就用的,所以有很多不理解,刚开没理解tokenizer,jieba加进去也头疼了一天。
按我现在的理解,tokenizer原意为分词器,而jieba就是分词器,傻傻我还以为jieba只是配合tntsearch的组件而,实际jieba也可以单独使用,从这个我才看到HMM算法。之后关键字过滤可以使用,虽说不是智能,但也比自己一篇文本对比关键词强。
而tntsearch是为生成、搜索索引而用。而最开始我想使用elasticsearch,但它需要另起服务,还是java的,就感觉很费性能,而tntsearch使用sqlite存储索引,TNTIndexer对应的那些***.index文件实际是sqlite数据库文件,可以用sqlite方式参看,可以查看wordlist表,得知分词结果。而我项目本身用的就是sqlite,更不用另起服务,更节省资源。