·您现在的位置: 云翼网络 >> 文章中心 >> 网站建设 >> 网站建设开发 >> php网站开发 >> 手把手教你做关键词匹配项目(搜索引擎)---- 第二十一天

手把手教你做关键词匹配项目(搜索引擎)---- 第二十一天

作者:佚名      php网站开发编辑:admin      更新时间:2022-07-23
手把手教你做关键词匹配项目(搜索引擎)---- 第二十一天

客串:屌丝的坑人表单神器、数据库那点事儿

面向对象升华:面向对象的认识----新生的初识、面向对象的番外----思想的梦游篇(1)、面向对象的认识---如何找出类

负载均衡:负载均衡----概念认识篇、负载均衡----实现配置篇(Nginx)

吐槽:现在欠的文章有面向对象的认识----类的转化、面向对象的番外---思想的梦游篇(2)、负载均衡 ---- 文件服务策略、手把手教你做关键词匹配项目(搜索引擎)。真心太多了,能不能让我休息一会儿。

第二十一天

起点:手把手教你做关键词匹配项目(搜索引擎)---- 第一天

回顾:手把手教你做关键词匹配项目(搜索引擎)---- 第二十天

今天有个理论知识要理解的,叫做测试驱动编程,之前我提到过概念,在:手把手教你做关键词匹配项目(搜索引擎)---- 第十一天

今天小帅帅秀逗了一回,使用了这个思想。

好了,以下正文开始。

话说小帅帅把自己写的业务拆词方法给了于老大看,于老大很高兴。

但是业务拆词的词组都是有限的,还有就是当业务拆词的数据量越来越大的时候,就会造成运算时间增加。

于老大就提到,是否可以用其它分词扩展来弥补拆词的不足。

毕竟人家专业人士做的,比较靠谱点。

于老大很有经验,就推荐小帅帅去了解SCWS的用法.

SCWS 是 Simple Chinese Word Segmentation 的首字母缩写(即:简易中文分词系统)。官方网址:http://www.xunsearch.com/scws/index.php

小帅帅听了当然很开心罗,因为又有新的知识点了。

小帅帅照着SCWS的安装文档安装了SCWS。

并把php扩展装好了,并尝试写了个测试代码:

<?phpclass TestSCWS {    public static function split($keyword){        if (!extension_loaded("scws")) {            throw new Exception("scws extension load fail");        }        $so = scws_new();        $so->set_charset('utf8');        $so->send_text($keyword);        $ret = array();        while ($res = $so->get_result()) {            foreach ($res as $tmp) {                if (self::isValidate($tmp)) {                    $ret[] = $tmp;                }            }        }        $so->close();        return $ret;    }    public static function isValidate($scws_words)    {        if ($scws_words['len'] == 1 && ($scws_words['word'] == "\r" || $scws_words['word'] == "\n")) {            return false;        }        return true;    }}var_dump(TestSCWS::split("连衣裙xxl裙连衣裙"));

测试通过,跟理想中的一摸一样,小帅帅很高兴,就去问于老大:于老大我会用SCWS了,下一步该怎么办?

于老大也不慌,就对小帅帅说: 你先写个ScwsSplitter来拆分关键词吧。

小帅帅非常高兴,因为他学到了新的知识,就对于老大说到好的。

小帅帅说到做到,代码如下:

class ScwsSplitter {    public $keyword;        public function split(){        if (!extension_loaded("scws")) {            throw new Exception("scws extension load fail");        }        $keywordEntity = new KeywordEntity($this->keyword);        $so = scws_new();        $so->set_charset('utf8');        $so->send_text($this->keyword);               while ($res = $so->get_result()) {            foreach ($res as $tmp) {                if ($this->isValidate($tmp)) {                    $keywordEntity->addElement($tmp["word"]);                }            }        }        $so->close();        return $keywordEntity;    }    public function isValidate($scws_words)    {        if ($scws_words['len'] == 1 && ($scws_words['word'] == "\r" || $scws_words['word'] == "\n")) {            return false;        }        return true;    }    }

小帅帅又跑去找于老大,说到:我把Scws的分词代码写好了。

于老大也佩服小帅帅的高效率。

又说到:如果我两个同时用了,我先用业务分词,遗留下来的词用Scws分词,小帅帅有好的方案吗?

小帅帅就问到: 为啥要这样,这不是多此一举。

于老大就说到:业务有些专有名词,SCWS分不出来丫,那怎么办好?

小帅帅又说到:我看文档的时候看到有词库和规则文件的设置,我们用它好不好?

于老大又说到:这个是可以,但是我们如何保证让运营人员维护,我们要学会把这些事情交出去丫。

小帅帅: …….

小帅帅沉默了片刻,觉得现在两个类都写了,一起用是最快的方案,就答应到:好吧,我回去改改….

首先小帅帅根据测试驱动编程的思想写了入口代码:

class SplitterApp {    public static function split($keyword,$cid){        $keywordEntity = new KeywordEntity($keyword);        #业务分词        $termSplitter = new TermSplitter($keywordEntity);        $seg = new DBSegmentation();        $seg->cid = $cid;        $termSplitter->setDictionary($seg->transferDictionary());        $termSplitter->split();        #SCWS分词        $scwsSplitter = new ScwsSplitter($keywordEntity);        $scwsSplitter->split();        #后续遗留单词或者词组处理        $elementWords = $keywordEntity->getElementWords();        $remainKeyword = str_replace($elementWords, "::", $keywordEntity->keyword);        $remainElements = explode("::", $remainKeyword);        foreach($remainElements as $element){            if(!empty($element))                $keywordEntity->addElement($element);        }     return $keywordEntity;    }}

小帅帅嘿了一声,有了测试入口,还怕其他的搞不定。

首先KeywordEntity的getElementWords,先搞定他.

class KeywordEntity{    public $keyword;    public $elements = array();    public function __construct($keyword)    {        $this->keyword = $keyword;    }    public function addElement($word, $times = 1)    {        if (isset($this->elements[$word])) {            $this->elements[$word]->times += $times;        } else            $this->elements[$word] = new KeywordElement($word, $times);    }    public function getElementWords()    {        $elementWords = array_keys($this->elements);        usort($elementWords, function ($a, $b) {            return (UTF8::length($a) < UTF8::length($b)) ? 1 : -1;        });        return $elementWords;    }    /**     * @desc 计算UTF8字符串权重     * @param string $word     * @return float     */    public function calculateWeight($word)    {        $element = $this->elements[$word];        return ROUND(strlen($element->word) * $element->times / strlen($this->keyword), 3);    }}class KeywordElement{    public $word;    public $times;    public function __construct($word, $times)    {        $this->word = $word;        $this->times = $times;    }}

其次就是分词了,首先先抽出公用类先,Splitter变成了公用类,有哪些方法呢?

  1. 抽象split方法

2. 获取关键词待拆分的词组

3. 是否需要拆分

按照这写,小帅帅写出了以下代码:

abstract class Splitter {    /**     * @var KeywordEntity $keywordEntity     */    public $keywordEntity;    public function __construct($keywordEntity){        $this->keywordEntity = $keywordEntity;    }    public abstract function split();    /**     * 获取未分割的字符串,过滤单词     *     * @return array     */    public function getRemainKeywords()    {        $elementWords = $this->keywordEntity->getElementWords();        $remainKeyword = str_replace($elementWords, "::", $this->keywordEntity->keyword);        $remainElements = explode("::", $remainKeyword);        $ret = array();        foreach ($remainElements as $element) {            if ($this->isSplit($element)) {                $ret[] = $element;            }        }        return $ret;    }    /**     * 是否需要拆分     *     * @param $element     * @return bool     */    public function isSplit($element)    {        if (UTF8::isPhrase($element)) {            return true;        }        return false;    }}

然后小帅帅继续实现业务拆分算法,以及Scws拆分算法。小帅帅淫笑了,这点小事情还是可以办到的。

class TermSplitter extends Splitter {    PRivate $dictionary = array();    public function setDictionary($dictionary = array())    {        usort($dictionary, function ($a, $b) {            return (UTF8::length($a) < UTF8::length($b)) ? 1 : -1;        });        $this->dictionary = $dictionary;    }    public function getDictionary()    {        return $this->dictionary;    }    /**     * 把关键词拆分成词组或者单词     *     * @return KeywordScore[] $keywordScores     */    public function split()    {        foreach ($this->dictionary as $phrase) {            $remainKeyword = implode("::",$this->getRemainKeywords());            $matchTimes = preg_match_all("/$phrase/", $remainKeyword, $matches);            if ($matchTimes > 0) {                $this->keywordEntity->addElement($phrase, $matchTimes);            }        }    }}class ScwsSplitter extends Splitter{    public function split()    {        if (!extension_loaded("scws")) {            throw new Exception("scws extension load fail");        }        $remainElements = $this->getRemainKeywords();        foreach ($remainElements as $element) {            $so = scws_new();            $so->set_charset('utf8');            $so->send_text($element);            while ($res = $so->get_result()) {                foreach ($res as $tmp) {                    if ($this->isValidate($tmp)) {                        $this->keywordEntity->addElement($tmp['word']);                    }                }            }            $so->close();        }    }    /**     * @param array $scws_words     * @return bool