近期還挺常遇到客戶想要他的搜尋 bar 像 Google 那麼強大,可以多關鍵字查找網站內容,但要達到那樣的要求在中文是很難的,中文有語意以及斷字問題,英文每個字都會分開寫,中文是全部連在一起,所以要達成這樣的功能必須使用中文斷詞的套件,再餵給 solr 或 elasticsearch 那種全文檢索引擎,但客戶的 content 不算龐大,安裝 solr 或 es 並且調校要花不少的成本,幸好 MySQL 在 5.6.4 之後已經支援全文檢索了,以下示範使用方式
斷詞套件
首先是要選擇斷詞套件,目前網路最熱門的應該就屬於 jieba 了,這個套件有 PHP 的版本,使用 composer 安裝好以後就可以寫程式碼了
資料表
不過要使用功能之前當然要設定我們的 MySQL,先建立表單
CREATE TABLE `cuts` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `content` TEXT NULL COLLATE 'utf8mb4_unicode_ci', `full_text` TEXT NULL COLLATE 'utf8mb4_unicode_ci', PRIMARY KEY (`id`), FULLTEXT INDEX `full_text` (`full_text`) ) COLLATE='utf8mb4_unicode_ci' ENGINE=InnoDB;
content 部份是要存原來的資料,而 full_text 的欄位是要存斷詞之後的結果
斷詞
<?php
ini_set('memory_limit', '1024M');
include 'vendor/autoload.php';
use Fukuball\Jieba\Jieba;
use Fukuball\Jieba\Finalseg;
Jieba::init();
Finalseg::init();
$content = '我工作於掌中乾坤,預計今年去看 NBA,去看劉寶傑打球';
$cuts = Jieba::cut($content);
印出來的結果為
Array
(
[0] => 我
[1] => 工作
[2] => 於
[3] => 掌中
[4] => 乾坤
[5] => ,
[6] => 預計
[7] => 今年
[8] => 去
[9] => 看
[10] => NBA
[11] => ,
[12] => 去
[13] => 看
[14] => 劉寶傑
[15] => 打球
)
看起來斷的挺精準的,不過我的公司叫掌中乾坤,如果打這四個字會搜尋不到,我們可以用自己定義的詞庫
<?php
ini_set('memory_limit', '1024M');
include 'vendor/autoload.php';
use Fukuball\Jieba\Jieba;
use Fukuball\Jieba\Finalseg;
Jieba::init();
Jieba::loadUserDict('user.txt');
Finalseg::init();
$content = '我工作於掌中乾坤,預計今年去看 NBA,去看劉寶傑打球';
$cuts = Jieba::cut($content);
print_r($cuts);
會印出
Array
(
[0] => 我
[1] => 工作
[2] => 於
[3] => 掌中乾坤
[4] => ,
[5] => 預計
[6] => 今年
[7] => 去
[8] => 看
[9] => NBA
[10] => ,
[11] => 去
[12] => 看
[13] => 劉寶傑
[14] => 打球
)
有一好沒兩好,乾坤這個詞洗掉了,可以修改斷詞為全模式
$cuts = Jieba::cut($content, true);
會印出
Array
(
[0] => 我
[1] => 工作
[2] => 於
[3] => 掌中乾坤
[4] => 乾坤
[5] => ,
[6] => 預
[7] => 今年
[8] => 去
[9] => 看
[10] => N
[11] => B
[12] => A
[13] => ,
[14] => 去
[15] => 看
[16] => 寶
[17] => 傑
[18] => 打球
)
看起來反而對精準度有所影響,看自己怎麼取捨,接著我們把這些內容轉成 base64 存到資料庫,因為 MySQL 的全文檢索不認中文…
<?php
ini_set('memory_limit', '1024M');
include 'vendor/autoload.php';
use Fukuball\Jieba\Jieba;
use Fukuball\Jieba\Finalseg;
Jieba::init();
Jieba::loadUserDict('user.txt');
Finalseg::init();
$content = '我工作於掌中乾坤,預計今年去看 NBA,去看劉寶傑打球';
$cuts = Jieba::cut($content);
$cutStrings = implode(' ', array_map('base64_encode', $cuts));
try {
$db = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', 123456);
$sql = "INSERT INTO `cuts`(content, full_text) VALUES(:content, :full_text)";
$sth = $db->prepare($sql);
$sth->bindParam(':content', $content, \PDO::PARAM_STR);
$sth->bindParam(':full_text', $cutStrings, \PDO::PARAM_STR);
$sth->execute();
} catch (Exception $e) {
var_dump($e->getMessage());
}
搜尋的模擬程式碼如下:
<?php
$searchKey = '找 掌中乾坤';
$keys = explode(' ', $searchKey);
$trim = function($item) {
return $item !== '';
};
$transkey = implode(' ', array_map('base64_encode', array_filter($keys, $trim)));
try {
$db = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', 123456);
$sql = "SELECT * FROM `cuts` WHERE MATCH(full_text) AGAINST(:full_text IN BOOLEAN MODE)";
$sth = $db->prepare($sql);
$sth->bindParam(':full_text', $transkey, \PDO::PARAM_STR);
$sth->execute();
$rows = $sth->fetchAll(\PDO::FETCH_ASSOC);
var_dump($rows);
} catch (Exception $e) {
var_dump($e->getMessage());
}
這樣就可以找到正確的結果了,不過 jieba 套件在 PHP 執行上算慢的,同樣的環境 PHP 7 要跑 2 秒,Python 版本是 0.2 秒…怕 PHP 存檔有延遲的人可以送 queue 後處理,或者是用排程跑 python 去搞定這件事情,也有大神寫了 extension 去呼叫 cpp 的版本,或者兩秒可以忍受的話就牙一咬吧 XDDDD
沒有留言:
張貼留言