原來爲了簡單方便,本身小網站上的文章頁的相關內容推薦就是從數據庫裏隨機抽取數據來填充一個列表,因此一點相關性都沒有,更本沒有辦法引導用戶去訪問推薦內容。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...