赫赫文王»网誌»站務»

article recommendation in Hugo 文章推薦舉例

經過艱苦卓絕的試驗,網站第四版終於完工了!現在介紹一下本站各類推薦中用到的算法。本人不懂編程,這裏只是瞎貓撞上死耗子。源碼直接在我 Github 上找就可以,分別在 home.html 和 single.html
  4373 字
 作者@柯棋瀚 標籤#coding
版權 CC BY-NC-SA 4.0,非商業用途可隨意使用

本站的索引 taxonomies 有分類 categories、系列 series、標籤 tags、作者 author、備註 notes。每篇文章都有 1 箇分類、0-1 個系列、0-3 個標籤、0-1 箇備註。分爲网誌、作品、赫赫讚府、春秋學刊 4 個 section

一級難度

從簡單的開始說。以下都是主葉中的。

作品

「作品」的 sectiongallery。按修改時間倒序列出所有 typegallery 的文章:

{{ range  (where .Site.RegularPages.ByLastmod.Reverse "Type" "gallery") }}
    <a href="{{ .Permalink }}">{{ .Title }}</a>
{{ end }}

二級難度

最近修改

思路如下:

  1. 按最近修改時間倒序,列出所有文章
  2. 篩選出所有發布時間和修改時間不一致的文章
  3. 列出前 15 項
{{ $lastmod := slice }}
{{ range   (where .Site.RegularPages.ByLastmod.Reverse "Type" "post")  }}
    {{ if ne .Lastmod .Date }}
        {{ $lastmod =$lastmod | append . }}
    {{ end }}
{{ end }}
{{ range first 15 $lastmod }}
    <a href="{{ .Permalink }}">{{ .Title }}</a>
{{ end }}

最簡單的方法就是直接按修改時間倒序列出,但是大部分都會和最近發布重複,就顯得沒意義了。

其他作者

同理,還有其他作者:

  1. 列出所有作者
  2. 篩選出不是「柯棋瀚」「柯棋瀚整理」的作者
  3. 隨機列出這些作者及他們的文章
{{ $author := slice }}
{{ range .Site.Taxonomies.author.ByCount }}
    {{ if ne .Page.Title  ["柯棋瀚"] ["柯棋瀚整理"] }}
        {{ $author = $author | append . }}
    {{ end }}
{{ end }}
{{ range $author  | shuffle  }}
    <a href="{{ .Page.RelPermalink }}">{{ .Page.Title }}</a>
    {{ range .Pages  | shuffle }}
        <li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
    {{ end }}
{{ end }}

過去與未來的的本周

文章頁面下的「過去的本周」,簡單粗暴用了査表法。.Unix 語法見 https://gohugo.io/functions/unix/,是從 1970-01-01 開始累加的秒數。

我這當然不是準確的「本周」,我算的是向前後加減三天。一天 86,400s,一年 315,360s,一閏年 316,224s。我將弟 4、8 年設爲閏年。于是思路很簡單了:

{{ $1date := sub .Date.Unix 31276800  }}
{{ $2date := sub .Date.Unix 31795200 }}
{{ $3date := sub .Date.Unix 62812800 }}
{{ $4date := sub .Date.Unix  63331200 }}
{{ $5date := sub .Date.Unix 94348800 }}
{{ $6date := sub .Date.Unix  94867200 }}
{{ $7date := sub .Date.Unix 125971200 }}
{{ $8date := sub .Date.Unix 126489600 }}
{{ $9date := sub .Date.Unix 157507200 }}
{{ $10date := sub .Date.Unix 158025600 }}
{{ $11date := sub .Date.Unix 189043200 }}
{{ $12date := sub .Date.Unix 189561600 }}
{{ $13date := sub .Date.Unix 220579200 }}
{{ $14date := sub .Date.Unix  221097600 }}
{{ $15date := sub .Date.Unix 252201600 }}
{{ $16date := sub .Date.Unix  252720000 }}
{{ $21date := add .Date.Unix 31276800  }}
{{ $22date := add .Date.Unix 31795200 }}
{{ $23date := add .Date.Unix 62812800 }}
{{ $24date := add .Date.Unix  63331200 }}
{{ $25date := add .Date.Unix 94348800 }}
{{ $26date := add .Date.Unix  94867200 }}
{{ $27date := add .Date.Unix 125971200 }}
{{ $28date := add .Date.Unix 126489600 }}
{{ $29date := add .Date.Unix 157507200 }}
{{ $30date := add .Date.Unix 158025600 }}
{{ $31date := add .Date.Unix 189043200 }}
{{ $32date := add .Date.Unix 189561600 }}
{{ $33date := add .Date.Unix 220579200 }}
{{ $34date := add .Date.Unix  221097600 }}
{{ $35date := add .Date.Unix 252201600 }}
{{ $36date := add .Date.Unix  252720000 }}
<ul>
{{ range  (where .Site.RegularPages.ByDate "Type" "post") }}
{{ $currentdate := .Date.Unix }}
{{ if and (ge $currentdate $2date) (le $currentdate $1date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $4date) (le $currentdate $3date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $6date) (le $currentdate $5date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $8date) (le $currentdate $7date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $10date) (le $currentdate $9date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $12date) (le $currentdate $11date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $14date) (le $currentdate $13date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $16date) (le $currentdate $15date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $21date) (le $currentdate $22date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $23date) (le $currentdate $24date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $25date) (le $currentdate $26date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $27date) (le $currentdate $28date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $29date) (le $currentdate $30date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $31date) (le $currentdate $32date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $33date) (le $currentdate $34date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ else if and (ge $currentdate $35date) (le $currentdate $36date) }}
<li><a href="{{ .RelPermalink }}">{{ .Title | safeHTML }}</a> <time>{{  .Date.Format "2006-01-02" }}</time></li>
{{ end }}
{{ end }}
</ul>

三級難度

分類

主葉上「分類」的目的是讓新讀者快速瞭解网誌所有文章類型,所以我想在每個分類下面生成兩項最近發布和兩項隨機推薦。同時,分類後面有「赫赫指北」和「資源」兩個系列,需要把它們排除了。大槩思路如下:

  1. 隨機排列所有分類
  2. 生成兩項最近發布
  3. 隨機生成兩項系列不是「赫赫指北」和「資源」的項目

具體思路見代碼塊中的註釋。

<!--  1、生成所有系列不是赫赫指北|資源|的文章。 -->
{{ $series := slice }}
{{ $1series := slice }}
{{ $2series := slice }}
{{ range .Site.Taxonomies.series.赫赫指北 }}
    {{ $1series = $1series | append .Page }}
{{ end }}
{{ range .Site.Taxonomies.series.資源 }}
    {{ $2series = $2series | append .Page }}
{{ end }}
{{ with (where .Site.RegularPages "Type" "post") }}
    {{ $series = . | complement $1series $2series }}
{{ end }}
<!--  2、隨機排列所有分類 -->
{{ $allCate := where .Site.Taxonomies.categories "Type" "post" }}
{{ range $allCate.ByCount  | shuffle }}
    {{ $first2 := slice }}
    {{ $thisCateinSeries := slice }}
    {{ $end := slice }}
    {{ $thisCate := slice }}
<!-- 3、生成兩項最近發布  -->    
    {{ range first 2 .Pages }}
        {{ $first2 = $first2 | append . }}
    {{ end }}
<!-- 4、生成 3 和 1 的交集,卽在最近兩項中排除系列是赫赫指北|資源|的  -->    
    {{ $First2inSeries :=  intersect $series $first2  }}
<!-- 5、生成這個分類下所有文章  -->    
    {{ range .Pages | shuffle }}
        {{ $thisCate = $thisCate | append . }}
    {{ end }}
<!-- 6、生成 5 和 1 的交集,卽在這個分類所有文章中排除系列是赫赫指北|資源|的 -->    
    {{ $thisCateinSeries := intersect $thisCate $series }}
<!-- 7、生成 6 和 4 的補集,卽在這個分類所有文章中排除系列是赫赫指北|資源|的,以及最近兩項  -->    
    {{ $end := $thisCateinSeries | complement $First2inSeries  }}
    <ol>
<!-- 8、結果  -->         
        <h2><a href="{{ .Page.Permalink }}">{{ .Page.Title | safeHTML  }}</a></h2>
        <div class="discover-des" style="margin-top:.5em;text-align:left">最近發布</div>
        {{ range $first2 }}
            <div><a href="{{ .Permalink }}">{{ .Title | safeHTML  }}</a></div>
        {{ end }}
        <div>隨機推薦</div>
        {{ range first 2 $end | shuffle }}
            <div><a href="{{ .Permalink }}">{{ .Title | safeHTML  }}</a></div>
        {{ end }}
    </ol>
{{ end }}

您可能喜歡

以下是各文章下面的推薦。

是從 ops.tips 那移植過來的,非常感謝。(安利一句,這網站挺好看的)進行了三處改進:

  1. 他只算了 tags,我加上了 categoriesseriesnotes 不列入計算
  2. 加權計算,系列|的權重比 [分類] #標籤 髙
  3. 根據相關文章篇數,實行梯度強弱相關度

首先看第二點。本站的情況是,如果兩篇文章的系列相同,那關聯度是最高的,我們可以據此設置 加權,系列占 4,分類、標籤占 3。有如下可能:

分類 3系列 4標籤 3總分
0000
0013
0026
0039
0104
0117
01210
01313
1003
1016
1029
10312
1107
11110
11213
11316

0,3,4,6,7,9,10,12,13,16 10 種情況。我們設置 3 4 6 7 9 10 13 7 檔。

第三點,如果一個系列篇數很多,超過了 10 篇,那推薦裏面全是這箇系列的,其他一些相關但關聯度弱一些的文章就不會出現。爲改進這個問題,我們可以用強弱相關度相結合的方法。分三種情況。

  1. 相關篇數少於 10:直接順序排列前 10 篇。
  2. 相關篇數 11—20:順序排列前 5 篇;弟 6—10 篇隨機選取 2 篇;弟 11—20 篇隨機選取 3 篇
  3. 相關篇數多於 21:順序排列前 5 篇;弟 6—20 篇隨機選取 3 篇;弟 21—30 篇隨機選取 2 篇

思路如下:

  1. 列出本文章之外的所有文章
  2. 加權計算本文章和其他文章的索引重合度
  3. 按索引重合度進行排序
  4. 分別生成前 5 項、6—10 項、11—20 項、6—20 項、21—30 項
  5. 根據不同情況,進行隨機選取。
{{ $current := . }}
{{ $articles := where (where $.Site.Pages ".Kind" "eq" "page" ) ".Title" "!=" $current.Title }}
{{ $3relevant := slice }}
{{ $4relevant := slice }}
{{ $6relevant := slice }}
{{ $7relevant := slice }}
{{ $9relevant := slice }}
{{ $10relevant := slice }}
{{ $13relevant := slice }}
{{ $recommend := slice }}
{{ $5recommend := slice }}
{{ $10recommend := slice }}
{{ $20recommend := slice }}
{{ $30recommend := slice }}
{{ $6to10recommend := slice }}
{{ $6to20recommend := slice }}
{{ $11to20recommend := slice }}
{{ $21to30recommend := slice }}
<!-- 加權計算索引重合度 -->
{{ range $idx, $article := $articles }}
    {{ $1intersection := len (intersect $article.Params.series $current.Params.series) }}
    {{ $1weighted := mul 4 $1intersection }}
    {{ $2intersection := len (intersect $article.Params.categories $current.Params.categories) }}
    {{ $2weighted := mul 3 $2intersection }}
    {{ $3intersection := len (intersect $article.Params.tags $current.Params.tags) }}
    {{ $3weighted := mul 3 $3intersection }}
    {{ $4weighted  := add $1weighted $2weighted }}
    {{ $5weighted  := add $3weighted $4weighted }}
    {{ if (ge $5weighted 13) }}
        {{ $13relevant = $13relevant | append $article }}
        {{ else if (ge $5weighted 10) }}
            {{ $10relevant = $10relevant | append $article }}
        {{ else if (eq $5weighted 9) }}
            {{ $9relevant = $9relevant | append $article }}
        {{ else if (eq $5weighted 7) }}
            {{ $7relevant = $7relevant | append $article }}
        {{ else if (eq $5weighted 6) }}
            {{ $6relevant = $6relevant | append $article }}
        {{ else if (eq $5weighted 4) }}
            {{ $4relevant = $4relevant | append $article }}
        {{ else if (eq $5weighted 3) }}
            {{ $3relevant = $3relevant | append $article }}
        {{ end }}
{{ end }}
<!-- 據索引重合度排列相關文章 -->
{{ range $13relevant }}
    {{ $recommend = $recommend | append . }}
{{ end }}
{{ range $10relevant }}
    {{ $recommend = $recommend | append . }}
{{ end }}
{{ range $9relevant }}
    {{ $recommend = $recommend | append . }}
{{ end }}
{{ range $7relevant }}
    {{ $recommend = $recommend | append . }}
{{ end }}
{{ range $6relevant }}
    {{ $recommend = $recommend | append . }}
{{ end }}
{{ range $4relevant }}
    {{ $recommend = $recommend | append . }}
{{ end }}
{{ range $3relevant }}
    {{ $recommend = $recommend | append . }}
{{ end }}
<!-- 列出數量區間 -->
{{- range shuffle (first 5 $recommend) -}}
    {{ $5recommend = $5recommend | append . }}
{{ end }}
{{- range shuffle (first 10 $recommend) -}}
    {{ $10recommend = $10recommend | append . }}
{{ end }}
{{ range shuffle (first 20 $recommend)  }}
    {{ $20recommend = $20recommend | append . }}
{{ end }}
{{ range shuffle (first 30 $recommend) }}
    {{ $30recommend = $30recommend | append . }}
{{ end }}
{{ $6to10recommend = $10recommend | complement  $5recommend | shuffle }}
{{ $6to20recommend = $20recommend | complement  $5recommend | shuffle }}
{{ $11to20recommend = $20recommend | complement  $10recommend | shuffle }}
{{ $21to30recommend = $30recommend | complement  $20recommend | shuffle }}
<!-- 根據相關文章篇數進行條件判斷 -->
{{ if ge (len $recommend) 21 }}
    <ul>
    <div>強相關</div>
    {{- range first 5 $recommend -}}
        <li><a href="{{ .Permalink }}">{{- .Title | safeHTML | markdownify -}}</a></li>
    {{ end }}
    <div>隨機次強相關</div>
    {{- range first 3 $6to20recommend -}}
        <li><a href="{{ .Permalink }}">{{- .Title | safeHTML | markdownify -}}</a></li>
    {{ end }}
    <div>隨機弱相關</div>
    {{- range first 2  $21to30recommend  -}}
        <li><a href="{{ .Permalink }}">{{- .Title | safeHTML | markdownify -}}</a></li>
    {{ end }}
    </ul>
        {{ else if ge (len $recommend) 11 }}
        <ul>
        <div>強相關</div>
        {{- range first 5 $recommend -}}
            <li><a href="{{ .Permalink }}">{{- .Title | safeHTML | markdownify -}}</a></li>
        {{ end }}
        <div>隨機次強相關</div>
        {{- range first 2 $6to10recommend -}}
            <li><a href="{{ .Permalink }}">{{- .Title | safeHTML | markdownify -}}</a></li>
        {{ end }}
        <div>隨機弱相關</div>
        {{- range first 3 $11to20recommend -}}
            <li><a href="{{ .Permalink }}">{{- .Title | safeHTML | markdownify -}}</a></li>
        {{ end }}
        </ul>
            {{ else if le (len $recommend) 10 }}
            <ul>
            <div>強相關</div>
            {{- range first 10 $recommend -}}
                <li><a href="{{ .Permalink }}">{{- .Title | safeHTML | markdownify -}}</a></li>
            {{ end }}
            </ul>
{{ end }}

蒙文通的經史觀——經學抉原讀後(附摘錄) 爲例。未加權:

  • 錢穆經學通論摘錄
  • 陳壁生諸書摘錄
  • 周予同諸篇摘錄
  • 北京讀經說記述要
  • 梁啓超中國近三百年學術史述要
  • 鄭玄地位的升降——禮是鄭學讀後
  • 蔗糖消費的社會性——甜與權力讀後
  • 維柯與康德的史學思想——新科學讀後
  • 霍布斯鮑姆筆下的蘇聯模式──極端的年代讀後
  • 公羊傳齊襄復讎事詮釋史

加權之後:

  • 錢穆經學通論摘錄
  • 陳壁生諸書摘錄
  • 周予同諸篇摘錄
  • 北京讀經說記述要
  • 梁啓超中國近三百年學術史述要
  • 鄭玄地位的升降——禮是鄭學讀後
  • 蔗糖消費的社會性——甜與權力讀後
  • 維柯與康德的史學思想——新科學讀後
  • 霍布斯鮑姆筆下的蘇聯模式──極端的年代讀後
  • 艾因哈德筆下的査理大帝——査理大帝傳讀後

區別不大。只有最後一項不一樣,被換成了讀書筆記。再加入了強弱相關度之後,後 5 項:

  • 蔗糖消費的社會性——甜與權力讀後
  • 于山水古琴練習曲集指要
  • 保罗·芮恩施平民政治的基本原理述要
  • 雷次宗略注喪服經傳疏議
  • 資源|文史類網路學術資源

出現了不是讀書|系列的內容。效果不錯。

走之前去髙 雄走一走 爲例,分類:[生活],系列:立言記|,標籤:#臺灣。直接列出重合度前十:

  • 在輔仁大學交換是怎樣的體驗?
  • 臺灣的幾箇孔廟
  • 2018 二〇一八
  • 我❤府城──府城客運
  • 故宮和蘆洲
  • 星爸星媽的天空
  • 基 kee 隆 lung
  • 動物園狂發照片
  • 兩次遊行
  • 臺灣ㄟ大學隨拍

都是立言記|的內容。結合了強弱相關度之後的後 5 項:

  • 樂山大佛的石窟
  • 動物園狂發照片
  • 星爸星媽的天空
  • 一些㒳厈詞彙、讀音對照
  • 臺灣訪琴記

效果不錯。

沒有喜歡的?這裏呢

在此基礎上,我們可以有代表性地生成與此不相關的文章:

  1. 列出所有分類,每個分類隨機產生一項文章
  2. 生成所有不是本文章、分類系列有重合的文章,及其他 section 的文章
  3. 生成 1 和 2 的交集
  4. 在這一交集中隨機產生 5 項文章
{{ $categories := slice }}
{{ range .Site.Taxonomies.categories.ByCount }}
    {{ with .Pages | shuffle }}
        {{ $cate := . }}
        {{ range first 1 $cate }}
            {{ $categories =  $categories | append  . }}
        {{ end }}
    {{ end }}
{{ end }}
{{ $current := slice }}
{{ $current = $current | append . }}
{{ $other := $.Site.RegularPages |  complement $current $recommendedArticles (where .Site.RegularPages "Type" "gallery") (where .Site.RegularPages "Type" "block")  (where .Site.RegularPages "Type" "zanfu") (where .Site.RegularPages "Type" "xuekan") | shuffle }}
{{ $others := intersect  $categories  $other }}
<ul>
    {{  range first 5 $others | shuffle }}
        <li><a href="{{ .Permalink }}">{{- .Title -}}</a></li>
    {{ end }}
</ul>

隨機推薦下面一行有個坑,如果直接寫成這樣:

{{ range .Site.Taxonomies.categories.ByCount }}
    {{ range first 1 .Pages | shuffle }}  
        {{ $categories =  $categories | append  . }}
    {{ end }}
{{ end }}

是不能隨機的,產生的都是各分類中發布時間最近的一項文章。所以我用了迂迴的方法。

推廣

如果配合 GitHub Actions,定時重新生成,那這些隨機的推薦可以讓靜態網頁變成半動態網頁,豈不美哉!

案:本站已實現 定時生成

評論系統:诏预Isso开放服务。本站對您在使用該系統時產生的隱私問題不負責任