こんにちは id:hano_tea です。
真・Google Ads API 徹底入門の第三弾、 Mutate 編です。
前回の真・Google Ads API 徹底入門第一弾、第二弾は以下からどうぞ。
今回は Google Ads API で Google 広告に変更を加えるための仕組み Mutate
について解説します。
今回もこれまで同様、 Ruby クライアントライブラリを使用している前提での解説になります。ご了承ください。
目次
Mutate とは
ここまでは情報の取得の方法について説明しました。ここからはオブジェクトの作成や更新について説明します。 Google Ads API において、オブジェクトに対して何らかの変更を加えるタイプの操作は Mutate と呼ばれるもので行います。
Mutate に関する公式のドキュメントは以下にあります。
Mutate は Search とは異なり、操作対象となるオブジェクトの Service 毎にそれぞれ別の Mutate メソッドが用意されています。
どういうことかというと、例えばキャンペーンの作成や更新を行うための Mutate は CampaignService
以下に MutateCampaigns
というメソッドとして用意されています。
Search はオブジェクト毎に別のメソッドに分かれておらず GoogleAdsService.Search
一つに集約されていたのとは対象的です。
Mutate はどのオブジェクトに対するものであっても、基本的に以下のように呼び出すことができます。
client.service.{object_name}.mutate_{plural_object_name}(customer_id, array_of_operations)
ここで、 {object_name}
は操作対象となるオブジェクトの名前です。 campaign
や ad_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
のリファレンスは以下のページにありますが、 create
や update
, remove
といったフィールドが存在していることが分かるかと思います。
create
と update
は作成/更新対象のオブジェクトそのものを渡すようなイメージです。
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 という機能を使っています。
Factories に関しては以前技術ブログの記事にまとめた内容があるのでそちらを参照してもらえればと思います。
上記のコードでは、 client.operation.create_resource.ad_group
のブロック引数として ad_group
を渡しています。
この ad_group
は Google::Ads::GoogleAds::V3::Resources::AdGroup
オブジェクトになっており、
このオブジェクトをブロック内で書き換えることで Operation で処理したい内容を記述するようなイメージです。
上記の例だと、 AdGroup の作成に際して必要な情報である、
- 親になるキャンペーンを
ad_group.campaign
にresource_name
で指定 - 広告グループ名を
ad_group.name
で指定 - 広告グループのステータス(有効/停止)を
ad_group.status
で指定
ということをしています。なお、オブジェクトの作成にあたって指定が必要なアトリビュート、指定が可能なアトリビュートはオブジェクト毎に異なります。
一応各オブジェクトのリファレンスページに必須っぽいアトリビュートだと This field is required and should not be empty ...
みたいな記述があったりしますが、
この記述が無くても実は必須なアトリビュートがあったりもするので、実際はとりあえず叩いてみないと分からない部分も多いのが実際のところです。
もうちょっと複雑な 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
というメソッドがあります。
このメソッドは「複数のオブジェクトを横断して Mutate を実行できる」のですが、「原子性をもった変更を行える」という特徴も持ち合わせています。
以前まとめた内容は以下にあるので、興味があれば参照していただけると。
なお、この「まとめて 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 を参照しています。
詳細はこちら。
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 というものを指定することになっています。以下がそのリファレンスです。
見ての通り Enum として取り得る値が列挙されています。 UNSPECIFIED
と UNKNOWN
を指定することは基本的に無いと思ってよいです。
今回の AdGroupStatus であれば ENABLED
, PAUSED
, REMOVED
の3つの値が使われます。こと作成に関しては前2つのみが使われます。
Enum も Factories の仕組み自体は用意されています。以下のドキュメントがそれです。
が、上記ドキュメントにも
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
と呼ばれるものです。
これを使うと、 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
という概念が存在していたりします。
先程の 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 ワークショップで解説されていました。興味がある方は以下のリンクからどうぞ。
オブジェクトを削除する
オブジェクトの削除も同じく 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 リクエストの例です。
validate_only
を true
にしてリクエストを行うと、 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 に関係する各種ドキュメントの歩き方を解説していきます。お楽しみに。