ある朝、いつものように AdMob のダッシュボードを開いたら、前日の収益の欄がグレーで表示されていました。「無効なトラフィック(Invalid Traffic)として調整されました」という趣旨の注記が添えられていて、心臓がすこし冷たくなったのを覚えています。
心当たりがすぐには浮かびませんでした。不正なことは何もしていない。けれど数字は確かに差し引かれている。この「原因がわからないまま収益が減る」という状態が、AdMob を使っていていちばん落ち着かない瞬間だと感じます。
個人開発で複数のアプリを並行して運用していると、この手のヒヤリは何度か通り過ぎます。今日は、その経験から見えてきた「無効なトラフィックの多くは防げる」という話を、具体的な仕組みに落として共有します。
無効なトラフィックは「不正」だけの話ではない
無効なトラフィックと聞くと、悪意ある大量クリックのような話を想像しがちです。もちろんそれも含まれますが、個人開発者が実際にぶつかるのは、もっと地味で身近な原因がほとんどです。
具体的には、開発中に自分で本番の広告を表示してタップしてしまう、身内にテスト版を配って何気なく広告を触ってもらう、同じ端末で不自然な回数だけ広告が呼ばれる、といったものです。Google 側は「人間の自然な操作ではない」と判断したインプレッションやクリックを機械的に差し引きます。悪意の有無は関係なく、パターンとして弾かれるのです。
つまり、無効なトラフィックの大半は「自分の運用の隙間」から生まれます。逆に言えば、隙間を塞ぐ仕組みさえ用意しておけば、そのほとんどは起きません。
いちばん多い原因は、開発中に自分の本番広告を叩くこと
Rork や Expo で作ったアプリを手元で動かしながら調整していると、画面確認のために広告を何度も表示します。このとき本番の広告ユニットが動いていると、あなた自身のタップやインプレッションがそのまま本番データに混ざります。これが最も起きやすく、かつ最も見落とされやすい原因です。
対策はふたつだけです。ひとつは自分の端末を「テスト端末」として登録すること。もうひとつは、開発ビルドでは必ずテスト用の広告ユニットに切り替えることです。両方を最初に仕込んでおけば、開発中に本番データを汚す経路がほぼ塞がれます。
react-native-google-mobile-ads を使う場合、初期化のタイミングでテスト端末を登録し、広告ユニット ID を環境で出し分けます。
import mobileAds, { TestIds, MaxAdContentRating } from 'react-native-google-mobile-ads';
// 1) 自分の端末をテスト端末として登録する
// → 本番の広告ユニットを指定していても、この端末にはテスト広告だけが返る
async function initAds() {
await mobileAds().setRequestConfiguration({
// 実機の端末IDは初回起動時のログに出力される(後述)
testDeviceIdentifiers: ['EMULATOR', 'YOUR_DEVICE_ID'],
maxAdContentRating: MaxAdContentRating.PG,
});
await mobileAds().initialize();
}
// 2) 広告ユニットIDは開発ビルドと本番で必ず出し分ける
// __DEV__ は Expo / React Native が自動で持つ開発フラグ
export const BANNER_UNIT_ID = __DEV__
? TestIds.BANNER
: 'ca-app-pub-0000000000000000/1111111111';
export const INTERSTITIAL_UNIT_ID = __DEV__
? TestIds.INTERSTITIAL
: 'ca-app-pub-0000000000000000/2222222222';ここで大切なのは、__DEV__ による出し分けを「広告ユニットを参照する全ての場所」で徹底することです。バナーは切り替えたのにインターステシャルだけ本番 ID が残っていた、というのが典型的な抜け穴になります。定数を一箇所にまとめ、各画面はそこから読むようにしておくと、抜けが起きにくくなります。
端末 ID は、テスト端末として未登録の状態でアプリを一度起動すると、コンソールに次のような行が出力されます。この文字列を testDeviceIdentifiers に貼り付けます。
I/Ads: Use RequestConfiguration.Builder.setTestDeviceIds(Arrays.asList("33BE2250B43518CCDA7DE426D04EE231"))
to get test ads on this device.複数アプリを運用するなら「事故が起きない仕組み」に寄せる
アプリが1本なら、注意して手作業で切り替えるだけでも何とかなります。けれど運用するアプリが増えるほど、「今どのビルドを触っているか」の意識だけに頼るのは危うくなります。人の注意力は、疲れているときや締め切り前に真っ先に落ちるからです。
そこで、注意ではなく仕組みで守れるところは仕組みに寄せます。私が複数アプリで共通して効いていると感じる予防策を、効き目の大きい順に並べます。
| 予防策 | 防げる事故 | 効き目 |
|---|---|---|
| __DEV__ で広告ユニットを出し分け | 開発中に本番広告を自分で叩く | 大(最頻出の原因を根から断つ) |
| 自分と身内の端末をテスト端末登録 | 手元・配布先での自然なタップ混入 | 大 |
| 広告ユニットIDを定数ファイルに集約 | 一部画面だけ本番IDが残る抜け穴 | 中 |
| 頻度上限(フリークエンシーキャップ) | 同一端末での不自然な連続表示 | 中 |
| リリース前チェックリストに広告確認を追加 | 切り替え忘れの見落とし | 中 |
このうち、複数アプリで最も費用対効果が高いのは「広告ユニット ID を定数ファイルに集約する」ことだと考えています。アプリごとにコードの書き方が少しずつ違っても、参照する入り口を1ファイルに固定しておけば、__DEV__ の出し分けをそこ1箇所で保証できます。新しいアプリを立ち上げるたびに、この定数ファイルをテンプレートとして持ち込むだけで済むようになります。
頻度上限については、無効なトラフィックの予防というより、そもそも過剰な広告表示を避ける設計の話でもあります。復帰時の体感を含めた設計は、別途AdMob オープンアプリ広告の頻度設計 — 復帰時の体感ストレスを抑えながら eCPM を取りに行く運用メモにまとめています。
警告や保留が来てしまったときの動き方
予防していても、原因不明の保留が来ることはあります。そのときに慌てて設定を大きく変えると、かえって状況が読めなくなります。私は次の順番で落ち着いて対応するようにしています。
まず、直近で自分がした操作を思い出します。新しい広告配置を入れた、テスト配布を増やした、特定の端末で繰り返し確認した、といった心当たりがないかを洗い出します。多くの場合、ここで原因の見当がつきます。
次に、心当たりのある経路を止めます。本番広告を触ってしまう導線があったなら、__DEV__ の出し分けやテスト端末登録を見直します。原因を残したまま様子見をしても、翌日も同じ調整が入るだけです。
そのうえで、Google の判定に対して明確な誤りがあると考える場合は、AdMob のヘルプから無効なトラフィックに関する問い合わせフォームで状況を伝えます。ここで大切なのは、感情ではなく「いつ・どの操作で・何が起きたか」を淡々と書くことです。調整自体は数日かけて反映・見直しされることが多いので、送った後は結果を待ちます。
最後に、同じ事故が二度起きないよう、原因と対策をリリース前チェックリストに1行足します。仕組みは、こうして事故のたびに少しずつ厚くなっていくものだと感じています。
まとめ
無効なトラフィックは、悪意のある攻撃よりも「自分の運用の隙間」から生まれることのほうが、個人開発では圧倒的に多い印象です。だからこそ、注意ではなく仕組みで防げます。
今日ひとつだけ手を動かすなら、広告ユニット ID を定数ファイルにまとめ、__DEV__ で本番とテストを出し分けるところから始めるのがおすすめです。最頻出の原因を、いちばん小さな労力で断てます。
私自身もまだ運用の中で学び続けている途中です。同じようにアプリを育てている方の、静かな安心の一助になれば嬉しいです。お読みいただきありがとうございました。