ブログ運営をしていると、自分のサイトの記事がどれくらい読まれているか気になります。
普通はアナリティクスで解析するものですが……
しかし、アナリティクスで確認したりするほどでもない時ってないですか?
私はWordPressにログインしているときに毎回、すぐにパパッと見たいわけです。
以前使っていたテーマのCocoonだと、トップページなどの記事一覧画面でタイトル下に管理者のみにPV数が表示されるようなプラグインがあったのですが、
最近高速化を図るため、テーマをSWELLに変えたところ、そのプラグインでPVが表示されなくなりました。
せっかく高速化のためにテーマをSWELLに変えて、表示速度が体感でもはっきり速くなったのに
「プラグインを増やしたくない」「サイトを軽く保ち続けたい」という気持ちもあり……
\ SWELLは有料ですが、買い切りだし高速なのでオススメ /
そんな方におすすめなのが、今回紹介するプラグインなしで自作するWordPress用のPVカウンターです。
この記事では、初心者の方でもコピペで簡単に実装できるよう丁寧に解説します。
- WordPressでPVを自動記録する方法
- 管理画面にPV一覧を表示する方法
- 自分やbotのアクセスを除外する方法
- プラグインなしで実装するコードの具体例
などなど
作成するPVカウンターの概要
この自作PVカウンターは、非常に軽量でシンプルなコード設計になっています。
一般的なPV計測プラグインに比べて、以下の点でサーバー負荷を大幅に抑えることができます:
- 必要最低限の処理だけを行い、データベースアクセスも制限的に実行される(Cookie制御による多重カウント防止)
- 外部通信やAPI連携がない(JavaScript読み込みなし)
- フロントエンド側では表示処理を一切行わないため、ページ表示の速度に影響を与えない(読者の邪魔をしない)
実際の軽量さとしては、一般的なプラグインと比べて、処理負荷はおよそ1/3〜1/5程度になると思います(環境によって変動あり)。
「少しでもWordPressを軽くしたい」「シンプルなコードで必要十分なPV管理をしたい」という方には、非常に高パフォーマンスな選択肢です。
今回作るPVカウンターには以下の機能があります。
- 記事ごとの本日、昨日、今週、今月、累計のPV数を表示
- 管理画面で一覧表示、並び替えやカテゴリ別表示が可能
- ログイン中のユーザーのアクセスを除外(自分のアクセスをカウントしない)
- 検索エンジンのクローラー(bot)のアクセスを除外
- 同じユーザーの連続アクセスを防止(Cookie制御)
これにより、正確かつ軽量なPV管理が可能になります。
PVカウント処理を別ファイルで実装(functions.phpをスッキリさせる)
functions.php
に直接すべてのコードを書くとファイルが長くなり、管理しにくくなります。
そこで、PVカウント処理専用のファイルを作成し、functions.php
から読み込ませる方法をおすすめします。
まず、テーマフォルダ内に新しくファイルを作成します(例:/functions/pv-tracker.php
)。このファイルに次のコードを記入します。
<?php
// PVカウント処理
function record_accurate_views() {
// ログインユーザーを除外
if ( is_user_logged_in() ) return;
// 個別の記事ページのみカウント
if ( ! is_singular() ) return;
// bot除外
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
if ( preg_match('/bot|crawl|slurp|spider/i', $ua) ) return;
$post_id = get_the_ID();
if ( ! $post_id ) return;
$time = time();
$today_key = 'views_day_' . date('Ymd');
$week_key = 'views_week_' . date('oW');
$month_key = 'views_month_' . date('Ym');
$total_key = 'views_total';
$keys = [ $today_key, $week_key, $month_key, $total_key ];
foreach ( $keys as $key ) {
$cookie_key = 'viewed_' . md5($post_id . $key);
if ( ! isset($_COOKIE[$cookie_key]) ) {
$val = (int) get_post_meta($post_id, $key, true);
update_post_meta($post_id, $key, $val + 1);
setcookie($cookie_key, '1', $time + 3600, COOKIEPATH, COOKIE_DOMAIN);
}
}
}
add_action('wp', 'record_accurate_views');
// 管理画面にPV一覧メニューを追加
add_action('admin_menu', function() {
add_menu_page(
'PV一覧',
'PV一覧',
'manage_options',
'pv-summary',
'render_pv_summary_page',
'dashicons-chart-bar',
25
);
});
// 管理画面に表示するPV一覧ページの内容
function render_pv_summary_page() {
echo '<div class="wrap"><h1>記事別PV一覧</h1></div>';
// ここにPV一覧表示用のコードを後ほど追加
}
次に、functions.php
に以下のように記述し、作成したpv-counter.php
を読み込ませます。
// PVカウント処理 → /functions/pv-tracker.php に分離しています
require_once get_stylesheet_directory() . '/functions/pv-tracker.php';
これでfunctions.php
がすっきりし、管理しやすくなります。

pv-counter.php
を別で作る場合は、functions.php
で上記のようにわかりやすいコメントを残しておけば、次回編集時に困りません。
管理画面「PV一覧」ページを作成するコード
全文は長いので折りたたんでいます。
以下クリックで開閉してご覧ください。
「PV一覧」ページを作成するコード全文
// PV一覧ページの表示処理
function render_pv_summary_page() {
// 🔁 PVデータをリセット(ボタン押下時)
if ( isset($_POST['reset_pv']) && check_admin_referer('reset_pv_action') ) {
$all_posts = get_posts(['post_type' => 'post', 'posts_per_page' => -1, 'fields' => 'ids']);
foreach ( $all_posts as $pid ) {
$meta = get_post_meta($pid);
foreach ( $meta as $key => $val ) {
if ( strpos($key, 'views_') === 0 ) delete_post_meta($pid, $key);
}
}
echo '<div class="notice notice-success"><p>全記事のPVデータをリセットしました。</p></div>';
}
// 📊 PVキーの定義
$today_key = 'views_day_' . date('Ymd');
$yesterday_key = 'views_day_' . date('Ymd', strtotime('-1 day'));
$week_key = 'views_week_' . date('oW');
$month_key = 'views_month_' . date('Ym');
$total_key = 'views_total';
// 📋 クエリ条件の取得
$posts_per_page = isset($_GET['ppp']) ? (int) $_GET['ppp'] : 20;
$paged = isset($_GET['paged']) ? (int) $_GET['paged'] : 1;
$selected_cat = isset($_GET['cat']) ? (int) $_GET['cat'] : 0;
$orderby = $_GET['orderby'] ?? 'newest';
$hide_zero = isset($_GET['hide_zero']) && $_GET['hide_zero'] === '1';
// 🔢 全体PV合計算出
$all_posts = get_posts(['post_type' => 'post', 'posts_per_page' => -1, 'fields' => 'ids']);
$sum_today = $sum_week = $sum_month = $sum_total = 0;
foreach ( $all_posts as $pid ) {
$sum_today += (int) get_post_meta($pid, $today_key, true);
$sum_week += (int) get_post_meta($pid, $week_key, true);
$sum_month += (int) get_post_meta($pid, $month_key, true);
$sum_total += (int) get_post_meta($pid, $total_key, true);
}
// 🧾 UI出力開始
echo '<div class="wrap"><h1>記事別 PV一覧</h1>';
echo '<form method="post" onsubmit="return confirm(\'本当にリセットしますか?\')">';
wp_nonce_field('reset_pv_action');
echo '<input type="submit" name="reset_pv" class="button button-danger" value="PVデータをリセット">';
echo '</form><hr>';
echo "<p><strong>全体合計:</strong> 本日:$sum_today|今週:$sum_week|今月:$sum_month|累計:$sum_total</p>";
// 絞り込みフォーム(カテゴリ・並び順など)
echo '<form method="get" style="margin-bottom:1em; display:flex; gap:1em;">';
echo '<input type="hidden" name="page" value="pv-summary">';
echo '<select name="cat" onchange="this.form.submit()"><option value="0">すべて</option>';
foreach ( get_categories(['hide_empty' => false]) as $cat ) {
$sel = $selected_cat === $cat->term_id ? 'selected' : '';
echo "<option value='{$cat->term_id}' $sel>{$cat->name}</option>";
}
echo '</select>';
echo '<select name="ppp" onchange="this.form.submit()">';
foreach ([10,20,50,100] as $val) {
$sel = $posts_per_page === $val ? 'selected' : '';
echo "<option value='$val' $sel>$val 件</option>";
}
echo '</select>';
$orders = [
'newest' => '新着順', 'oldest' => '古い順', 'today' => '本日PV順',
'yesterday' => '昨日PV順', 'week' => '今週PV順',
'month' => '今月PV順', 'least' => 'PV少ない順', 'total' => '累計PV順'
];
echo '<select name="orderby" onchange="this.form.submit()">';
foreach ( $orders as $key => $label ) {
$sel = $orderby === $key ? 'selected' : '';
echo "<option value='$key' $sel>$label</option>";
}
echo '</select>';
echo '<label><input type="checkbox" name="hide_zero" value="1" onchange="this.form.submit()"'
. ( $hide_zero ? ' checked' : '' ) . '> PV0除外</label>';
echo '</form>';
// 📑 WP_Query で記事取得
$args = [
'post_type' => 'post',
'posts_per_page' => $posts_per_page,
'paged' => $paged
];
if ( $selected_cat ) $args['cat'] = $selected_cat;
if ( $hide_zero ) {
$args['meta_query'] = ['relation' => 'OR',
['key' => $today_key, 'value' => 0, 'compare' => '>', 'type' => 'NUMERIC'],
['key' => $week_key, 'value' => 0, 'compare' => '>', 'type' => 'NUMERIC'],
['key' => $month_key, 'value' => 0, 'compare' => '>', 'type' => 'NUMERIC'],
['key' => $total_key, 'value' => 0, 'compare' => '>', 'type' => 'NUMERIC']
];
}
switch ($orderby) {
case 'oldest': $args['orderby'] = 'date'; $args['order'] = 'ASC'; break;
case 'today': $args['meta_key'] = $today_key; $args['orderby'] = 'meta_value_num'; $args['order'] = 'DESC'; break;
case 'yesterday': $args['meta_key'] = $yesterday_key; $args['orderby'] = 'meta_value_num'; $args['order'] = 'DESC'; break;
case 'week': $args['meta_key'] = $week_key; $args['orderby'] = 'meta_value_num'; $args['order'] = 'DESC'; break;
case 'month': $args['meta_key'] = $month_key; $args['orderby'] = 'meta_value_num'; $args['order'] = 'DESC'; break;
case 'total': $args['meta_key'] = $total_key; $args['orderby'] = 'meta_value_num'; $args['order'] = 'DESC'; break;
case 'least': $args['meta_key'] = $today_key; $args['orderby'] = 'meta_value_num'; $args['order'] = 'ASC'; break;
default: $args['orderby'] = 'date'; $args['order'] = 'DESC'; break;
}
$query = new WP_Query($args);
// 📋 テーブル出力
echo '<style>.pv-thumb{width:120px;aspect-ratio:16/9;object-fit:cover;}</style>';
echo '<table class="widefat striped"><thead><tr><th>タイトル</th><th>サムネイル</th><th>本日</th><th>昨日</th><th>今週</th><th>今月</th><th>累計</th></tr></thead><tbody>';
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
$pid = get_the_ID();
echo '<tr>';
echo '<td><a href="' . get_edit_post_link() . '">' . get_the_title() . '</a></td>';
echo '<td>' . get_the_post_thumbnail($pid, [120,68], ['class'=>'pv-thumb']) . '</td>';
echo '<td>' . (int) get_post_meta($pid, $today_key, true) . '</td>';
echo '<td>' . (int) get_post_meta($pid, $yesterday_key, true) . '</td>';
echo '<td>' . (int) get_post_meta($pid, $week_key, true) . '</td>';
echo '<td>' . (int) get_post_meta($pid, $month_key, true) . '</td>';
echo '<td>' . (int) get_post_meta($pid, $total_key, true) . '</td>';
echo '</tr>';
}
wp_reset_postdata();
} else {
echo '<tr><td colspan="7">投稿が見つかりませんでした。</td></tr>';
}
echo '</tbody></table>';
echo '</div>';
}
ここからは、管理画面に表示されるPV一覧ページのコードを段階的に解説していきます。
PVデータのリセット処理
管理画面に「リセット」ボタンを表示し、それが押されたときに全記事のPVデータを初期化します。
開発中や運用の切り替え時に便利です。
// PVデータをリセット(ボタン押下時)
if ( isset($_POST['reset_pv']) && check_admin_referer('reset_pv_action') ) {
$all_posts = get_posts(['post_type' => 'post', 'posts_per_page' => -1, 'fields' => 'ids']);
foreach ( $all_posts as $pid ) {
$meta = get_post_meta($pid);
foreach ( $meta as $key => $val ) {
if ( strpos($key, 'views_') === 0 ) delete_post_meta($pid, $key);
}
}
echo '<div class="notice notice-success"><p>全記事のPVデータをリセットしました。</p></div>';
}
PVキーの定義と合計の算出
PVの記録には「本日」「昨日」「今週」「今月」「累計」といったキーを使用します。
ここではそれぞれのキーを定義し、全記事のPV合計もあらかじめ算出します。
// PVキーの定義
$today_key = 'views_day_' . date('Ymd');
$yesterday_key = 'views_day_' . date('Ymd', strtotime('-1 day'));
$week_key = 'views_week_' . date('oW');
$month_key = 'views_month_' . date('Ym');
$total_key = 'views_total';
UI出力:全体合計とフォームの表示
管理画面の見た目を作る部分です。
全体のPV合計を表示したり、「PVをリセットする」ためのボタンを設置したりします。
// UI出力開始
echo '<div class="wrap"><h1>記事別 PV一覧</h1>';
echo '<form method="post" onsubmit="return confirm(\'本当にリセットしますか?\')">';
wp_nonce_field('reset_pv_action');
echo '<input type="submit" name="reset_pv" class="button button-danger" value="PVデータをリセット">';
echo '</form><hr>';
echo "<p><strong>全体合計:</strong> 本日:$sum_today|今週:$sum_week|今月:$sum_month|累計:$sum_total</p>";
絞り込みフォームの表示(カテゴリ・並び順など)
記事一覧を見やすくするために、カテゴリや並び順、表示件数、PVゼロの記事の除外などのフィルター機能を備えたフォームを作成します。
// 絞り込みフォーム(カテゴリ・並び順など)
echo '<form method="get" style="margin-bottom:1em; display:flex; gap:1em;">';
echo '<input type="hidden" name="page" value="pv-summary">';
echo '<select name="cat" onchange="this.form.submit()"><option value="0">すべて</option>';
foreach ( get_categories(['hide_empty' => false]) as $cat ) {
$sel = $selected_cat === $cat->term_id ? 'selected' : '';
echo "<option value='{$cat->term_id}' $sel>{$cat->name}</option>";
}
echo '</select>';
echo '<select name="ppp" onchange="this.form.submit()">';
foreach ([10,20,50,100] as $val) {
$sel = $posts_per_page === $val ? 'selected' : '';
echo "<option value='$val' $sel>$val 件</option>";
}
echo '</select>';
$orders = [
'newest' => '新着順', 'oldest' => '古い順', 'today' => '本日PV順',
'yesterday' => '昨日PV順', 'week' => '今週PV順',
'month' => '今月PV順', 'least' => 'PV少ない順', 'total' => '累計PV順'
];
echo '<select name="orderby" onchange="this.form.submit()">';
foreach ( $orders as $key => $label ) {
$sel = $orderby === $key ? 'selected' : '';
echo "<option value='$key' $sel>$label</option>";
}
echo '</select>';
echo '<label><input type="checkbox" name="hide_zero" value="1" onchange="this.form.submit()"'
. ( $hide_zero ? ' checked' : '' ) . '> PV0除外</label>';
echo '</form>';
WP_Queryで記事取得・並び順設定
指定された条件に基づいてWordPressの投稿一覧を取得する処理です。
並び順やカテゴリなど、絞り込みの設定もここで行います。
// WP_Query で記事取得
$args = [
'post_type' => 'post',
'posts_per_page' => $posts_per_page,
'paged' => $paged
];
if ( $selected_cat ) $args['cat'] = $selected_cat;
if ( $hide_zero ) {
$args['meta_query'] = ['relation' => 'OR',
['key' => $today_key, 'value' => 0, 'compare' => '>', 'type' => 'NUMERIC'],
['key' => $week_key, 'value' => 0, 'compare' => '>', 'type' => 'NUMERIC'],
['key' => $month_key, 'value' => 0, 'compare' => '>', 'type' => 'NUMERIC'],
['key' => $total_key, 'value' => 0, 'compare' => '>', 'type' => 'NUMERIC']
];
}
switch ($orderby) {
case 'oldest': $args['orderby'] = 'date'; $args['order'] = 'ASC'; break;
case 'today': $args['meta_key'] = $today_key; $args['orderby'] = 'meta_value_num'; $args['order'] = 'DESC'; break;
case 'yesterday': $args['meta_key'] = $yesterday_key; $args['orderby'] = 'meta_value_num'; $args['order'] = 'DESC'; break;
case 'week': $args['meta_key'] = $week_key; $args['orderby'] = 'meta_value_num'; $args['order'] = 'DESC'; break;
case 'month': $args['meta_key'] = $month_key; $args['orderby'] = 'meta_value_num'; $args['order'] = 'DESC'; break;
case 'total': $args['meta_key'] = $total_key; $args['orderby'] = 'meta_value_num'; $args['order'] = 'DESC'; break;
case 'least': $args['meta_key'] = $today_key; $args['orderby'] = 'meta_value_num'; $args['order'] = 'ASC'; break;
default: $args['orderby'] = 'date'; $args['order'] = 'DESC'; break;
}
$query = new WP_Query($args);
PV一覧テーブルを表示
取得した投稿のデータを表形式で表示します。
各記事のタイトル、アイキャッチ画像、各期間のPV数が一覧で確認でき、視覚的に分かりやすい設計です。
// テーブル出力
echo '<style>.pv-thumb{width:120px;aspect-ratio:16/9;object-fit:cover;}</style>';
echo '<table class="widefat striped"><thead><tr><th>タイトル</th><th>サムネイル</th><th>本日</th><th>昨日</th><th>今週</th><th>今月</th><th>累計</th></tr></thead><tbody>';
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
$pid = get_the_ID();
echo '<tr>';
echo '<td><a href="' . get_edit_post_link() . '">' . get_the_title() . '</a></td>';
echo '<td>' . get_the_post_thumbnail($pid, [120,68], ['class'=>'pv-thumb']) . '</td>';
echo '<td>' . (int) get_post_meta($pid, $today_key, true) . '</td>';
echo '<td>' . (int) get_post_meta($pid, $yesterday_key, true) . '</td>';
echo '<td>' . (int) get_post_meta($pid, $week_key, true) . '</td>';
echo '<td>' . (int) get_post_meta($pid, $month_key, true) . '</td>';
echo '<td>' . (int) get_post_meta($pid, $total_key, true) . '</td>';
echo '</tr>';
}
wp_reset_postdata();
} else {
echo '<tr><td colspan="7">投稿が見つかりませんでした。</td></tr>';
}
echo '</tbody></table>';
echo '</div>';
}
botアクセスの除外をさらに強化する方法
Googleなどの検索エンジンbotのアクセスがPVに含まれてしまうと、実際のアクセス数より多くなります。
私は現在は以下の簡単なbot除外をしています。
if ( preg_match('/bot|crawl|slurp|spider/i', $ua) ) return;
より厳密にbotを除外したい場合、次のように多くのbotを指定することが可能です。
$bot_list = '/bot|crawl|slurp|spider|mediapartners|Googlebot|bingbot|Yahoo! Slurp|AhrefsBot|SemrushBot|MJ12bot/i';
if ( preg_match($bot_list, $ua) ) return;
この設定により、さらに正確な「人間によるPV」だけを集計できます。
自分のアクセスをPVカウントから除外する方法(複数の方法から選べます)
サイト運営者が自分のPVをカウントしてしまうと、本来の訪問者数が分からなくなってしまいます。
ここでは、自分のアクセスを除外するための方法を5つ紹介します。
あなたの運営スタイルに合った方法を選んでください。
方法①:ログイン中のユーザー全員を除外(最も簡単)
現在のコードではこの方法を採用しています。
ログインしているユーザーのアクセスをすべて除外します。一人運営の場合に最適です。
if ( is_user_logged_in() ) return;
方法②:管理者や編集者だけを除外(会員サイト向け)
管理者や編集者など、投稿できる権限を持つユーザーのみを除外します。
if ( current_user_can('edit_posts') ) return;
方法③:特定のユーザーIDを指定して除外
特定のユーザーだけを指定して除外することができます。
$user = wp_get_current_user(); $exclude_user_ids = [1, 2, 3];
// 除外するユーザーID if ( in_array($user->ID, $exclude_user_ids) ) return;
方法④:IPアドレスで除外(ログイン不要)
IPアドレスを指定して、そのアクセスを除外します。
$exclude_ips = ['123.456.789.000']; // あなたのIPアドレス if ( in_array($_SERVER['REMOTE_ADDR'], $exclude_ips) ) return;
方法⑤:Cookieで除外する方法(応用編)
特定のブラウザだけをサイト管理専用として除外します。
専用Cookieの設定(管理者専用ページに追加)
setcookie('exclude_pv', '1', time() + (86400 * 30), "/"); // 30日間有効
PVカウントでCookieを判定
if ( isset($_COOKIE['exclude_pv']) && $_COOKIE['exclude_pv'] === '1' ) return;
結局どの方法がおすすめ?
方法 | 状況 |
---|---|
ログイン中全員除外 | 一人運営、会員機能なし |
投稿権限ユーザーのみ除外 | 会員機能あり、ログインした読者はカウントしたい |
特定ユーザーIDで除外 | 少人数で管理する場合 |
IPアドレスで除外 | ログインせずに閲覧する場合 |
Cookieで除外 | 特定のブラウザを使う場合 |
あなたの運営環境にぴったりなものを選んでください。
表示を工夫する(昨日のPVを見やすく表示)
昨日のPVを表示するだけでなく、昨日のPVが多かった順に並び替えられるようにすると、さらに見やすく便利になります。
管理画面の並び替えコードに以下を追加します。
case 'yesterday': $args['meta_key'] = $yesterday_key; $args['orderby'] = 'meta_value_num'; $args['order'] = 'DESC'; break;
ドロップダウンの選択肢にも追加します。
'yesterday' => '昨日PV順',
これで、昨日人気があった記事を簡単に確認できます。
このPVカウンターを使うメリットとデメリット
最後に、この自作PVカウンターを導入するメリットとデメリットを整理し、実際に使った感想をまとめました。
メリット | デメリット |
---|---|
プラグイン不要でサイトが軽い 細かく自由にカスタマイズ可能 正確なPV数を把握できる(自分やbotのアクセスを除外) 管理画面で手軽に確認可能 | 詳細な分析はGoogleアナリティクスに劣る コードを扱うため、完全な初心者にはやや難しい |
function.phpに手を加える自信がなかったり、或いはきちんと詳細にアクセスを解析したいという方には、専用のものを使用するのが理想的です。
「PV数を知るだけじゃなく、リライトや収益アップに繋げたい」「でもGoogleアナリティクスは難しい」と感じる方には、
GA4をビジュアルで分かりやすく解析してくれるツールがおすすめです。
▶ GA4のデータを可視化&自動レポート【DeeBoard】
【DeeBoard】
実際に使った感想(まとめ)
自作PVカウンターを運用した結果、毎日のPV数が管理画面から一目で確認できるため、記事作成や運営のモチベーションが向上しました。
- 毎日のPV数が簡単に把握できる
- 昨日の人気記事が一目瞭然
- 自分のアクセスが除外されているため、訪問者数が正確でわかりやすい
「プラグインは使いたくないけど正確にPV数を知りたい」という方には非常におすすめです。
PV数管理で迷った際はぜひ導入してみてください〜