コメントが多い順のランキングページを作りました [Ask-me]

当ページのリンクには広告が含まれています。

コメントが多いランキングは、記事ページの下部と専用の固定ページの2種類になります。けっこう四苦八苦して、今でも悩んでいる状況です。同じように、コメントが多い順ランキングを作りたい人に向けて、微力ながら協力したいと思い、ソースコードを公開しておきます。

私は、プログラマーやSEなどをやっていましたが、大した技術があるわけでもなく、素人に毛が生えた程度です。だから、プログラムのソースコードもグチャグチャです。とりあえずは、参考程度にどうぞです。

メディアに取り上げられた時に比べれば数字は落ちています。それでも、以前と変わらず唯一の自慢出来る数字は、直帰率です。60%を切っています。いくつかサイトやブログを運営している中で、ここまで直帰率が低いのは旦那デスノートだけです。

ただ、ピーク時とは行かなくとも、PVはもう少し盛り返したいなと悩んでいます。目指すところは、アクセスしてきた人には出来るだけ回遊して頂き、さらに直帰率を下げたいなと考えています。

人は、ウェブサイトに限らず、ランキングが大好きです。ショッピングをするにも、どこかへ旅行するにも、ランキングで行動の仕方が変わったりします。そこで、今回は、サイトを回遊してもらうため、コメントが多い記事ランキングページを作りました。

作りましたというか、まだ制作途上な感じで、いったんは出来上がったのですが、もっと磨ける部分はあるだろうな、と感じています。

旦那デスノートのシステムは、Wordpressによる、海外の有料テーマをベースに作っています。テーマはAsk-meと言い、themeforestにて6,000円くらいで買えます。細かいことは以下に書きました。SNSシステム制作に興味がありましたら、参考にしてみてください。
誰でも簡単に作れます。旦那デスノートのシステムは、海外themeforestにあるWordPressのAsk-meというシステムテンプレートで作られています。

Ask-meって、ところどころにアクションフックであるdo_actionが書いてあるので、まぁまぁカスタマイズしやすいです。ちょこっとバグもあるのですが、私はアクションフックで書き替えてます。

冒頭にも書いた通り、会員登録ができるSNSで、会員様は自分で投稿をすることが出来ます。今現在、投稿は15万件を超えています。ここまでの規模をWordpressで運用したことが無く、常に悩みます。特にランキングなどは、大量の投稿を取得したりします。

大量の投稿を取得ということは、データベースにガンガンアクセスをし、forループが回りまくったりするわけです。そこをうまく軽くしていくことに悩みます。ネットで調べたところで、ここまで大きな規模を想定した記事などは少なく、自分でなんとかしなければなりません。

重い処理を扱う場合は、キャッシュか、非同期という言葉が思い浮かびます。私は非同期に関しては全く判らないので、基本的にはキャッシュを使ってランキングなど動的なページは作るようにしています。

WordPressでキャッシュと聞くと、有名なプラグインがたくさんあります。はっきり言って、プログラムがよく判らない初心者は、キャッシュプラグインを使ってはダメです。その時は軽くなって喜べたとしても、どこかで大体悪さをして、バグの原因が見つからないループに陥ります。

私は、キャッシュプラグインは一切使いません。今まで制作したサイトやブログも全てキャッシュのプラグインは使っていません。画像が多いブログは、cloudflareを使ったりしています。使うキャッシュはTransientsです。Transientsはデーターベースに保存したいコードなどを入れておく仕組みです。

私が使用している中では、人気ページランキングを表示してくれる「Simple GA Ranking」もTransientsを使っています。phpmyadminでTransientsが使うwp_optionsテーブルを見ると判ります。

上の画像の140745にある_transient_sga_ranking_sga_3e591d8e8って、Simple GA Rankingのやつですよね。140753の_transient_date_comments_ranking2021-03-0120は、私が作ったコメント多いランキング用のキャッシュです。

右のoption_valueを見れば判りますが、HTMLがそのまま入っています。setで保存、getで取得、getでfalseならget_postsなどを行なう、というのが簡単な流れです。Transientsを使うだけで、激重のDB処理も、最初の1回だけ我慢すれば、あとはサクサクになります。

記事ページ下部にある、コメントが多いページランキングのソースコードは以下です。あまりソースコードを公開したことが無いので、笑わないでください。



function date_comments_ranking( $atts ){

    $html = '';
    $from    = '2017-09-01';
    $to        = '2017-09-30';
    $posts_per_page = 5;
    
    $post_date = get_the_date('Y-m');
    $from = date('Y-m-d',strtotime( $post_date . ' first day of last month'));
    $to = date('Y-m-d',strtotime( $post_date . ' last day of last month'));
    
    $args = array(
        'post_type'            => 'question',
        'posts_per_page'    => $posts_per_page,
        'orderby'            => 'comment_count',
        'date_query'        => array(
            array(
                
                'after'        =>    $from,
                'before'    =>    $to,
                'inclusive' => true,
            ),
        ),

    );
    
    $key = 'date_comments_ranking' . $from . $to;
    $html = get_transient( $key );

    
    if( $html === false ){
        
        $my_posts = get_posts( $args );
    
        if ( $my_posts ) {
            
            $html .= '<div class="sga_ranking_same_category">';
            $html .= '<h3>前月のコメントが多かったデスノート</h3>';
            $html .= '<p>' . date('Y年n月j日', strtotime($from)) . 'から' . date('Y年n月j日', strtotime($to)) . 'までのコメントが多い順のデスノートです。カッコの数字はコメント数です。</p>';
            $html .= '<ol>';
                
            foreach( $my_posts as $post ) {
                    
                $comments = wp_count_comments( $post->ID );
                $comments_count = $comments->approved;
        
                $html .= '<li>';
        
                $html .= '<a href="/note-' . $post->ID . '">';
                $html .= $post->post_title;
                $html .= '(' . $comments_count . ')';
                $html .= '</a>';
        
                $html .= '</li>';
            }
            $html .= '</ol>';


            $date_last_month = date( 'Y-m', strtotime( $post_date . ' last month' ) );
            $html .= '<p><a href="/date_comments_ranking_more?';
            $html .= 'date=' . $date_last_month;
            $html .= '">もっとみる</a></p>';
            $html .= '</div>';

            wp_reset_postdata();
        }
        set_transient( $key,
            $html,
            60 * 60 * 24
        );
    }
    return $html;
    
}
add_shortcode('date_comments_ranking', 'date_comments_ranking');

プログラムとしては、ショートコードとして登録してあります。ショートコードは全てプラグインのCode Snippetsを使っています。スイッチでON OFFが出来るので、簡単で便利です。

苦労した点は、投稿を月間で取得する方法ですね。投稿取得のクエリであるget_postsにdate_queryで月初から月末を設定しなければなりません。月初や月末の日時取得とか知らなかったので、勉強になりました。

このコメント多いランキングは、表示している記事の前月の投稿を出力しています。理由は、回遊させたいからです。同じ月だと、ランキングのリンクをタップして移動したとき、下部に出てくるランキングは同じものです。

でも、表示している記事の前月、としておくと、飛ぶたびに前月になるので、ランキングがガラッと変わります。色々変わることで、新鮮さを味わってもらい、違うものを見て、少しでも辛い気持ちを落ち着けてもらえばな、という意図です。

この後に固定ページで動的にもっと多いランキングを表示させるのですが、渡すパラメータとして、最初は月初と月末の日時を渡していました。Y-mだけ渡せば、あとはプログラムで月初と月末を設定すれば良いと気付き、URLを少し短くすることができました。

最初のこれを、
/date_comments_ranking?from=2020-09-01&to=2020-09-30

こういう風に変更。
/date_comments_ranking?date=2020-09

先ほども書いたとおり、transientを使っています。$argsにクエリパラメータを設定して、get_transient用にkeyを作って、getして、無かったらget_postsをさせるようにしていますね。

記事ページ用は5件しか表示させないとしても、ページを開くたびにget_postsが走るのはあまり良くないですね。アクセス数もそれなりにあるので、塵も積もれば山となる。なので、キャッシュを使っています。

で、5件のあとに、もっとみる、というリンクが、多く表示されるランキングページへ飛ぶようになっています。ランキングページは固定ページで用意し、ショートコードを使い、GETパラメータにより動的に表示させています。

POSTじゃなくGETにしている理由は、ランキングページをお気に入りにブックマーク出来るように配慮しているためです。

では次に、ランキングページである固定ページに使っているショートコードです。



function date_comments_ranking_more( $atts ){

    $html = '';
    $from    = '2017-09-01';
    $to        = '2017-09-30';
    $posts_per_page = 15;
    
    if( isset( $_GET['date'] ) ){

        $date = h( $_GET['date'] );
            
        if( preg_match('/\A[0-9]{4}-[0-9]{1,2}\z/', $date) != false ){
            $from    = date('Y-m-d',strtotime( $date . ' first day of'));
            $to        = date('Y-m-d',strtotime( $date . ' last day of'));
        }
    }
    
    $args = array(
        'post_type'            => 'question',
        'posts_per_page'    => $posts_per_page,
        'orderby'            => 'comment_count',
        'date_query'        => array(
            array(
                'after'        =>    $from,
                'before'    =>    $to,
                'inclusive' => true,
            ),
        ),
    );
    
    $key = 'date_comments_ranking_more' . $from . $to;
    $html = get_transient( $key );

    if( $html === false ){
        
        $my_posts = get_posts( $args );
    
        if ( $my_posts ) {
            
            $html .= '<p>' . date('Y年n月j日', strtotime($from)) . 'から' . date('Y年n月j日', strtotime($to)) . 'までのコメントが多い順のデスノートです。</p>';
            
            $html .= '<ol>';
        
            foreach( $my_posts as $post ) {
                    
                $comments = wp_count_comments( $post->ID );
                $comments_count = $comments->approved;
        
                $html .= '<li>';
        
                $html .= '<a href="/note-' . $post->ID . '#comments">';
                $html .= $comments_count . '件';
                $html .= '</a> ';
                    
                $html .= '<a href="/note-' . $post->ID . '">';
                $html .= $post->post_title;
                $html .= '</a>';
        
                $html .= '</li>';
            }
            $html .= '</ol>';
            
        wp_reset_postdata();
        }
        set_transient( $key,
            $html,
            60 * 60 * 24
        );
    }
    return $html;
}
add_shortcode('date_comments_ranking_more', 'date_comments_ranking_more');

やっていることは、記事ページの下部にあるやつと大して変わりはありませんね。3つ目となる最後が問題です。というのも、ようやくここのショートコードが形になったので、この記事を書いたようなものです。

ランキングページである固定ページの下部に、selectタグによるプルダウンのメニューを付けました。これは、今までに投稿があった年月が一覧になっています。いずれかの年月を選ぶと、ランキングページが対象月のものに変わります。

ポイントは、年月の後ろにカッコで付けている数字です。これは、対象月の投稿に対して書かれたコメントの数です。これが重かった。大変だった。今でもまだ悩んでいます。

投稿は2015年8月からあるのですが、冒頭にも書いたとおり、全部で15万件の投稿があるわけです。コメント数を出力するということは、投稿の全てを確認しないといけないのです。15万件を全件一気に取得しにいくと、メモリオーバーでサイトが落ちます。

ということで、今の所は、対象月のコメント数のみプルダウンメニューに反映させる、反映したらtransientに登録、という処理に落ち着きました。ちょっと、ショートコードにしたソースコードを貼りますね。まだ苦労した言いたいことがあるのですよ。



function pull_down_year_day_comments( $atts ){
    
    $html = '';
    $current_date_from    = '2017-09-01';
    $current_date_to    = '2017-09-30';
    $current_date        = '2017-09';
    $slug = '/date_comments_ranking_more';

    if( isset( $_GET['date'] ) ){

        $date = h( $_GET['date'] );
            
        if( preg_match('/\A[0-9]{4}-[0-9]{1,2}\z/', $date) != false ){
            $current_date = $date;
            
            $current_date_from    = date('Y-m-d',strtotime( $current_date . ' first day of'));
            $current_date_to    = date('Y-m-d',strtotime( $current_date . ' last day of'));
        }
    }
    
    $transient_key_first_post = 'pull_down_year_day_comments_first_post';
    $first_post = get_transient( $transient_key_first_post );
    
    if( $first_post === false ){
        $args = array(
            'post_type'            => 'question',
            'posts_per_page'         => '1',
            'ignore_sticky_posts'    => false,
            'order'                  => 'ASC',
        );
        
        $first_post = get_posts( $args );
        
        set_transient( $transient_key_first_post,
            $first_post,
            7 * 60 * 60 * 24 //1週間
        );
    }
    
    if( $first_post ){
        
        $first_post_date_ym = date('Y-m', strtotime($first_post[0]->post_date));
        
        $latest_post_date_ym = date( 'Y-m' );

        $html .= '<p style="margin-top: 4em;">月別の記事一覧です。カッコの数字は対象月のコメント数です。</p>';
        $html .= '<select name="example2" onChange="location.href=value;">';
        
        for ( $i = $latest_post_date_ym; $i >= $first_post_date_ym; $i = date( 'Y-m', strtotime( $i . ' -1 month') ) ) {

            $html .= '<option value="' . $slug . '?date=' . $i . '"';
            if( $current_date == $i ){
                $html .= ' selected';
            }
            $html .= '>' . $i;

            
            $transient_key_comments = 'pull_down_year_day_comments_select_' . $i;
            $comments = get_transient( $transient_key_comments );
            if( $comments === false && $current_date == $i ){
                $args = array(
                    'post_type'    => 'question',
                    'posts_per_page' => -1,
                    'date_query'        => array(
                        array(
                            'after'        =>    date('Y-m-d',strtotime( $i . ' first day of' ) ),
                            'before'    =>    date('Y-m-d',strtotime( $i . ' last day of' ) ),
                            'inclusive' => true,
                        ),
                    ),
                    'fields' => 'ids',
                );
                $my_posts = get_posts( $args );
                $comments_count = 0;
                foreach( $my_posts as $post_id ){
                    $comments = wp_count_comments( $post_id );
                    $comments_count += $comments->approved;
                }
                
                $comments = '(' . $comments_count . ')';
                
                set_transient( $transient_key_comments,
                    $comments,
                    7 * 60 * 60 * 24
                );

            }
            $html .= $comments;
            $html .= '</option>';
        }
        $html .= '</select>';

    }
        
    return $html;
}
add_shortcode('pull_down_year_day_comments', 'pull_down_year_day_comments');

プルダウンメニューの年月の横はコメント数と言いました。これ、月間のコメント数なんですけど、対象月の投稿にあったコメント数なんです。対象月に投稿されたコメント数、ではなく、対象月の投稿にあったコメントです。

get_commentsでは、取得できませんでした。get_commentsは、コメントの日時をクエリパラメータにします。コメントの日時にしてしまうと、私が取得したい数字ではなくなってしまうのです。

例えば、
記事は、2020-10-02 に投稿され、この記事に対し、2020-11-10 にコメントが投稿されると、コメントのカウントは、2021-11 の分になるわけです。そうではなくて、2020-10 の投稿にされたコメント数が欲しいのです。

というのも、ランキングの各リンクには、全てコメント数を書いてあります。ランキングを全部表示させているわけではないですが、基本的には、ランキングで表示されているコメント数の合計値がプルダウンメニューの年月に書いてあるコメント数と一致しないと気持ち悪いのです。

これがget_commentsでは取得できなく悩みました。で、結局は、月間の投稿を全て取得し、各投稿のコメント数を合計する、という方法にしました。ただし、プルダウンに使う全ての年月を取ろうとすると、メモリオーバーで落ちてしまいます。

そこで、対象月のコメント数のみ取得し、transientキャッシュに登録する、というやり方にしました。この方法が正しいかどうかは判りませんが、今はこんな感じです。少しでもサイトを回遊し、PVが上がってくれることを祈ります。

この記事を書いた人

旦那デスノートで実際に使っている、WordpressやPHPのコードなどを公開しています。公開しているものは、ネットには あまり載っていない、痒いところに手が届くような拡張です。
@koichiromakita2

コメント

コメントする