Feedforce Developer Blog

フィードフォース開発者ブログ

真・Google Ads API 徹底入門 その3 Mutate 編

こんにちは id:hano_tea です。

真・Google Ads API 徹底入門の第三弾、 Mutate 編です。

前回の真・Google Ads API 徹底入門第一弾、第二弾は以下からどうぞ。

developer.feedforce.jp

developer.feedforce.jp

今回は Google Ads API で Google 広告に変更を加えるための仕組み Mutate について解説します。

今回もこれまで同様、 Ruby クライアントライブラリを使用している前提での解説になります。ご了承ください。

目次

Mutate とは

ここまでは情報の取得の方法について説明しました。ここからはオブジェクトの作成や更新について説明します。 Google Ads API において、オブジェクトに対して何らかの変更を加えるタイプの操作は Mutate と呼ばれるもので行います。

Mutate に関する公式のドキュメントは以下にあります。

developers.google.com

Mutate は Search とは異なり、操作対象となるオブジェクトの Service 毎にそれぞれ別の Mutate メソッドが用意されています。 どういうことかというと、例えばキャンペーンの作成や更新を行うための Mutate は CampaignService 以下に MutateCampaigns というメソッドとして用意されています。

developers.google.com

Search はオブジェクト毎に別のメソッドに分かれておらず GoogleAdsService.Search 一つに集約されていたのとは対象的です。

Mutate はどのオブジェクトに対するものであっても、基本的に以下のように呼び出すことができます。

client.service.{object_name}.mutate_{plural_object_name}(customer_id, array_of_operations)

ここで、 {object_name} は操作対象となるオブジェクトの名前です。 campaignad_group などです。 また、 {plural_object_name} も同様にオブジェクトの名前なのですが、こちらは 複数形 になります。 campaigns, ad_groups など。

array_of_operations は実際に行う変更内容を含んだ配列です。この変更内容は Operation と呼ばれるもので、 Mutate する際に最も重要な要素になります。

オブジェクトを作る

では実際にオブジェクトの作成を行うような Mutate を発行してみましょう。 いつもどおりキャンペーンの作成…と行きたいところですが、キャンペーンは作成に必要な情報が多くてややこしいので、広告グループの作成を例にします。

最初に完成形のコードを示します。

client = ...
customer_id = '1112223333'
campaign_id = '2223334444'

operation = client.operation.create_resource.ad_group do |ad_group|
  ad_group.campaign = client.path.campaign(customer_id, campaign_id)
  ad_group.name = 'campaign name'
  ad_group.status = :ENABLED
end

response = client.service.ad_group.mutate_ad_groups(customer_id, [operation])

検索とはだいぶ勝手が違い、新しく出てきている概念もあるので少しづつ見ていきます。

Operation とは

Operation は前述の通り、 Mutate で行う処理の内容を格納したオブジェクトです。 オブジェクトの作成を行う Operation, オブジェクトを変更する Operation, オブジェクトを削除する Operation があります。

例えば広告グループを作成するための AdGroupOperation のリファレンスは以下のページにありますが、 createupdate, remove といったフィールドが存在していることが分かるかと思います。

developers.google.com

createupdate は作成/更新対象のオブジェクトそのものを渡すようなイメージです。 remove はやや毛色が違い、削除対象のオブジェクトの resource_name を与える形になっています。

Operation オブジェクトを作る

最初のコードに戻り、 Operation を作成している部分を振り返ってみます。

operation = client.operation.create_resource.ad_group do |ad_group|
  ad_group.campaign = client.path.campaign(customer_id, campaign_id)
  ad_group.name = 'campaign name'
  ad_group.status = :ENABLED
end

この書き方は Factories という機能を使っています。

developers.google.com

Factories に関しては以前技術ブログの記事にまとめた内容があるのでそちらを参照してもらえればと思います。

developer.feedforce.jp

上記のコードでは、 client.operation.create_resource.ad_group のブロック引数として ad_group を渡しています。 この ad_groupGoogle::Ads::GoogleAds::V3::Resources::AdGroup オブジェクトになっており、 このオブジェクトをブロック内で書き換えることで Operation で処理したい内容を記述するようなイメージです。

上記の例だと、 AdGroup の作成に際して必要な情報である、

  • 親になるキャンペーンを ad_group.campaignresource_name で指定
  • 広告グループ名を ad_group.name で指定
  • 広告グループのステータス(有効/停止)を ad_group.status で指定

ということをしています。なお、オブジェクトの作成にあたって指定が必要なアトリビュート、指定が可能なアトリビュートはオブジェクト毎に異なります。 一応各オブジェクトのリファレンスページに必須っぽいアトリビュートだと This field is required and should not be empty ... みたいな記述があったりしますが、 この記述が無くても実は必須なアトリビュートがあったりもするので、実際はとりあえず叩いてみないと分からない部分も多いのが実際のところです。

developers.google.com

もうちょっと複雑な Operation

上記の Operation は非常にシンプルなものなのでわかりやすいですが、もっとややこしい Operation もあります。 例えば先程例示するのを避けたキャンペーンの作成はやや複雑な Operation になります。

operation = client.operation.create_resource.campaign do |campaign|
  campaign.name = campaign_name
  campaign.advertising_channel_type = :SHOPPING
  campaign.status = :ENABLED
  campaign.campaign_budget = client.path.campaign_budget(customer_id, budget_id)
  campaign.bidding_strategy_type = :MANUAL_CPC

  campaign.manual_cpc = client.resource.manual_cpc do |manual_cpc|
    manual_cpc.enhanced_cpc_enabled = true
  end

  campaign.shopping_setting = client.resource.shopping_setting do |setting|
    setting.sales_country = :JP
    setting.campaign_priority = 0
    setting.merchant_id = merchant_id
  end
end

ショッピング広告キャンペーンの作成時に使う Operation は上記のようになります。 先程までの広告グループの作成とは違い、様々な項目を設定していることがわかります。

ブロック内の上5行までは先程の広告グループのそれとあまり違いはありませんが、7行目以降に広告グループの際はなかったブロックの入れ子が出てきています。 このように、特定のオブジェクトを作成する際に別のオブジェクトが必要になることが時折発生します。

その際は client.resource.{object_name} という形で、 Operation 作成ブロック内で別のオブジェクトを作成するコードを記述することが出来ます。 この client.resource も Factories の一種で、これもブロック引数として object_name で指定したオブジェクトが渡されるので、 Optimizer 同様、このオブジェクトのアトリビュートを書き換えて処理内容を記述します。

複数のオブジェクトをまとめて変更する

上述の通り、オブジェクトの操作を行う際は、基本的に各オブジェクトに用意された Mutate メソッドを使って行うことになります。 この方法だと一度に操作できるオブジェクトは1種類に限定されるため、例えばキャンペーンと広告グループをまとめて作成したい、といった操作ができなさそうに思えます。

基本的には種類の異なるオブジェクトは個別に作成するのが通常の方法になりますが、実は GoogleAdsService.Mutate というメソッドがあります。

developers.google.com

このメソッドは「複数のオブジェクトを横断して Mutate を実行できる」のですが、「原子性をもった変更を行える」という特徴も持ち合わせています。

以前まとめた内容は以下にあるので、興味があれば参照していただけると。

developer.feedforce.jp

なお、この「まとめて Mutate を行う」機能は 全てのオブジェクトに対応しているわけではない のでご注意ください。

実際にオブジェクトを作ってみる

ということでオブジェクトを作ってみましょう。

せっかくなので原子性を持ったリクエストの例を示します。

client = ...
customer_id = '...'
campaign_name = 'API Test Campaign by xxx'

budget_operation = client.operation.create_resource.campaign_budget do |campaign_budget|
  campaign_budget.resource_name = client.path.campaign_budget(customer_id, -1)
  # キャンペーン間で予算を共有しない(単一キャンペーン用の予算にする)設定にしている
  # see https://developers.google.com/google-ads/api/docs/fields/campaign_budget#campaign_budgetexplicitly_shared
  campaign_budget.explicitly_shared = false
  campaign_budget.amount_micros = 1_000 * 1_000_000
end

# 別々に作るならこんな感じにして budget を作成、 id を得る
# budget_response = client.service.campaign_budget.mutate_campaign_budgets(customer_id, [budget_operation])
# resource_name = budget_response.results.first.resource_name
# resource_name.split('/').last

campaign_operation = client.operation.create_resource.campaign do |campaign|
  campaign.name = campaign_name
  campaign.advertising_channel_type = :SEARCH
  campaign.status = :PAUSED
  campaign.campaign_budget = client.path.campaign_budget(customer_id, -1)
  campaign.bidding_strategy_type = :MANUAL_CPC

  campaign.network_settings = client.resource.network_settings do |ns|
    ns.target_google_search = true
    ns.target_search_network = true
    ns.target_content_network = false
    ns.target_partner_search_network = false
  end

  campaign.manual_cpc = client.resource.manual_cpc do |manual_cpc|
    manual_cpc.enhanced_cpc_enabled = true
  end
end

# 別々に作るなら budget 同様以下のように Campaign を作成
# response = client.service.campaign.mutate_campaigns(customer_id, [operation])

# mutate_operation でラップする
budget_mutate_op = client.operation.mutate
budget_mutate_op.campaign_budget_operation = budget_operation
campaign_mutate_op = client.operation.mutate
campaign_mutate_op.campaign_operation = campaign_operation

response = client.service.google_ads.mutate(customer_id, [budget_mutate_op, campaign_mutate_op])

リクエストに成功したら実際に管理画面で確認してみます。キャンペーンが作成できているはずです。 ここで作成したキャンペーンは後ほど更新、削除を試してみるときに使う予定なので、レスポンスを保持しておきます。

原子性の部分の説明を詳しくはしていないのですが、必要な部分だけ一応軽く補足しておきます。

原子性を持ったリクエストを行う際も、 Campaign の作成時には CampaignBudget を指定する必要があります。 しかし、原子性を持ったリクエストを行う際は「まだ作成されていない」 CampaignBudget の ID を指定しなければいけません。

存在しないオブジェクトの ID は当然指定できないため、原子性を持ったリクエスト内 で作成される予定のオブジェクトに参照用の仮 ID -1 を指定しています。 この ID は原子性を持ったリクエスト内でのみ有効で、キャンペーンの作成時にこの仮 ID -1 を実際に使って CampaignBudget を参照しています。

詳細はこちら。

developers.google.com

Operation に関する Tips

Operation の書き方をここまで説明してきましたが、ブロック内で使っている記法について補足説明をしておきます。

プロトコルバッファと auto_boxing による自動変換

Mutate する際に作成する各種オブジェクトの各種アトリビュートは、本来プロトコルバッファの形、 Google::Protobuf で用意されているオブジェクトである必要があります。 例えば数値、整数値を設定すべきアトリビュートであれば Google::Protobuf::Int32Value, 文字列であれば Google::Protobuf::StringValue のようになります。

しかし、これまでのサンプルコードでは特にそういったオブジェクトを使わず、通常の Ruby のオブジェクトを渡していたと思います。 実はライブラリ内で暗黙的にオブジェクトの変換が行われていたりします。

最初の頃はGoogle Ads API 徹底入門にある通り、 ラッパーが用意されていたので、それを明示的に呼び出して変換を行う必要がありました。

いつから不要になったのか記憶が定かではないですが(Changelog 的にはクライアントライブラリバージョン 2.3.0 で導入されたっぽいようですが)、 auto_boxing という機能が導入され、基本的にはラッパーを使う必要がなくなっています。

この auto_boxing という機能のおかげで、 Ruby の標準的な数値や文字列をそのまま使っても、最終的にプロトコルバッファのオブジェクトに変換していい感じにしてくれます。

以前は公式のドキュメントがあったはずなんですが、今見たら見当たらない…

Enum 型なアトリビュートはシンボルで値を指定する

Mutate のサンプルコード内で時々 :ENABLED のようなシンボルを渡しているコードが出てきているかと思います。 このような形で値を設定している部分は Enum な型が使われる部分になっています。

例えば AdGroup の status というアトリビュートは AdGroupStatus というものを指定することになっています。以下がそのリファレンスです。

developers.google.com

見ての通り Enum として取り得る値が列挙されています。 UNSPECIFIEDUNKNOWN を指定することは基本的に無いと思ってよいです。 今回の AdGroupStatus であれば ENABLED, PAUSED, REMOVED の3つの値が使われます。こと作成に関しては前2つのみが使われます。

Enum も Factories の仕組み自体は用意されています。以下のドキュメントがそれです。

developers.google.com

が、上記ドキュメントにも

We recommend using the symbol syntax for statically setting enum fields (e.g., campaign.status = :PAUSED).

とある通り、 Enum は基本的にシンボル記法で指定するのをオススメされているため、 Factories を明示的に使うことはほとんど無いと思います。

resource_name が必要な場合は client.path ユーティリティを使う

Google Ads では完全に独立したオブジェクトというのはほとんどなく、だいたい何かしらの親になるようなオブジェクトがあったり、 あるいは逆に子にあたるオブジェクトを持っていたり、というような構造になっていることがほとんどです。

このため、あるオブジェクトを作成する際に親オブジェクトを指定したり、といったような関連付けたいオブジェクトを指定するということがよく行われます。 このとき、ある別のオブジェクトを指定、参照するのに使われるのが resource_name になります。

resource_name は手で組み立てることも可能ではありますが、多少組み立てを楽にしてくれるユーティリティが用意されていたりします。それが client.path, PathLookupUtil と呼ばれるものです。

developers.google.com

これを使うと、 resource_name の組み立てに必要な ID を渡すだけで resource_name を組み立ててくれます。ちょっと便利。

(どの ID が必要なのかは叩いてみないと or ドキュメントを読んでどんな形の resource_name なのかを知らないと分からないのですが…)

オブジェクトを更新する

ここまではオブジェクトの作成について見てきました。 次は mutation で行える作成以外の機能、更新について見ていきます。

と言っても、更新はそんなに作成と操作方法に違いはありません。 例えばキャンペーンステータスの更新を行う場合、以下のようなコードになります。

customer_id = '1112223333'
campaign_id = '2223334444'
resource_name = client.path.campaign(customer_id, campaign_id)

operation = client.operation.update_resource.campaign(resource_name) do |campaign|
  campaign.status = :PAUSED
end

client.service.campaign.mutate_campaigns(customer_id, [operation])

更新内容が少ないのもありますが、特に複雑な点はないかなという気がしています。 作成時との違いは主に以下のオペレーションの作成を行うコードでしょうか。

operation = client.operation.update_resource.campaign(resource_name) do |campaign|

更新用のオペレーション作成の場合は client.operation の後が update_resource になります。作成のときは create_resource でした。

また、 update_resource 以降、オブジェクト名と同名のメソッドの引数が作成のときとは異なっています。 create_resource のときは特に引数が無かったのですが、 update_resource の場合は更新対象とするリソースを特定するため resource_name を渡す必要があります。

それ以外の部分については基本的には作成のときとやり方は変わりません。

実際にオブジェクトを更新してみる

というわけでこちらもやってみましょう。 先程キャンペーンを作成した際に保持しておいた response から必要な情報… Campaign の情報を取り出します。

campaign_identifier = response.mutate_operation_responses.last.campaign_result.resource_name

operation = client.operation.update_resource.campaign(campaign_identifier) do |campaign|
  campaign.status = :ENABLED
end

client.service.campaign.mutate_campaigns(customer_id, [operation])

管理画面を確認すると、先程停止状態で作成したキャンペーンが配信中になっているはずです。

update_mask について

上記の例では最初から Factories を使っているため、変更内容だけを設定すれば更新用の Operation を作成できました。 Factories を使うと意識しなくて済むのですが、 update_mask という概念が存在していたりします。

developers.google.com

先程の Factories で作成した Operation の中身は以下のようになっています。

> operation
=> <Google::Ads::GoogleAds::V3::Services::CampaignOperation: create: nil, update: <Google::Ads::GoogleAds::V3::Resources::Campaign: resource_name: "customers/1112223333/campaigns/2223334444", id: nil, name: nil, status: :PAUSED, campaign_budget: nil, ad_serving_optimization_status: :UNSPECIFIED, advertising_channel_type: :UNSPECIFIED, advertising_channel_sub_type: :UNSPECIFIED, tracking_url_template: nil, url_custom_parameters: [], network_settings: nil, experiment_type: :UNSPECIFIED, start_date: nil, end_date: nil, serving_status: :UNSPECIFIED, bidding_strategy_type: :UNSPECIFIED, bidding_strategy: nil, manual_cpc: nil, manual_cpm: nil, target_cpa: nil, target_spend: nil, base_campaign: nil, target_roas: nil, maximize_conversions: nil, maximize_conversion_value: nil, hotel_setting: nil, dynamic_search_ads_setting: nil, percent_cpc: nil, shopping_setting: nil, manual_cpv: nil, final_url_suffix: nil, real_time_bidding_setting: nil, frequency_caps: [], target_cpm: nil, video_brand_safety_suitability: :UNSPECIFIED, targeting_setting: nil, vanity_pharma: nil, selective_optimization: nil, tracking_setting: nil, geo_target_type_setting: nil, target_impression_share: nil, commission: nil, app_campaign_setting: nil, payment_mode: :UNSPECIFIED, labels: []>, remove: "", update_mask: <Google::Protobuf::FieldMask: paths: ["status"]>>

Operation の末尾に update_mask: <Google::Protobuf::FieldMask: paths: ["status"]> という記述があると思います。 このように、 update の対象となる箇所を指定するためのマスクを指定する必要があります。

Factories を使っていると、ブロック内で変更を行っている箇所から自動的にこの update_mask を組み立ててくれるので、普段はあまり意識しなくても大丈夫です。 極稀に Factories を使うとうまく Operation が組み立てられない場合があり、その際は手動でこれを組み立てる必要が出てくるので注意しましょう。

ちなみにこの辺りの話は Google Ads API ワークショップで解説されていました。興味がある方は以下のリンクからどうぞ。

youtu.be

オブジェクトを削除する

オブジェクトの削除も同じく Mutate で行います。 作成と更新は対象となるオブジェクトを作成する必要がありましたが、削除の際は対象となるオブジェクトの resource_name だけあれば大丈夫です。

以下のようなコードで削除できます。 Campaign Criterion という、ターゲティング設定や除外キーワード設定などに使われるオブジェクトを例にします。 他のオブジェクトでもほぼ同じコードで削除可能です。

customer_id = '...'
resource_name = '...'
operation = client.operation.remove_resource.campaign_criterion(resource_name)
client.service.campaign_criterion.mutate_campaign_criteria(customer_id, [operation])

実際にオブジェクトを削除してみる

こちらもやってみましょう。 対象となるキャンペーンの resource_name は先程取得済みなのでそれを使います。

# campaign_identifier = response.mutate_operation_responses.last.campaign_result.resource_name
operation = client.operation.remove_resource.campaign(campaign_identifier)
client.service.campaign.mutate_campaigns(customer_id, [operation])

管理画面上でキャンペーンが削除されていることが確認できるかと思います。

Mutate の Dry-run

Search と違い、 Mutate は Google Ads 上の状態に対して変更を加えることになるため、テスト環境ならともかく、本番環境では迂闊に使えません。 とはいえ、本番環境で動作確認をしたくなるときはよくあります。自分たちで好きに操作できる自社用広告アカウントがあればいいのですが、現実的にそれができる環境は多くはないと思います。

こういったとき、実際に本番環境への変更は行わないが、 API で問題なく操作が行えるかどうかの動作確認を行うための Dry-run の仕組みが用意されています。 ガイドなどには載っていないのですが、各 Service の API に validate_only というフィールドが用意されていることがわかります。以下はキャンペーンの Mutate リクエストの例です。

developers.google.com

validate_onlytrue にしてリクエストを行うと、 API に対するリクエスト自体は発行されますが、実際に変更は行われず、また返ってくる処理結果のレスポンスは(エラーが無ければ)空になります。 もしエラーになってしまうような Mutate リクエストを発行したときはちゃんとエラーの詳細が返ってくるので、動作確認に使えて便利です。

Google Ads API を叩くコードの実装を行う際にはこの validate_only オプションを指定できるような形にしておくと良いでしょう。 どのように値を指定するかは色々考えられるかとは思いますが、環境変数で切り替えられるようにしておくと便利です。

(新たに Mutate をするような機能を追加した際に validate_only を設定するコードを書き忘れると悲惨なことになるので気をつけましょう。)

Dry-run してみる

こちらもやってみましょう。 CampaignBudget の作成を Dry-run してみます。

# client = GoogleAdsOperation::Api::Client.build
# customer_id = '...'
budget_operation = client.operation.create_resource.campaign_budget do |campaign_budget|
  campaign_budget.explicitly_shared = false
  campaign_budget.amount_micros = 1_000 * 1_000_000
end

budget_response = client.service.campaign_budget.mutate_campaign_budgets(customer_id, [budget_operation], validate_only: true)

特にエラーなどにはならず、レスポンス内の results[] になっていて、処理結果としては空、何も行われていません。

上記の例は特に何もエラーなどにならない正常なケースでしたが、エラーになるようなリクエストをするとどうなるのかも確認しておきましょう。

budget_operation = client.operation.create_resource.campaign_budget do |campaign_budget|
  campaign_budget.amount_micros = 1_000 * 1_000_000
end

budget_response = client.service.campaign_budget.mutate_campaign_budgets(customer_id, [budget_operation], validate_only: true)

explicitly_shared を無くした状態でリクエストしてみました。どうなったでしょうか。 以下のようなエラー詳細が表示されたのではないかと思います。

> budget_response = client.service.campaign_budget.mutate_campaign_budgets(customer_id, [budget_operation], validate_only: true)
W, [2020-04-20T15:09:15.099419 #4463]  WARN -- : CID: ..., Host: googleads.googleapis.com:443, Method: /google.ads.googleads.v3.services.CampaignBudgetService/MutateCampaignBudgets, IsFault: yes
I, [2020-04-20T15:09:15.099868 #4463]  INFO -- : Outgoing request: Headers: {"x-goog-api-client":"gl-ruby/2.6.5 gapic/5.0.0 gax/1.8.1 grpc/1.27.0","developer-token":"...","login-customer-id":"...","x-goog-request-params":"customer_id=..."} Payload: {"customerId":"...","operations":[{"create":{"amountMicros":"1000000000"}}],"validateOnly":true}
I, [2020-04-20T15:09:15.100475 #4463]  INFO -- : Incoming response (errors): 
Error 1: {"errorCode":{"fieldError":"REQUIRED"},"message":"The required field was not present.","trigger":{"stringValue":""},"location":{"fieldPathElements":[{"fieldName":"operations","index":"0"},{"fieldName":"create"},{"fieldName":"name"}]}}
Google::Ads::GoogleAds::Errors::GoogleAdsError: Google::Ads::GoogleAds::Errors::GoogleAdsError
from /.../ruby/2.6.0/gems/google-ads-googleads-5.0.0/lib/google/ads/google_ads/error_transformer.rb:10:in `block (2 levels) in <module:GoogleAds>'
Caused by GRPC::InvalidArgument: 3:Request contains an invalid argument.
from /.../ruby/2.6.0/gems/grpc-1.27.0-x86_64-linux/src/ruby/lib/grpc/generic/active_call.rb:31:in `check_status'

このように、 validate_only が指定されている Dry-run であっても、リクエスト内容が不正でエラーが発生するようなものだった場合、 ちゃんとそのエラーの内容や詳細を返してくれます。

また、別のポイントとして、「エラーが発生した場合は WARN レベルでリクエストの概要が、 INFO レベルでリクエストの詳細(本来 DEBUG レベルの内容)が表示される」ことも確認できます。 このように、普段 INFO レベルのログだけにしていても、必要に応じて詳細なログが INFO レベルでも出力されるようになっています。

余談

余談なのですが、 explicitly_shared という項目の説明 を見ると、

Defaults to true if unspecified in a create operation.

という記述が見受けられます。しかし今回のテスト用リクエストで explicitly_shared を消したらエラーになりました。 ドキュメントには省略できそうな記述があるのに実際は省略できない、という例です。

このように微妙にドキュメントが信頼しきれないので、トライ・アンド・エラーが重要になってくることが分かるかと思います。

次回予告

さて、今回は真・Google Ads API 徹底入門 その3 Mutate 編ということで、 Google Ads API で Google Ads を操作する方法を解説しました。

次回は最終回、 Google Ads API に関係する各種ドキュメントの歩き方を解説していきます。お楽しみに。