近期還挺常遇到客戶想要他的搜尋 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
沒有留言:
張貼留言