GEMINI LABEN
NANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定ですNANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定です
記事一覧/API / SDK
API / SDK/2026-05-13中級

AdMob収益の急落を朝8時に知る仕組みをGemini APIで作った

AdMob収益の異常をGemini APIのFunction Callingで自動検知する仕組みの実装方法を、個人開発10年以上の実体験をもとに解説します。前日比の閾値設定から原因分析まで、実際に動くPythonコードで紹介します。

admob4gemini-api279function-calling20python103indie-dev21monetization10

ある月曜日の朝、AdMobのダッシュボードを開いたら前日比で収益が62%下落していました。原因を特定し始めたのはその時点からで、対処が完了したのは4時間後のことです。あの4時間の損失は、1時間以内に検知できていれば半分以下に抑えられていたはずだと今でも思います。

2014年から個人でモバイルアプリを開発し、壁紙系アプリを中心に累計5,000万ダウンロードを達成してきました。AdMobの月収はピーク時で150万円を超えていたこともあります。そうなると、収益の異常をどれだけ早く気づけるかは、事業継続の問題と直結してきます。

Gemini APIのFunction Callingを使って、AdMob Reporting APIから収益データを自動取得し、前日比・前週比で異常を検知したら即座にSlack通知を送る仕組みを作りました。今回はその実装を紹介します。

なぜGemini APIのFunction Callingを選んだか

最初は単純なシェルスクリプトでAdMob APIを叩き、前日比が一定閾値を下回ったらアラートを送る実装を試みました。ただ、固定閾値では誤検知が多く使いものになりませんでした。月曜日は週明け特有の低水準があり、祝日前後は変動が大きく、キャンペーン期間中は逆に急増するからです。

Gemini APIを間に挟むことで、単純な数値比較ではなく「この変動は通常の曜日変動の範囲内か、それとも真の異常か」という判断をGeminiに委ねることができます。コンテキストを渡してGeminiに診断させる、という使い方です。

Functionの定義 → AdMob APIの実行 → Geminiによる結果解析 → 異常時にSlack通知、という流れです。

AdMob Reporting APIの準備

まずGoogle Cloud ConsoleでAdMob APIを有効化し、サービスアカウントを作成します。サービスアカウントのJSONキーをダウンロードして環境変数に設定します。

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json"
export ADMOB_PUBLISHER_ID="pub-XXXXXXXXXXXXXXXX"  # あなたのパブリッシャーID
export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"

AdMob Reporting APIでは「ネットワークレポート」と「メディエーションレポート」の2種類が取得できます。今回は収益の総額を確認するためにネットワークレポートを使用します。

from google.oauth2 import service_account
from googleapiclient.discovery import build
from datetime import datetime, timedelta
import os
 
def get_admob_credentials():
    """AdMob APIの認証情報を取得する"""
    credentials = service_account.Credentials.from_service_account_file(
        os.environ["GOOGLE_APPLICATION_CREDENTIALS"],
        scopes=["https://www.googleapis.com/auth/admob.readonly"]
    )
    return credentials
 
def fetch_admob_revenue(days_ago: int = 1) -> dict:
    """
    AdMob Reporting APIから指定日の収益データを取得する
 
    Args:
        days_ago: 何日前のデータを取得するか(1=昨日)
 
    Returns:
        {"date": "YYYY-MM-DD", "revenue": float, "impressions": int, "ecpm": float}
    """
    credentials = get_admob_credentials()
    service = build("admob", "v1", credentials=credentials)
 
    target_date = datetime.now() - timedelta(days=days_ago)
    date_str = target_date.strftime("%Y%m%d")
    
    publisher_id = os.environ["ADMOB_PUBLISHER_ID"]
    
    report_request = {
        "reportSpec": {
            "dateRange": {
                "startDate": {
                    "year": target_date.year,
                    "month": target_date.month,
                    "day": target_date.day
                },
                "endDate": {
                    "year": target_date.year,
                    "month": target_date.month,
                    "day": target_date.day
                }
            },
            "metrics": ["ESTIMATED_EARNINGS", "IMPRESSIONS", "IMPRESSION_RPM"],
            "dimensions": [],  # 集計(アプリ・広告ユニット別に分解しない)
        }
    }
    
    response = service.accounts().networkReport().generate(
        parent=f"accounts/{publisher_id}",
        body=report_request
    ).execute()
    
    # レスポンスのパース
    revenue = 0.0
    impressions = 0
    ecpm = 0.0
    
    for row in response:
        if "row" in row:
            metrics = row["row"].get("metricValues", {})
            revenue += float(metrics.get("ESTIMATED_EARNINGS", {}).get("microsValue", 0)) / 1_000_000
            impressions += int(metrics.get("IMPRESSIONS", {}).get("integerValue", 0))
            ecpm = float(metrics.get("IMPRESSION_RPM", {}).get("doubleValue", 0))
    
    return {
        "date": target_date.strftime("%Y-%m-%d"),
        "revenue": round(revenue, 2),
        "impressions": impressions,
        "ecpm": round(ecpm, 4)
    }

Gemini APIのFunction Callingで収益データを取得・分析する

Function Callingを使って、Geminiが必要なデータを自ら取得しながら分析できるようにします。

import google.generativeai as genai
import json
import os
 
genai.configure(api_key=os.environ["GEMINI_API_KEY"])
 
# Geminiが呼び出せるFunctionを定義
tools = [
    {
        "function_declarations": [
            {
                "name": "get_revenue_data",
                "description": "AdMob収益データを取得する。days_agoで何日前のデータかを指定する",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "days_ago": {
                            "type": "integer",
                            "description": "何日前のデータを取得するか。1=昨日、2=一昨日、7=1週間前"
                        }
                    },
                    "required": ["days_ago"]
                }
            }
        ]
    }
]
 
def run_revenue_analysis():
    """
    Gemini APIのFunction Callingを使って収益異常を分析する
    """
    model = genai.GenerativeModel(
        model_name="gemini-2.5-pro-preview-05-06",
        tools=tools
    )
    
    today_weekday = datetime.now().strftime("%A")  # 曜日情報をコンテキストで渡す
    
    # Geminiへの分析依頼
    initial_prompt = f"""
    今日は{today_weekday}です。
    get_revenue_data関数を使って以下を取得し、収益の異常がないか分析してください:
    
    1. 昨日(1日前)の収益データ
    2. 2日前の収益データ
    3. 3日前の収益データ
    4. 1週間前(7日前)の収益データ
    5. 2週間前(14日前)の収益データ
    
    分析の観点:
    - 前日比での変化率
    - 前週同曜日との比較
    - eCPM(1000インプレッション単価)の変化
    - 異常と判断する場合はその根拠と考えられる原因
    
    最終的に以下のJSON形式で結果を返してください:
    {{
        "is_anomaly": true/false,
        "severity": "low/medium/high",
        "yesterday_revenue": 昨日の収益(ドル),
        "change_from_prev_day": 前日比変化率(%),
        "change_from_last_week": 前週同曜日比変化率(%),
        "summary": "状況の要約(日本語で1〜2文)",
        "possible_causes": ["考えられる原因のリスト"]
    }}
    """
    
    chat = model.start_chat()
    response = chat.send_message(initial_prompt)
    
    # Function Callのループ処理
    while response.candidates[0].content.parts[0].HasField("function_call"):
        fc = response.candidates[0].content.parts[0].function_call
        
        if fc.name == "get_revenue_data":
            days_ago = fc.args["days_ago"]
            result = fetch_admob_revenue(days_ago=days_ago)
            print(f"  📊 {result['date']}: ${result['revenue']:.2f} ({result['impressions']:,} impressions, eCPM ${result['ecpm']:.2f})")
            
            # Function Callの結果をGeminiに返す
            response = chat.send_message(
                genai.protos.Content(
                    parts=[genai.protos.Part(
                        function_response=genai.protos.FunctionResponse(
                            name="get_revenue_data",
                            response={"result": result}
                        )
                    )]
                )
            )
    
    # 最終分析結果を取得
    final_text = response.text
    
    # JSONブロックを抽出
    import re
    json_match = re.search(r'\{.*\}', final_text, re.DOTALL)
    if json_match:
        return json.loads(json_match.group())
    
    return None
 
# 実行例
print("🔍 AdMob収益分析を開始します...")
analysis = run_revenue_analysis()
 
if analysis:
    if analysis["is_anomaly"]:
        severity_emoji = {"low": "🟡", "medium": "🟠", "high": "🔴"}.get(analysis["severity"], "⚠️")
        print(f"\n{severity_emoji} 異常検知: {analysis['summary']}")
        print(f"  昨日の収益: ${analysis['yesterday_revenue']:.2f}")
        print(f"  前日比: {analysis['change_from_prev_day']:+.1f}%")
        print(f"  前週同曜日比: {analysis['change_from_last_week']:+.1f}%")
        print(f"  考えられる原因: {', '.join(analysis['possible_causes'])}")
    else:
        print(f"\n✅ 収益は正常範囲内です。昨日の収益: ${analysis['yesterday_revenue']:.2f}")

期待される実行例(異常検知時):

🔍 AdMob収益分析を開始します...
  📊 2026-05-12: $42.30 (128,500 impressions, eCPM $0.33)
  📊 2026-05-11: $98.20 (182,300 impressions, eCPM $0.54)
  📊 2026-05-10: $91.80 (179,100 impressions, eCPM $0.51)
  📊 2026-05-06: $95.40 (180,200 impressions, eCPM $0.53)
  📊 2026-04-29: $93.10 (177,800 impressions, eCPM $0.52)

🔴 異常検知: 昨日の収益が前日比57%減少。eCPMも0.54→0.33と急落しています。
  昨日の収益: $42.30
  前日比: -56.9%
  前週同曜日比: -55.7%
  考えられる原因: インプレッション数の減少, eCPMの急落, 広告埋め込みの問題または広告主予算の消化

実際に動かして気づいたこと

Function Callingの実装で最初に詰まったのは、Function Callのループ処理でした。Geminiは1回のリクエストで複数のFunction Callを行うことがあります。「昨日と1週間前のデータを取得してください」と頼むと、2回分のFunction Callをまとめて返してくることがあり、そのハンドリングが必要です。

また、AdMob Reporting APIのレスポンスで収益がマイクロ通貨(microsValue)で返ってくる点も注意が必要です。$1.00 = 1,000,000 microsValue という変換を忘れると、数値が100万倍になります。私は最初これに気づかず、「収益が突然100万ドルになった」とシステムが検知する事態になりました。

もう一つ気づいたのは、GeminiはFunction Callingの結果に基づいて追加のデータを要求することがあるという点です。最初は3日分だけ取得していましたが、Geminiが「前週のデータも確認したい」と判断して追加のFunction Callを要求してくることがあります。上記コードのようにループで処理するのが重要です。

詳細な実装パターンについてはGemini APIのFunction Calling完全ガイドも参照してみてください。

Slack通知まで組み合わせる

分析結果を受け取ったら、requestsライブラリでSlackのIncoming Webhookに送信するだけです。

import requests
 
def send_slack_alert(analysis: dict, webhook_url: str):
    """異常検知時にSlackに通知する"""
    if not analysis["is_anomaly"]:
        return
    
    severity_color = {"low": "#FFC107", "medium": "#FF5722", "high": "#F44336"}.get(
        analysis["severity"], "#9E9E9E"
    )
    
    payload = {
        "attachments": [{
            "color": severity_color,
            "title": f"AdMob収益アラート [{analysis['severity'].upper()}]",
            "text": analysis["summary"],
            "fields": [
                {"title": "昨日の収益", "value": f"${analysis['yesterday_revenue']:.2f}", "short": True},
                {"title": "前日比", "value": f"{analysis['change_from_prev_day']:+.1f}%", "short": True},
                {"title": "前週同曜日比", "value": f"{analysis['change_from_last_week']:+.1f}%", "short": True},
                {"title": "考えられる原因", "value": "\n".join(f"• {c}" for c in analysis["possible_causes"]), "short": False}
            ],
            "footer": "AdMob Anomaly Detector powered by Gemini API",
            "ts": int(datetime.now().timestamp())
        }]
    }
    
    requests.post(webhook_url, json=payload)
    print("✅ Slack通知を送信しました")

このコードをCloud RunかHerokuに乗せて、毎朝8時にCronで実行するように設定しています。朝の最初のコーヒーを飲む前に、収益が正常かどうかの通知が届く仕組みです。

この仕組みを導入してからは、収益の急落を最大4時間以内に検知できるようになりました。以前と比べて、対応の初動が格段に早くなっています。同じように個人でアプリを運営している方の参考になれば嬉しいです。

Pythonの非同期処理との組み合わせで複数アプリを並行監視する方法についても、別途まとめています。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API / SDK2026-06-23
Gemini 3.2 Pro の Function Calling を iOS/Android アプリに統合する実装パターン
Gemini 3.2 Pro の Function Calling を iOS/Android アプリに統合する実装ガイド。SwiftUI・Kotlin 両対応の動くコード例と、個人開発の壁紙アプリで実証した本番設計パターン(コスト・レイテンシ・段階ロールアウト・回帰テスト)を解説します。
API / SDK2026-05-25
Gemini 2.5 Flash で個人開発アプリのヘルプ翻訳を3ヶ月運用した所感
iOS / Android の個人開発アプリで提供してきた多言語ヘルプを、Gemini 2.5 Flash を中心とした翻訳パイプラインで運用し始めて3ヶ月が経ちました。Pro との使い分け、訳文ドリフトへの備え、AdMob 収益に与えた小さな効果まで、運用ログに残った所感を淡々と記録します。
API / SDK2026-05-24
Gemini File API に半年分の AppStore レビュー CSV を投げて分類した2週間の運用ログ
App Store Connect から書き出した半年分のレビュー CSV を、Gemini File API に直接投げて分類・要約させる仕組みを2週間動かしました。Batch Mode との使い分け、トークン消費の落とし所、つまずいたところを淡々と記録します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →