はじめに
こんにちは、トモニテで開発を担当している吉田です。
デジタル広告の運用において、広告パフォーマンスの分析とレポート作成は重要な業務の一つです。しかし、弊社では手動でレポートを作成しており、営業活動に集中する時間を削ってしまう課題がありました。
本記事では、Google Ad Manager(GAM)の REST API と BigQuery を連携させ、レポート作成を自動化するシステムの構築事例について、紹介します。
背景:セールスレポート作成の課題
ビジネス課題
セールスチームが Google Ad Manager の広告レポートを手動で作成する際、以下の課題に直面していました。
- レポート作成工数の嵩み: 現状 30 分〜1 時間程度の工数が発生
- データ抽出の複雑さ: GAM から直接データを取得する手間
- 営業活動時間の減少: レポート作成に時間を取られ、営業活動に集中できない
期待される成果
レポート作成の自動化により、以下の成果を期待しました。
- 工数削減: レポート作成時間の短縮
- 営業活動の強化: レポート作成時間を営業活動に充て、売上貢献の向上
- データ活用の効率化: BigQuery での SQL による柔軟なデータ抽出
技術選定:REST API の採用
既存システムの課題
社内の別サービスでは、Google Ad Manager の SOAP API を使用していました。しかし、以下の理由から REST API(現在 Beta 版)で実装することを決定しました。
項目 | SOAP API | REST API |
---|---|---|
実装の複雑さ | XML ベースで複雑 | JSON ベースでシンプル |
エラーハンドリング | 複雑な XML パースが必要 | 標準的な HTTP ステータスコード |
デバッグの容易さ | XML ログの可読性が低い | JSON ログで直感的 |
メンテナンス性 | 古い技術スタック | モダンな技術スタック |
ドキュメント | 限定的 | 豊富で分かりやすい |
REST API の選択理由
- 開発効率の向上: JSON ベースのシンプルな実装
- 保守性の向上: モダンな技術スタックによる将来性
- エラー処理の簡素化: 標準的な HTTP レスポンスの活用
- チーム開発の効率化: より直感的な API 設計
注意: Google Ad Manager REST API は現在 Beta 版のため、本番環境での使用には注意が必要です。API の仕様変更や制限事項について、公式ドキュメントを定期的に確認することをお勧めします。
システムアーキテクチャ
全体構成
- Cloud Run: メイン処理コンテナ
- GAM REST API を呼び出してレポートデータを取得
- 取得したデータを BigQuery に格納
- GAM REST API: 広告データの提供
- BigQuery: データの保存と分析
データフロー
- 実行開始: Cloud Run が HTTP リクエストまたはイベントで実行
- 日付抽出: リクエストから対象日付を取得
- レポート生成: GAM API を使用してレポートデータを取得
- BigQuery 挿入: 取得したデータを BigQuery に保存
GAM REST API の実装詳細
API クライアントの初期化
from google.ads import admanager_v1 client = admanager_v1.ReportServiceClient()
レポート定義の作成
GAM REST API では、レポートの構造を詳細に定義する必要があります。
def create_report_definition(target_date: date, dimensions: list, metrics: list) -> admanager_v1.Report: """GAMレポートの定義を作成""" report = admanager_v1.Report() report.report_definition.dimensions = dimensions report.report_definition.metrics = metrics report.report_definition.report_type = admanager_v1.types.Report.ReportType.HISTORICAL report.report_definition.filters = [ admanager_v1.types.Report.Filter( field_filter=admanager_v1.types.Report.Filter.FieldFilter( field=admanager_v1.types.Report.Field( dimension=admanager_v1.types.Report.Dimension.AD_UNIT_NAME ), operation=admanager_v1.types.Report.Filter.Operation.MATCHES, values=[ admanager_v1.types.Report.Value(string_value="PREFIX_.*") ] ) ) ] report.report_definition.date_range.fixed = admanager_v1.types.Report.DateRange.FixedDateRange( start_date=date_pb2.Date( year=target_date.year, month=target_date.month, day=target_date.day ), end_date=date_pb2.Date( year=target_date.year, month=target_date.month, day=target_date.day ) ) return report
レポートの実行とデータ取得
def create_and_run_report(client: admanager_v1.ReportServiceClient, report: admanager_v1.Report) -> str: """GAMレポートを作成して実行""" request = admanager_v1.CreateReportRequest( parent=f"networks/{NETWORK_ID}", report=report, ) create_response = client.create_report(request=request) report_id = create_response.report_id run_request = admanager_v1.RunReportRequest( name=f"networks/{NETWORK_ID}/reports/{report_id}" ) operation = client.run_report(request=run_request) run_result = operation.result() return run_result.report_result
データの抽出と変換
GAM API から取得したデータを Pandas DataFrame に変換する処理です。
def extract_dimension_value(dim_value) -> any: """ディメンション値を抽出""" if dim_value.string_value: return dim_value.string_value elif dim_value.int_value: return dim_value.int_value elif dim_value.double_value: return dim_value.double_value else: return None def extract_metric_value(primary_value) -> any: """メトリクス値を抽出""" if primary_value.int_value: return int(primary_value.int_value) elif primary_value.double_value: return primary_value.double_value else: return None def fetch_report_data(client: admanager_v1.ReportServiceClient, report_result_name: str, column_names: list[str]) -> pd.DataFrame: """レポートデータを取得してDataFrameに変換""" fetch_request = admanager_v1.FetchReportResultRowsRequest( name=report_result_name ) rows_response = client.fetch_report_result_rows(request=fetch_request) rows_list = [] for row in rows_response: row_data = [] for dim_value in row.dimension_values: row_data.append(extract_dimension_value(dim_value)) for metric_group in row.metric_value_groups: for primary_value in metric_group.primary_values: row_data.append(extract_metric_value(primary_value)) rows_list.append(row_data) df = pd.DataFrame(rows_list, columns=column_names) return df
BigQuery との連携設計
スキーマ設計の考え方
BigQuery へのデータ保存では、以下の設計思想を採用しました。
- 日付別テーブル分割: パフォーマンスとコスト最適化
- 型安全性の確保: 適切なデータ型の設定
- 効率的なクエリ: 分析に適したスキーマ設計
スキーマ定義
以下のスキーマ定義は一例です。実際のプロジェクトでは、ビジネス要件や分析ニーズに応じて適切なカラム名とデータ型を設定してください。
DIMENSION_SCHEMA = [ bigquery.SchemaField("date", "INTEGER"), bigquery.SchemaField("advertiser_name", "STRING"), bigquery.SchemaField("advertiser_id", "INTEGER"), bigquery.SchemaField("order_name", "STRING"), bigquery.SchemaField("order_id", "INTEGER"), bigquery.SchemaField("line_item_type", "STRING"), bigquery.SchemaField("line_item_name", "STRING"), bigquery.SchemaField("line_item_id", "INTEGER"), bigquery.SchemaField("ad_unit", "STRING"), bigquery.SchemaField("ad_unit_id", "INTEGER"), bigquery.SchemaField("demand_channel_name", "STRING"), bigquery.SchemaField("creative_name", "STRING"), bigquery.SchemaField("creative_id", "INTEGER"), ] METRICS_SCHEMA = [ bigquery.SchemaField("total_impressions", "INTEGER"), bigquery.SchemaField("total_clicks", "INTEGER"), bigquery.SchemaField("total_ctr", "FLOAT"), ]
BigQuery へのデータ挿入
def insert_df_to_bigquery(df: pd.DataFrame, target_date: date, bigquery_schema: list[bigquery.SchemaField], table_name: str): """Pandas DataFrameをBigQueryに挿入""" client = bigquery.Client() job_config = bigquery.LoadJobConfig( schema=bigquery_schema, write_disposition=bigquery.WriteDisposition.WRITE_TRUNCATE, ) date_str = target_date.strftime('%Y%m%d') table_id = f"{PROJECT_ID}.{DATASET}.{table_name}_{date_str}" job = client.load_table_from_dataframe(df, table_id, job_config=job_config) job.result()
Cloud Run の実装
メイン処理の実装
@cloud_event def main(cloud_event: CloudEvent) -> None: """Cloud Run のエントリーポイント""" try: target_date = extract_target_date(cloud_event) df = get_gam_report_data(dimensions, metrics, column_names, target_date) insert_df_to_bigquery(df, target_date, schema, table_name) print("レポート処理が完了しました") except Exception as e: print(f"処理でエラーが発生しました: {e}") raise e
運用面での工夫
Cloud Run のデプロイと実行
Cloud Run のデプロイは gcloud
コマンドで行い、以下の設定で実行されます。
- Region: asia-northeast1
- Runtime: Python 3.13
- Memory: 512MB
- Trigger: HTTP
手動実行のためのコマンド
運用効率を向上させるため、以下のような手動実行用のコマンドを作成しました。
- 単独日付指定: 特定の日付のレポートを生成
- 範囲指定: 開始日から終了日までの期間でレポートを一括生成
これらのコマンドにより、スケジュール実行以外にも必要に応じて柔軟にレポートを生成できるようになっています。
システムの実行方式
システムは Cloud Run として実装されており、様々な実行パターンに対応できます。例えば、以下のような方法があります。
- 手動実行: HTTP トリガーによる直接実行
- スケジュール実行: Cloud Scheduler による定期実行
- イベント駆動: Pub/Sub や Eventarc を経由した実行
ブログ内で言及はしていませんが、弊社では Cloud Scheduler から Pub/Sub トピックを起動し、サブスクリプションを通じて Cloud Run を定期実行する仕組みを構築しています。この仕組みにより、毎日決まった時間にレポートデータが自動的に更新され、手動作業を大幅に削減できています。
実装で得られた知見
1. GAM REST API の特徴
メリット:
- JSON ベースで直感的な実装
- 豊富なドキュメントとサンプルコード
- 標準的な HTTP エラーハンドリング
注意点:
- レポート実行は非同期処理のため、完了待ちが必要
- 大量データの場合はページネーションが必要
- レート制限に注意が必要
2. BigQuery との連携
最適化のポイント:
- 日付別テーブル分割によるクエリ性能向上
- 適切なスキーマ設計によるストレージコスト削減
- WRITE_TRUNCATE モードによる冪等性の確保
成果と今後の展望
期待される成果
- 工数削減: レポート作成時間の短縮(現状 30 分〜1 時間)
- 営業活動の強化: レポート作成時間を営業活動に充て、売上貢献の向上
- データ活用の効率化: BigQuery での SQL による柔軟なデータ抽出
まとめ
Google Ad Manager REST API と BigQuery の連携により、セールスレポート作成の自動化を実現しました。
このシステムにより、セールスチームが営業活動により多くの時間を割けるようになり、結果として売上の向上に貢献することが期待されます。
同様の課題を抱えている組織の参考になれば幸いです。