Google Ads APIを用いた拡張コンバージョン機能を外部SaaSから移行・内製化した話

Google Ads APIを用いた拡張コンバージョン機能を外部SaaSから移行・内製化した話

はじめに

こんにちは、MA部の中原です。

MA部ではZOZOTOWNにおけるメルマガやLINE通知、アプリプッシュ通知、Web広告を配信するためのマーケティングオートメーションシステムを開発・運用しています。本記事では、Web広告について外部SaaSで利用していた機能の内製化と移行についてご紹介します。

目次

背景・課題

ZOZOTOWNでは、GoogleやYahoo!、Metaなどの様々な媒体に広告を出稿しています。広告の運用担当者は、配信した広告のクリック率やコンバージョン率などをみて効果検証し、広告の改善や最適化を行っています。Google広告においてはコンバージョンの計測を補完するために、外部SaaSの機能を活用して拡張コンバージョン(詳細は後述)を導入していました。しかし、外部SaaSに依存しているためエラーが発生した際にリカバリーができず、コンバージョンデータの欠損が発生し効果検証や分析に支障をきたすという課題がありました。そこで、Google Ads APIを用いた拡張コンバージョンの実装を内製化し、課題の解決を図りました。

拡張コンバージョンとは?

従来の方法では、コンバージョンは以下の流れで計測されます。

  1. 広告主がコンバージョンページ(注文完了ページなど)にコンバージョンタグを設定
  2. ユーザが広告をクリックするとGCLID(Google Click Identifier)という広告クリックを一意に識別するIDや広告のキャンペーン情報をCookieに保存
  3. ユーザがコンバージョンページに訪問すると、コンバージョンタグが発火し、Cookieの情報を読み取りGoogleに送信

しかし、近年のCookie規制の強化により従来の方法ではコンバージョンの欠損が起きやすくなります。今回紹介する拡張コンバージョンは、ファーストパーティデータ(自社データ)とGoogleアカウントを照合することで、この欠損を補完する機能です。具体的な計測の流れを以下に示します。

  1. 自社データのユーザ情報(ハッシュ化した電話番号やメールアドレスなど)やコンバージョン情報(注文日時)をGoogleに送信
  2. Google側で送信されたデータとGoogleアカウントのデータと照合し、広告クリックとコンバージョンの関連付けが行われ計測

about enhanced conversions

拡張コンバージョンの実装

設定方法

拡張コンバージョンの設定方法は3つあります。

設定方法 メリット デメリット
Googleタグマネージャー ユーザデータを収集するためのタグ設定を行うだけで非エンジニアでも設定可能 コンバージョンページに電話番号やメールアドレスの情報を持たせる必要があり、セキュリティリスクがある
Googleタグ
Google Ads API 自社から直接ユーザデータを送信するためセキュリティが高い 技術的な知識が不可欠でエンジニアによる開発が必要

今回はセキュリティの観点からGoogle Ads APIを採用しました。詳細はGoogle広告ヘルプページの拡張コンバージョンに関する説明をご覧ください。

環境構築

インストールや認証等の設定は以下の公式ドキュメントに従えばスムーズに設定できました。この章では実装するにあたって特に重要なポイントについて説明します。

developers.google.com

developers.google.com

1. アクセス認証情報の作成

OAuth 2.0クライアントにはウェブアプリケーションやデスクトップアプリケーションなど、さまざまな種類があります。今回の実装では、APIリクエストに必要なリフレッシュトークンを簡単に発行できるデスクトップアプリケーションとしてOAuth 2.0クライアントを作成しました。なお、サービスアカウントを使用する方法もありますが、これにはGoogle Workspaceドメイン全体の権限を委任する必要があります。この仕組みでは、Google広告の権限を持つユーザーになりすまして認可を受ける形となります。しかし、サービスアカウントに非常に強い権限を付与することになるためセキュリティリスクが高く、この方法は推奨されていません。アクセス認証情報の詳細については以下をご覧ください。

developers.google.com

2. API認証ユーザーに付与するGoogle広告のアクセス権限

APIリクエスト時の認証で使用するユーザは、Google広告にアカウントを追加およびアクセス権限の付与が必要です。主な権限とできることを以下に示します。

権限 できること
読み取り専用 閲覧のみ(広告、キャンペーン、レポートの確認など)
標準 広告運用の全般(キャンペーンや広告の作成、編集、コンバージョンデータのアップロードなど)
管理者 全ての操作

読み取り専用では拡張コンバージョンのアップロードで権限エラーが発生するため、標準以上の権限が必要です。上記以外にもアクセス権限の種類はありますが、詳細は以下をご覧下さい。

support.google.com

3. APIリクエストには個人アカウントのリフレッシュトークンを使用する

Google Ads APIではサービスアカウント単体でのリクエストがサポートされていません。そのため個人アカウントの認証情報を使うしかありません。理由は以下の2点です。

  • Google広告には個人のアカウントしか登録できない
  • Google Ads APIの認証時にAPI発行主がGoogle広告にアカウントと権限があるかが確認される

APIのクライアントライブラリではリフレッシュトークンを認証情報として渡します。このリフレッシュトークンは、Google広告に権限があるアカウントで発行したものを使用します。APIリクエスト時にはリフレッシュトークンでアクセストークンを取得し、そのアクセストークンで認証・認可が行われます。リフレッシュトークンは個人アカウントに依存するため、システムをチーム全体で管理する際に不便な場合があります。たとえば、APIリクエストに使用していたメンバーのアカウントが退職などで削除されると、そのアカウントに紐づいたリフレッシュトークンを使用してAPIリクエストができなくなります。一方で、Google広告のアカウントを持つユーザーのリフレッシュトークンを使用することで、Google広告のアカウントを持たない他のメンバーやシステムからもAPIリクエストは可能です。そのためチーム全体で運用する際は、認証に使うアカウントの管理や引き継ぎを考慮する必要があります。個人に紐づくトークンを利用する設計は望ましくないため、将来的にはサービスアカウントをセキュアに利用できる仕組みが導入されることを期待しています。

全体の流れ

実装の説明の前に簡単な全体像を以下に示します。

overview

大まかな流れは次の通りです。

1. 運用担当者の広告設定・広告出稿

運用担当者は広告を作成した後、コンバージョンを計測するためにコンバージョンアクションというものを作成し設定します。コンバージョンアクションとは、サイトでの商品購入やアプリのダウンロードなどユーザの特定の行動です。例えば、ウェブサイトの注文完了ページへのクリックで購入を測定する「購入コンバージョン」などです。サイト内にタグを設置することでクリック時に発火し測定されます。今回は購入コンバージョンの計測を補完しました。

2. ユーザが広告をクリックし購入

ユーザが広告をクリックし注文完了ページに到達すると、コンバージョンアクションのタグが発火し、Googleに以下の情報を送信します。

  • クリックID(GCLID):広告クリックを一意に識別するID
  • 広告ID:クリックされた広告を特定するための情報
  • クリック時間データ:広告がユーザーに配信されてからクリックされた時間に関する情報

3. ファーストパーティ(自社)の購入データをGoogleに送信

自社のDBに蓄積される注文データ(ハッシュ化したメールアドレスなどのユーザデータや注文日時など)をGoogleに送信します。Google側でその情報を使用して広告のクリックとコンバージョンを関連付けます。ハッシュ化しているとはいえ、ユーザの個人情報を外部へ送信するため事前に法務部への確認が必要です。

実装

overview

上図の赤の部分を実装しました。定期実行するために、Digdagを使用しています。Digdagとはオープンソースのワークフローエンジンで、複数のタスクをワークフローとして定義しバッチ処理を行えるものです。自社の注文データをGoogle Ads APIを使用してGoogleに送信するシンプルなアプリケーションを作成しました。送信データとAPIを使用したアップロードの実装について説明します。

送信データ

送信するデータは公式ドキュメントに記載されています。すべての項目を送信する必要はないため必要最低限の以下の項目を送信しました。

送信項目 説明
order_id コンバージョンのタグで指定する注文ID
phone_number ハッシュ化したユーザの電話番号
country_code ISO 3166の2文字の国名コード
email ハッシュ化したユーザのメールアドレス
conversion_date_time 注文日時

ハッシュ化する項目は、ハッシュ化する前に以下のように変換する必要があります。

  • 先頭と末尾の空白を取り除く
  • テキストを小文字に変換する
  • E164規格に従って電話番号をフォーマットする
  • メールアドレスのドメイン名の前にあるピリオド(.)をすべて削除する

ハッシュ化はBigQueryのクエリで行ってテーブルに書き出し、アップロード時はテーブルのデータを参照するだけの状態にしました。以下にBigQueryでハッシュ化するクエリ例を示します。

SELECT
  MemberID,
  to_hex(sha256(lower(replace(replace(split(email, '@')[0], ' ', ''), '.', '')) || '@' || lower(replace(split(email, '@')[1], ' ', '')))) AS email_sha256,
  to_hex(sha256('+81' || ltrim(replace(tel, '-', ''), '0'))) AS tel_sha256
FROM
  `project.dataset.Member` -- 会員テーブル

送信するデータはファーストパーティのCookieの保管期限の関係で、ドキュメントに記載の通り、コンバージョン発生から24時間以内のデータだけを送るように注意します。

APIを使用したアップロード

Pythonのクライアントライブラリを使用しました。サンプルコードが公式で用意されており、それを参考に実装しました。APIのリクエストは以下のドキュメントにある通り、1回につき2000件までという制約があります。直列で2000件ずつ処理すると時間がかかるため、今回はAPIリクエストを並列で処理するように工夫して実装しました。

developers.google.com

並列処理の方法

Digdagにはループ処理ができるfor_each>オペレータと、並列処理できる_parallel:オプションがあります。

docs.digdag.io

これらを使用し、対象データを固定の並列数で2000件ずつ処理するためのグループに分け、ループしながら効率的に処理することにしました。

1. 対象データ取得

DBから過去24時間以内の注文情報を取得して一時テーブルに書き出します。

2. バッチグループの作成

対象データからバッチ処理するグループのリストを作成します。以下はグループ作成のサンプルコードです。引数のcountには対象データの件数を渡します。

_BATCH_SIZE = 2000 # リクエスト1回あたりの最大件数
_PARALLEL_SIZE = 50 # 並列数

def generate_batch_group_list(count: int) -> List:
    batch_size = int(count / _BATCH_SIZE)
    if int(count % _BATCH_SIZE) > 0:
        batch_size += 1
    batch_number_list = list(range(batch_size))
    return [batch_number_list[i:i + _PARALLEL_SIZE] for i in range(0, len(batch_number_list), _PARALLEL_SIZE)]

例えば対象データ件数が100万件の場合、それぞれの変数とbatch_group_listの中身は以下の通りです。

count: 1000000
batch_size:(1000000 / 2000)= 500
batch_number_list: [0, 1, 2 ... 499]
batch_group_list: [[0, 1, 2, ..., 49][50, 51, 52, ..., 99]...[450, 451, ... , 499]]

batch_group_listは並列数の50個ずつに区切られた2次元配列となり、中身の数字は、BigQueryに書き出したテーブルから2000件ずつ取得するために基準となる数字です。

3. 並列処理

上記2で作成したbatch_group_listをDigdagの変数にセットし、以下のようなDigdagのタスクを定義します。

+upload:
  for_each> :
    group_list : ${batch_group_list}
  _do :
    for_each> :
      batch_number : ${group_list}
    _parallel : true
    _do :
      _export:
        python: /usr/bin/pipenv_run_python
        docker:
          image: ${docker_python.image}
          pull_always: ${docker_python.pull_always}
        !include : k8s.dig
      py>: models.google_ads.upload_enhanced_conversions
      timestamp: ${moment(session_time).local().format("YYYYMMDD_HHmmss")}

外側のループで50個ずつにまとめたグループのリストを取り出し、内側のループでは_parallel : trueオプションをつけることで50個を並列で処理します。ここで呼び出しているupload_enhanced_conversionsメソッドでは、batch_numberを基準として、1で書き出した一時テーブルから2000件取得してリクエストします。Google Ads APIのライブラリのクラスやメソッドを使う処理は省略しますが、以下にコード例を示します。

_BATCH_SIZE = 2000

def upload_enhanced_conversions(timestamp: str, batch_number: int) -> None:
    start_index = batch_number * _BATCH_SIZE
    # 対象データをすべて書き出したテーブル
    table_id = f'project.dataset.purchase_conversions_{timestamp}'
    conversions_iterator = bq.list_rows(table_id, start_index, _BATCH_SIZE) # 指定位置から2000件取得
    conversions_list = list(conversions_iterator)
    """
    conversions_listをGoogle Ads APIのライブラリのメソッドを使用してアップロード
    """

def list_rows(table_id: str, start_index: int, batch_size: int) -> List[bigquery.Row]:
    client = bigquery.Client(project=config["gcp_project"], credentials=gcp.get_credentials())
    table = client.get_table(table_id)
    row_iterator = client.list_rows(table, start_index=start_index, max_results=batch_size)
    return list(row_iterator)

これで50並列での処理が可能になりました。対象データが100万件の場合、2000件ずつのリクエストが500回となり、50並列で実行するため外側のループは500/50=10回まわることになります。

外部SaaSからの移行

ここまで説明した内容を冒頭でも説明したように外部SaaSを利用して行っていました。ここからは外部SaaSからどのように移行をしたのかについてお話しします。

1. テスト用のコンバージョンアクションの作成

Google広告には開発環境が用意されておらず、本番環境でテスト用のコンバージョンアクションを作成しました。本番運用で使用しているコンバージョンアクションと同じ設定で作成します。本番運用と同じページタグを設置することで、本番運用と同様のコンバージョンを計測でき、本番用のコンバージョンに影響を与えることなく確認できます。

2. テスト用のコンバージョンアクションに対して拡張コンバージョンをアップロード

アップロードは、コンバージョンアクションID単位です。このIDはコンバージョンアクション単位で振られます。テスト用のコンバージョンアクションに対してアップロードします。

3. テスト用のコンバージョンアクションで拡張コンバージョンが記録されることを確認

Google広告の管理画面から確認します。以下の画面の「オフラインでのコンバージョン」の詳細ページでアップロードされたかが確認できます。 management portal uploaded cv

アップロードのデータに問題があると以下のようにアラートが表示されます。 alert uploaded data

この時は以下が原因でアラートが表示されました。

  • 24時間より前に購入が発生したデータをアップロードしていた
    • コンバージョン発生から24時間以内のデータが必要とされる
  • テスト用ということで少量データしかアップロードしなかった
    • タグが発火した回数(コンバージョン発生回数)分のコンバージョンデータをアップロードする必要がある

4. 本番適用と外部SaaSとの並行稼働

本番運用で使用しているコンバージョンアクションに対してもテスト用と同様にAPIを使用してアップロードします。アップロードが上手くいかずコンバージョンの計測を補完ができなかった場合、本番運用に影響するため外部SaaSと並行稼働します。

5. 外部SaaSの利用停止

問題なく安定的に本番運用で使用しているコンバージョンアクションに対してアップロードできていることを確認して、外部SaaSの利用を停止します。

移行前と移行後

外部SaaSからの移行前後で拡張コンバージョンのカバレッジが改善され安定するようになりました。コンバージョンが失われている可能性があるとカバレッジの割合は低く表示されるため、高い割合であることが好ましいです。 before after enhanced cv

まとめ

本記事では、Google Ads APIを用いた拡張コンバージョンの実装と外部SaaSからの移行方法について紹介しました。外部SaaSの機能の内製化と移行によって、拡張コンバージョンのカバレッジが安定し、利用コストも削減できました。Google Ads APIを使用して拡張コンバージョンの実装を検討している方の参考になりましたら幸いです。

さいごに

ZOZOでは一緒にプロダクトを開発してくれるエンジニアを募集しています。ご興味のある方は下記リンクからぜひご応募ください!

corp.zozo.com

カテゴリー