原来为了简单方便,本身小网站上的文章页的相关内容推荐就是从数据库里随机抽取数据来填充一个列表,因此一点相关性都没有,更本没有办法引导用户去访问推荐内容。html
如何能作到类似内容的推荐呢,碍于小网站还跑在虚拟主机上(对的,连一个本身完整可控的服务器都没有),因此能够想的办法很少,条件限制在 只能用PHP+MySql。因此我想到的办法就是经过Tags来匹配类似文章进行推荐。若是两篇文章的TAGS 比较类似python
好比:文章A 的TAGS为: [A,B,C,D,E]
文章B 的 TAGS 为:[A,D,E,F,G]
文章C 的 TAGS 为:[C,H,I,J,K]
经过眼睛咱们能很方便的发现,文章B和文章A更为类似,由于它们有三个关键字相同分别为:[A,D,E],哪如何用计算机来判断它们的类似度呢,这里咱们用jaccard类似度的最基本应用来计算它们的类似度mysql
jaccard类似度算法
给定两个集合A,B,Jaccard 系数定义为A与B交集的大小与A与B并集的大小的比值,定义以下:sql
文章A和文章B的交集为 [A,D,E],大小为3,并集为[A,B,C,D,E,F,G],大小为7,3/7=0.4285...
而文章A和文章C交集为 [C],大小为1,并集为[A,B,C,D,E,H,I,J,K],大小为9, 1/9=0.11111...数据库
这样就能够得出文章A,B比文章A,C更为类似,有了这个算法,计算机就能够来判断两篇文章的类似度了。数组
给定一篇文章,获取该文章的关键字TAGS,而后经过以上算法去数据库比对全部文章的类似度,获取最类似的N篇文章进行推荐。缓存
文章的TAGS是经过TF-IDF算法,提取文章中的高频词,选取N个做为TAGS,对于中文的文章来讲还涉及到一个中文分词的问题,由于是虚拟主机的关系,这步的工做我用python(为何用Python ,jieba分词,真香)在本地写了一个程序,完成全部文章的分词,词频统计,生成TAGS,并写回服务器的数据库。因为本文是写推荐的算法,因此分词和创建TAGS的部分就不具体展开了,并且不一样的系统有不一样的TAGS创建方式。服务器
创建两张表,用于存储TAGS
tags,用于存全部tag的名称函数
+-------+------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+------------+------+-----+---------+-------+ | tag | text | YES | | NULL | | | count | bigint(20) | YES | | NULL | | | tagid | int(11) | NO | PRI | 0 | | +-------+------------+------+-----+---------+-------+
tag_map 创建tag和文章的映身关系。
+-----------+------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-----------+------------+------+-----+---------+-------+ | id | bigint(20) | NO | PRI | 0 | | | articleid | bigint(20) | YES | | NULL | | | tagid | int(11) | YES | | NULL | | +-----------+------------+------+-----+---------+-------+
tag_map存的数据相似以下:
+----+-----------+-------+ | id | articleid | tagid | +----+-----------+-------+ | 1 | 776 | 589 | | 2 | 776 | 471 | | 3 | 776 | 1455 | | 4 | 776 | 1287 | | 5 | 776 | 52 | | 6 | 777 | 1386 | | 7 | 777 | 588 | | 8 | 777 | 109 | | 9 | 777 | 603 | | 10 | 777 | 1299 | +----+-----------+-------+
其实作类似推荐的时候,只须要用到tag_map表就能够了,由于tagid和tag name 是一一对应的。
mysql> select articleid, GROUP_CONCAT(tagid) as tags from tag_map GROUP BY articleid; +-----------+--------------------------+ | articleid | tags | +-----------+--------------------------+ | 12 | 1178,1067,49,693,1227 | | 13 | 196,2004,2071,927,131 | | 14 | 1945,713,1711,2024,49 | | 15 | 35,119,9,1,1180 | | 16 | 1182,1924,2200,181,1938 | | 17 | 46,492,414,424,620 | | 18 | 415,499,153,567,674 | | 19 | 1602,805,691,1613,194 | | 20 | 2070,1994,886,575,1149 | | 21 | 1953,1961,1534,2038,1393 | +-----------+--------------------------+
经过以上SQL,能够一次性查询所用文章,极其对应的全部tag
在PHP,咱们能够把tags变成数组。
public function getAllGroupByArticleId(){ //缓存查询数据,由于这个是全表数据,并且不更新文章不会变化,即是每次推荐都要从数据库里获取一次数据,对性能确定会有影响,因此作个缓存。 if($cache = CacheHelper::getCache()){ return $cache; } $query_result = $this->query('select articleid, GROUP_CONCAT(tagid) as tags from tag_map GROUP BY articleid'); $result = []; foreach($query_result as $key => $value){ //用articleid 作key ,值是该id下的全部tagID数组。 $result[$value['articleid']] = explode(",",$value['tags']); } CacheHelper::setCache($result, 86400); return $result; }
有了这个的返回结果,就比较好办了,接下去的工做就是去应用jaccard类似度这个算法了,具体就看代码吧。
/** * [更据指定文章返回类似的文章推荐] * @param $articleid 指定的文章ID * @param $top 要返回的推荐条数 * @return Array 推荐条目数组 */ function getArticleRecommend($articleid, $top = 5){ if($cache = CacheHelper::getCache()){ return $cache; } try{ $articleid = intval($articleid); $m = new TagMapModel(); $all_tags = $m->getAllGroupByArticleId();//调用上面的函数返回全部文章的tags $finded = $all_tags[$articleid];//由于上面是包含全部文章了,因此确定包含了当前文章。 unset($all_tags[$articleid]);//把当前文章从数组中删除,否则本身和本身确定是类似度最高了。 $jaccard_arr = []; //用于存类似度 foreach ($all_tags as $key => $value) { $intersect =array_intersect($finded, $value); //计算交集 $union = array_unique(array_merge($finded, $value)); //计算并集 $jaccard_arr[$key] = (float)(count($intersect) / count($union)); } arsort($jaccard_arr); //按类似度排序,最类似的排最前面 $jaccard_keys = array_keys($jaccard_arr);//因为数组的key就是文章id,因此这里把key取出来就能够了 array_splice($jaccard_keys, $top);//获取前N条推荐 //到这里咱们就已经获得了,最类似N篇文章的ID了,接下去的工做就是经过这几个ID,从数据库里把相关信息,查询出来就能够了 $articleModels = new \Api\Model\ArticleModel(); $recommendArticles = $articleModels->getRecommendByTag($jaccard_keys); CacheHelper::setCache($recommendArticles, 604800); //缓存7天 return $recommendArticles; } catch (\Exception $e) { throw new \Exception("获取推荐文章错误"); } }
虽然简单,短短几条代码,可是效果仍是能够的,推荐的文章有了必定的类似度,确定能够带来更好的用户体验,实例 ,大家能够看看 https://www.wx2share.com/Arti...