2020/04/21

Blogger コンテンツに自動的に関連記事を掲載する

Blogger コンテンツに自動的に関連記事を掲載するために、テーマに手を加えます。

内部リンクの必要性

このブログを自分で見返していて、記事間の接続が悪いな、と思いました。
ところどころ明示的に記事内で他記事へリンクしているのですが、ほとんどの記事は他記事への接続性がありません。せっかく興味を持って見ていただいているので、関連する記事も見ていっていただきたいのですが、おそらくラベルのページでも開けていただかない限りは、辿りつけません。
また、このブログは Google 検索にヒットするページがまだまだ少なく、Google Search Console でその原因を調べていくと「検出 - インデックス未登録」扱いになっている記事が多いことに気付きました。おそらく、内部リンクが不十分でクロール優先度が低くなってしまっているのでしょう。

関連記事リンクは自動で作れる!

いろんなブログを見ていると、各記事に関連記事へのリンクが付いていて、なるほどこういう形で内部リンクを充実させればいいんだな、と理解しました。
でもこれメンテナンス面倒だな、記事書く度に関連性を考えてリンクしていくのか……、と思っていました。
でも実は、ラベルを使って類似度を判断して、自動的に関連記事をピックアップする仕組みがあると知りました。これならメンテナンスフリーですからありがたい!

そういうわけで、このブログにも関連記事リンクを入れてみたので、顛末を紹介します。

ベースにしたもの

Blogger's Origin の Custom Related Post Widget For Blogger – With Thumbnail というウィジェットが公開されていて、求めているものに近そうだったので、こちらを使わせていただきました。でも、いくつか直したいところがあったので、改変しています。

組み込み方

テーマの「HTML の編集」で、直接テーマを変更していきます。変更箇所は3箇所あります。

1. この仕組みを動かす JavaScript を仕込む

まず、以下のコードを </head> タグのすぐ上などに入れます。pageType == item、つまり単独記事のページのときだけ、この JavaScript が埋め込まれます。関連記事を抽出するために API アクセスするときのコールバック関数や関連記事を表示するための各種処理が定義されています。

<!-- Related Posts widget with Thumbnails Code Before Head Start-->
<b:if cond='data:blog.pageType == &quot;item&quot;'>
<script type='text/javascript'>
//<![CDATA[
var borelatedTitles=new Array();var rboTitlesNum=0;var relatedUrls=new Array();var thumburl=new Array();function related_results_labels_thumbs(json){for(var i=0;i<json.feed.entry.length;i++){var entry=json.feed.entry[i];borelatedTitles[rboTitlesNum]=entry.title.$t;try{thumburl[rboTitlesNum]=entry.gform_foot.url}catch(error){s=entry.content.$t;a=s.indexOf("<img");b=s.indexOf("src=\"",a);c=s.indexOf("\"",b+5);d=s.substr(b+5,c-b-5);if((a!=-1)&&(b!=-1)&&(c!=-1)&&(d!="")){thumburl[rboTitlesNum]=d}else thumburl[rboTitlesNum]='data:image/png;base64,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'}for(var k=0;k<entry.link.length;k++){if(entry.link[k].rel=='alternate'){relatedUrls[rboTitlesNum]=entry.link[k].href;rboTitlesNum++}}}}function removeDuplicate_thumbs(){var tmp=new Array(0);var tmp2=new Array(0);var tmp3=new Array(0);for(var i=0;i<relatedUrls.length;i++){if(!contains_thumbs(tmp,relatedUrls[i])){tmp.length+=1;tmp[tmp.length-1]=relatedUrls[i];tmp2.length+=1;tmp3.length+=1;tmp2[tmp2.length-1]=borelatedTitles[i];tmp3[tmp3.length-1]=thumburl[i]}}borelatedTitles=tmp2;relatedUrls=tmp;thumburl=tmp3}function contains_thumbs(a,e){for(var j=0;j<a.length;j++)if(a[j]==e)return true;return false}function printRelatedLabel_thumbs(){for(var i=0;i<relatedUrls.length;i++){if((relatedUrls[i]==findcurrentposturl)||(!(borelatedTitles[i]))){relatedUrls.splice(i,1);borelatedTitles.splice(i,1);thumburl.splice(i,1);i--}}var r=Math.floor((borelatedTitles.length-1)*Math.random());var i=0;if(borelatedTitles.length>0)document.write('<h4><span>'+titleofrelatedpost+'</span></h4>');document.write('<sl class="related-posts-list">');while(i<borelatedTitles.length&&i<20&&i<maxpost){document.write('<ci><a href="'+relatedUrls[r]+'"><span class="rthumb"><img class="related_img" src="'+thumburl[r]+'"/></span><p><span class="related-title">'+borelatedTitles[r]+'</span></p></a></ci>');if(r<borelatedTitles.length-1){r++}else{r=0}i++}document.write('</sl>');relatedUrls.splice(0,relatedUrls.length);thumburl.splice(0,thumburl.length);borelatedTitles.splice(0,borelatedTitles.length)}//]]></script>
</b:if>
<!-- Related Posts widget with Thumbnails Code Before Head Ends-->

2. 関連記事を抽出するための処理を仕込む

続いて、以下のコードを <b:widget ... type='Blog' ... >...</b:widget> の中の <b:includable id='post' var='post'>...</b:includable> の中の <div class='post-footer'> のすぐ上に入れます。ここで、その記事に付いているラベル全てに対してフィード情報を取得して、コールバック関数に渡しています。また関連記事エリアの表示位置も決めています。

<!-- Related Posts widget with Thumbnails Code Before Post Footer Start-->
<b:if cond='data:blog.pageType == &quot;item&quot;'>
<div id='related-posts'>
<b:loop values='data:post.labels' var='label'>
<script expr:src='&quot;/feeds/posts/default/-/&quot; + data:label.name + &quot;?alt=json-in-script&amp;callback=related_results_labels_thumbs&amp;max-results=7&quot;' type='text/javascript'/>
</b:loop>
<script type='text/javascript'>
var findcurrentposturl=&quot;<data:post.url/>&quot;;
var maxpost=6;
var titleofrelatedpost=&quot;<b>関連記事</b>&quot;;
removeDuplicate_thumbs();
printRelatedLabel_thumbs();
</script>
</div>
</b:if>
<!-- Related Posts widget with Thumbnails Code Before Post Footer Ends-->

3. 関連記事エリアの見た目を決める CSS を仕込む

最後に CSS を入れます。<b:skin><![CDATA[ と ]]></b:skin> の間に入れましょう。

/* CSS For related post widget
----------------------------------------------- */
#related-posts {float:left;width:101%;}
#related-posts sl{margin:0 !important;padding:0 !important;}
#related-posts h4{margin:10px 0px 20px;font-size:18px;color:#696868}
.related-posts-list ci{background-color:#fff;box-shadow:0 0 4px rgba(180, 180, 180, 0.55);width:27%;float:left;position:relative;margin:0 3% 3% 0;padding:2%;}
.related-posts-list ci:nth-child(3n){margin-right:0 !important;}
#related-posts a{color:#696868;text-decoration:none;}
#related-posts sl ci p{margin:0;padding-top:1px;}
#related-posts .related_img{height:64px;margin-bottom:0;width:64px;object-fit:cover;}
.related-title{text-align:center;padding:5px 0px;}.rthumb{float:left;margin-right:10px;}

オリジナルからの改変箇所

いくつかオリジナルから改変している箇所があるので、説明しておきます。

  • JavaScript 部
    • サムネイルが無い記事用のデフォルト画像を Data URI で組み込んでいる
      • オリジナルでは、おそらく作者の Blogger の中にアップロードされているファイルへのリンクになっていました
      • 依存関係が発生するのはあまりよくないと思ったのと、かなり高解像度の画像だったので、低解像度化し、Data URI 化して埋め込むことにしました
    • 記事タイトルを短くカットする処理をやめました
      • オリジナルでは45文字くらいで切っていたのですが、45文字だとだいたい収まっていて、ちょっとだけ切れることが多く、微妙だなと思い、切るのをやめました
  • 記事部
    • エリアタイトル名を変えました
    • <b:if></b:if> を除去しました
      • 中で何もしていない if ブロックだったので、削除しました
  • CSS 部
    • 記事タイトル欄のマージンを無くしました
      • 適当にマージンを取って、縦位置がサムネイルの真ん中くらいにくるように調整しているように見えましたが、画面幅を狭くするとバランスが悪くなるので、上に寄せました
    • フォントを変えるのをやめました
      • 何箇所かフォントを指定している箇所がありましたが、テーマ全体のスタイルに従った方が良いかと思い、削除しました

カスタマイズ可能箇所

関連記事数の変更

記事部の9行目 (var maxpost=6;) の 6 を変えると、表示する関連記事数を変更することができます。変更するときは、5行目の max-results=7 の値が+1になるように、合わせて変更してください。
max-results はフィードから取得する記事の数です。1つ多く取得しているのは、今表示している記事そのものが含まれるからです。1つ多く取得しておけば、今表示している記事を除いても表示したい関連記事数に見合う情報が取得できます。

関連記事エリアのタイトルの変更

記事部の10行目です。