先に結論です。エージェントAI(Claude Code)に「アフィリエイトリンクのクリック率を測りたい」と頼んだら、10分後、WordPressの管理画面に「広告計測」というメニューが増えていました。コードは1行も書いていません。

広告のクリック計測は、Cocoonにない機能です。今回はデザインの模様替えではなく、テーマに「ない機能」そのものをAIに作ってもらいました。頼んだ言葉も、できあがった仕組みも、コピペで使えるコードも、全部この記事に載せます。

いままではA8の管理画面とにらめっこして、どの記事のリンクが押されたのか謎解きしてました
Cocoonでは、広告のクリック数が見えません
Cocoonには「アフィリエイトタグ」という便利な仕組みがあります。広告コードを1箇所に登録して、記事には エックスサーバーを公式サイトでチェック
と書くだけ。差し替えも一括でできる、よくできた機能です。![]()
ただ、この画面に数字は何もありません。何回表示されて、何回押されたのか。それはA8などASP側のレポートを見に行くことになります。そしてASPのレポートで分かるのは合計だけ。「どの記事の、どのリンクが押されたのか」は、どこにも出てきません。

この機能、望んでいる人はずっといました。Cocoon公式フォーラムには2018年4月、「アフィリエイトタグのクリック数測定」という要望が投稿されています。「管理画面で管理出来れば非常に便利ですので」という、いま読んでもまったく同感のお願いです(該当スレッド)。
開発者のわいひらさんの返信は「クリック集計も視野に入れた設計にはなっています」。ただ、簡単な機能ではなく開発コストが重い、という内容でした。それから8年、この機能は実装されていません。無料テーマの限られた開発リソースを何に使うかを考えれば、責められる話ではないと思います。
ちなみに有料テーマのSWELLには、広告タグごとの表示回数・クリック率の計測機能が最初から付いています。「計測したいならSWELLへ」という乗り換え記事が多いのも、この差があるからです。
AIへの頼み方は、今回も一言でした
頼んだ言葉は、これだけです。
Cocoonでアフィリエイトリンクのクリック率を測りたい。SWELLみたいにWordPressの管理画面で見られるようにして
仕様書もコードも渡していません。この実演をやっているのは、ファイルの編集や動作確認まで代行してくれるエージェントAIです(このブログの主役。詳しくはこちらの記事)。ChatGPTなど対話型AIでのやり方は、記事の最後にまとめました。
10分後、管理画面に「広告計測」が増えていました
頼んだのが朝の7時54分。管理画面に計測の数字が表示されたのが8時4分。ちょうど10分でした。その間にAIがやったことは、こうです。
- Cocoonの仕組みを調査(公式の公開ソースを読みにいく)
- 計測プログラムを書く(表示とクリックを数えて保存する仕組み)
- 子テーマのfunctions.phpに追記して保存
- 実際にページを開いてクリックし、数字が増えることを確認
できあがった「広告計測」でできることは3つ。広告ごとの表示回数・クリック数・CTR(クリック率)が見えること。自分がログインして操作した分は数えないこと。ボタン1つでデータをリセットできること。表示回数は「画面に半分以上見えたら1回」と数える方式で、ページの下の方にあって読まれなかった広告はカウントされません。地味にえらいポイントです。
AIが見つけた、8年前の伏線
作業中のAIの報告に、面白い発見がありました。Cocoonが出力する広告リンクのHTMLを見ると、こうなっています。
<a rel="nofollow" data-atag-id="2" data-post-id="66" href="https://px.a8.net/...">
data-atag-id(どの広告タグか)と data-post-id(どの記事に貼られたか)。計測に必要な情報が、最初からリンクに埋め込まれていたんです。フォーラムでわいひらさんが書いていた「クリック集計も視野に入れた設計」は、言葉どおり本当にコードの中に用意されていました。
つまり今回AIが作ったのは、8年前から敷かれていたレールの上を走る列車のようなものです。Cocoonの設計に、AIが計測部分を接続しただけ。だからHTMLもデザインも一切いじらずに済んでいます。
「どの記事の、どのリンクか」も、追加の一言で付きました
最初の版の集計は広告ごとの合計だけでした。そこで「どのページの、どのリンクが押されたかも分かるの?」と聞いてみたところ、さきほどのdata-post-idを使う拡張を提案してきて、5分後には記事別の内訳が表示されるようになりました。

「同じ広告を3記事に貼ってるけど、押されてるのはどれ?」が一覧で分かるようになりました
冒頭の画面がその状態です。広告タグごとの合計の下に、記事タイトル付きの内訳がぶら下がる2階建て。機能が足りなければ、その場で頼んで足せる。既製品との一番の違いはここだと感じます。
今回は作っていないことも、書いておきます
同じ記事の中に同じ広告タグを2箇所貼った場合、どちらが押されたかまでは今回は作り込んでいません(ちなみに既製品のSWELLの広告タグ計測も、ここはタグ単位です)。頼めば「記事上のボタン」「記事下のボタン」を区別する拡張も作れます。まずは使ってみて、必要になったら一言足す。あとから足せるのが自作の良さです。
ロボットのアクセスを厳密に見分ける仕組みも入れていません。ここは作り込むほど大掛かりになるわりに、得られるものが少ない場所。数字は広告の置き場所を考えるための目安と割り切るのが、手間と釣り合うと思います。
それと、自作した機能の面倒を見るのは自分です。テーマの大型更新で動かなくなったら、直すのも自分(とAI)。既製の有料テーマは、この面倒ごとをお金で開発者に預ける仕組みなんだと、作ってみてよく分かりました。
判定: 🟡 工夫が要った
カスタマイズ実例の恒例、3段階判定です。
- 🟢 AIで一発 = 一言頼んで、修正なしで完成
- 🟡 工夫が要った = 頼み直しやプロンプトの工夫が必要だった
- 🔴 別の道が早い = 手動やスキンのほうが早い
ボタン記事のときに「大物になるほど🟡や🔴が出てくるはず。出たら出たで、そのまま書きます」と書きました。今回がその第1号です。最初の版はスマホで広告ボタンの表示が崩れて、画面の様子を言葉で伝えて作り直してもらいました。だから🟡。それでも、コードを1行も書かずにテーマの機能が1つ増えた事実は変わりません。
🔴側の線引きも書いておきます。数字を見る場所がASPのレポートで足りているなら、作る必要はありません。この道が活きるのは、「どの記事のどの広告が働いているか」を見て、貼り場所や文言を変えていきたい人です。
ChatGPTなど、対話型AIでやる場合
最後に、チャット画面で使う対話型AIでのやり方です。この記事の実演はファイルの書き込みや動作確認までAIがやりましたが、対話型AIでも作れるものは同じです。違いは、出てきたコードを自分で子テーマのfunctions.phpに貼ること。作業は1回増えますが、やることは貼り付けだけです。
頼み方の文例です。そのままコピペで使えます。
WordPressテーマCocoonで、アフィリエイトタグ [affi id=N] の表示回数とクリック数を計測して、管理画面で見られるようにするPHPコードを書いてください。子テーマのfunctions.phpに貼って使います。Cocoonの広告リンクには data-atag-id と data-post-id という属性が付いているので、それを使ってください。
貼るときの注意は3つです。必ず子テーマのfunctions.phpに貼ること(親テーマはアップデートで消えます)。貼る前にCocoon設定のバックアップ機能でバックアップを取ること。保存ボタンを押したら「ファイルの編集に成功しました」の表示まで確認すること。WordPressはコードに致命的な間違いがあると自動で元に戻してくれるので、画面が真っ白のまま戻れなくなる事故は起きにくくなっています。
参考までに、今回AIが書いたコードの全文も置いておきます。これをそのまま貼っても動きますし、対話型AIに「これを下敷きに」と渡すのも近道です。
<?php //子テーマ用関数
if ( !defined( 'ABSPATH' ) ) exit;
//子テーマ用のビジュアルエディタースタイルを適用
add_editor_style();
//以下に子テーマ用の関数を書く
// ===== 広告クリック計測(AIAD)ここから =====
// [affi id=N] の表示回数・クリック数を、広告タグ×記事の単位で計測して管理画面「広告計測」で見る
// v3: Cocoonがリンクに付けている data-atag-id(広告タグID)と data-post-id(貼られた記事ID)を
// そのまま計測に使う。HTMLには一切手を加えない
// 1) 集計の保存と加算(wp_options・キーは「タグID_記事ID」)
function aiad_get_stats() {
$s = get_option('aiad_stats', array());
return is_array($s) ? $s : array();
}
function aiad_add_hit($tag, $pid, $type) {
$s = aiad_get_stats();
$key = intval($tag) . '_' . intval($pid);
if (!isset($s[$key]) || !is_array($s[$key])) $s[$key] = array('imp' => 0, 'click' => 0);
$s[$key][$type]++;
update_option('aiad_stats', $s, false);
}
// 2) 計測の受け口(REST API)
add_action('rest_api_init', function () {
register_rest_route('aiad/v1', '/hit', array(
'methods' => 'POST',
'permission_callback' => '__return_true',
'callback' => function ($req) {
$tag = intval($req->get_param('id'));
$pid = intval($req->get_param('pid'));
$type = $req->get_param('type');
if ($tag < 1 || $tag > 99999 || $pid < 0 || !in_array($type, array('imp', 'click'), true)) {
return new WP_Error('aiad_bad_request', 'invalid', array('status' => 400));
}
aiad_add_hit($tag, $pid, $type);
return array('ok' => true);
},
));
});
// 3) フロント計測JS(ログイン中=自分は出力しない→カウントされない)
add_action('wp_footer', function () {
if (is_user_logged_in()) return;
?>
<script id="aiad-tracker">
(function () {
var els = document.querySelectorAll('[data-atag-id]');
if (!els.length) return;
var url = '<?php echo esc_url_raw(rest_url('aiad/v1/hit')); ?>';
var seen = {};
function send(id, pid, type) {
try {
var fd = new FormData();
fd.append('id', id);
fd.append('pid', pid);
fd.append('type', type);
navigator.sendBeacon(url, fd);
} catch (e) {}
}
function idsOf(el) {
return {
tag: el.getAttribute('data-atag-id'),
pid: el.getAttribute('data-post-id') || '0'
};
}
if ('IntersectionObserver' in window) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (en) {
if (en.isIntersecting) {
var ids = idsOf(en.target);
var k = ids.tag + '_' + ids.pid;
if (!seen[k]) {
seen[k] = 1;
send(ids.tag, ids.pid, 'imp');
io.unobserve(en.target);
}
}
});
}, { threshold: 0.5 });
els.forEach(function (el) { io.observe(el); });
}
document.addEventListener('click', function (ev) {
var t = ev.target && ev.target.closest ? ev.target.closest('[data-atag-id]') : null;
if (t) {
var ids = idsOf(t);
send(ids.tag, ids.pid, 'click');
}
}, true);
})();
</script>
<?php
});
// 4) 管理画面「広告計測」(タグ別合計+記事別内訳+リセット)
add_action('admin_menu', function () {
add_menu_page('広告計測', '広告計測', 'manage_options', 'aiad-stats', 'aiad_render_admin', 'dashicons-chart-bar', 58);
});
function aiad_ctr($imp, $clk) {
return $imp ? round($clk / $imp * 100, 2) . '%' : '—';
}
function aiad_render_admin() {
if (isset($_POST['aiad_reset']) && check_admin_referer('aiad_reset_action')) {
delete_option('aiad_stats');
echo '<div class="updated"><p>計測データをリセットしました。</p></div>';
}
$s = aiad_get_stats();
// 「タグID_記事ID」形式のキーだけ集計(旧形式のテスト値は無視)
$groups = array();
foreach ($s as $key => $d) {
if (strpos($key, '_') === false || !is_array($d)) continue;
list($tid, $pid) = explode('_', $key, 2);
$groups[intval($tid)][intval($pid)] = array('imp' => intval($d['imp']), 'click' => intval($d['click']));
}
ksort($groups);
echo '<div class="wrap"><h1>広告クリック計測</h1>';
echo '<p>[affi id=N] で貼った広告の表示回数とクリックを、広告タグ×記事の単位で自動集計します(ログイン中の操作は数えません)。</p>';
echo '<table class="widefat striped" style="max-width:760px">';
echo '<thead><tr><th>広告タグ/記事</th><th>表示回数</th><th>クリック</th><th>CTR</th></tr></thead><tbody>';
if (!$groups) {
echo '<tr><td colspan="4">まだ計測データがありません。</td></tr>';
}
foreach ($groups as $tid => $rows) {
$name = 'タグID ' . $tid;
if (function_exists('get_affiliate_tag') && ($r = get_affiliate_tag($tid))) {
if (!empty($r->title)) $name = $r->title;
elseif (!empty($r->name)) $name = $r->name;
}
$timp = 0; $tclk = 0;
foreach ($rows as $d) { $timp += $d['imp']; $tclk += $d['click']; }
echo '<tr style="background:#f0f6fc;font-weight:bold"><td>' . esc_html($name) . '(合計)</td><td>' . $timp . '</td><td>' . $tclk . '</td><td>' . esc_html(aiad_ctr($timp, $tclk)) . '</td></tr>';
ksort($rows);
foreach ($rows as $pid => $d) {
if ($pid && ($title = get_the_title($pid))) {
$label = '<a href="' . esc_url(get_permalink($pid)) . '" target="_blank" rel="noopener">' . esc_html($title) . '</a>';
} elseif ($pid) {
$label = '投稿ID ' . $pid;
} else {
$label = '(記事不明)';
}
echo '<tr><td style="padding-left:2em">' . $label . '</td><td>' . intval($d['imp']) . '</td><td>' . intval($d['click']) . '</td><td>' . esc_html(aiad_ctr($d['imp'], $d['click'])) . '</td></tr>';
}
}
echo '</tbody></table>';
echo '<form method="post" style="margin-top:12px" onsubmit="return confirm(\'計測データを全部消します。よろしいですか?\');">';
wp_nonce_field('aiad_reset_action');
submit_button('計測データをリセット', 'delete', 'aiad_reset', false);
echo '</form></div>';
}
// 5) ABテスト [aiad_ab a=X b=Y](AとBの広告タグをランダム出し分け・集計は上の仕組みに相乗り)
add_shortcode('aiad_ab', function ($atts) {
$a = isset($atts['a']) ? intval($atts['a']) : 0;
$b = isset($atts['b']) ? intval($atts['b']) : 0;
if (!$a || !$b) return '';
$pick = mt_rand(0, 1) ? $a : $b;
return do_shortcode('[affi id=' . $pick . ']');
});
// ===== 広告クリック計測(AIAD)ここまで =====
まとめ: 「テーマにない機能」は、諦める理由にならなくなった
- 頼んだのは一言。「クリック率を測りたい。管理画面で見たい」
- 10分で管理画面に「広告計測」が増えた。コードは書いていない
- 「どの記事のどのリンクか」は、追加の一言で5分
8年前の要望スレッドを読み返すと、欲しかったものはシンプルで、「管理画面で見たい」それだけなんですよね。テーマの機能一覧に載っていないことは、もう諦める理由になりません。欲しい機能を言葉にできれば、それが仕様書になります。


コメント