Feedforce Developer Blog

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

真・Google Ads API 徹底入門 その2 Search 編

こんにちは id:hano_tea です。

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

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

developer.feedforce.jp

今回は Google Ads API から情報を取得するための仕組み Search について解説します。

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

目次

情報の取得 Search と SearchStream

情報の取得は GoogleAdsService を使用して行います。

developers.google.com

なお、ここでいう「情報」とは、

  • Google Ads 上の各種オブジェクト自体の情報
  • 各種広告成果レポートデータ

などのことを指します。オブジェクトの情報取得とレポートデータの取得を同じ仕組み上で行うことができます。

ちなみに、 API v2 までは Search というメソッドのみでしたが、 v3 から SearchStream という新たなメソッドが追加されました。 この SearchSearchStream の違いについては後ほど説明します。 今は2種類のメソッドがあるということだけ頭の隅に置いておいていただければ。

基本的なつかいかた

Search は Google Ads API で情報の取得を行う際に使用する最も基本的なメソッドです。 以下のようなコードで Search を実行することができます。

customer_id = '1112223333'
query = '...'
result = client.service.google_ads.search(customer_id, query)

ここで、customer_id は「情報を取得したい対象となる広告アカウントの ID」です。 また、query は「取得したい情報を指定するためのクエリ」です。

client は前回の記事で紹介した Client のインスタンスです。 この後のコードも、基本的には client という変数にインスタンスが格納されている前提となります。

Search においては、情報の取得に使う query 文字列が重要になります。こちらについては次の項目で改めて説明します。

ここから先の解説はしばらく(SearchStream も基本的な使い方はほぼ共通しているので)Search に絞って解説します。

Search で使われるクエリ GAQL

Search で使われるクエリは Google Ads Query Language, 略称 GAQL の仕様に則って記述します。

developers.google.com

Adwords のときも類似の概念として Adwords Query Language, 略称 AWQL というものがありましたが、これとは別物です。

SQL ライクな構文で取得したい情報について記述することができ、慣れるとドキュメントなしでもサクッとクエリを書けたりします。

書き方は以下のドキュメントを参照してもらうのが良いと思います。

developers.google.com

基本的には以下のような構成になります。

SELECT
  取得したい情報、メトリクス、セグメント
FROM
  取得対象のオブジェクト名
WHERE
  取得対象のフィルタ条件
ORDER BY
  取得したい情報やメトリクス (ASC | DESC)
LIMIT
  上限取得件数

PARAMETERS というものも追加されていましたが、使うタイミングはそこまで多くはなさそうなので、 気になる方はこちらのドキュメントを参照ください。

この中で最低限必要なのは SELECT, FROM のみです。以下のようなクエリは有効なものになります。

SELECT campaign.name FROM campaign

上記のクエリは、特定の… search メソッドに渡した customer_id で指定した広告アカウント以下にある、全てのキャンペーンの名前を取得します。

最低限過ぎてわかりにくいのでもう少し内容を増やしてみます。 キャンペーン ID が '2223334444' なキャンペーンの、キャンペーン名と resource_name を取得する場合、以下のようになります。

SELECT
  campaign.name,
  campaign.resource_name
FROM
  campaign
WHERE
  campaign.id = '2223334444'

GAQL を生成する便利ツール Interactive Google Ads Query Builder

GAQL は慣れればリファレンスを見ながら手で書くこともできますが、 FROM に何を指定できるのか、 FROM に指定したものに対して SELECT は何が使えるのか、 はたまた WHERE の条件はどう書くのか…などなど、なかなか何も見ずにシュッと書くのは難しいものです。

そんな悩みをある程度解決してくれる便利ツールを Google は用意してくれています。 それが Interactive Google Ads Query Builder です。

developers.google.com

これを使うと、取得したいオブジェクトと、指定したいオブジェクトに対してどんなアトリビュートを指定できるか、 どんな条件で絞り込みができるかなどを確認しながらクエリを組み立てることができます。

特に、最初に取得対象のオブジェクトの種類を選択すると、それ以降の選択肢には 「そのオブジェクトに対して指定できる項目のみ」しか現れなくなるのでとても便利です。

ただし、これで組み立てたクエリが100%有効なものになっているという保証は無いのでご注意ください。 (クエリの文法的には有効だけどリクエストとしては不正なこともある)

実際に Search を試してみる

ということで実際に Search をしてみましょう。 以下のようなコードを入力し、キャンペーンを取得してみます。

customer_id = '...' # キャンペーン情報を取得したい広告アカウントの ID
query = 'SELECT campaign.name FROM campaign'
result = client.service.google_ads.search(customer_id, query)

=> #<Google::Gax::PagedEnumerable:0x000055f617c349a8
 @func=#<Proc:0x000055f617c43098@/.../vendor/bundle/ruby/2.6.0/gems/google-gax-1.8.1/lib/google/gax/api_callable.rb:353>,
 @page=
  #<Google::Gax::PagedEnumerable::Page:0x000055f61848d708
   @resource_field="results",
   @response=
    <Google::Ads::GoogleAds::V3::Services::SearchGoogleAdsResponse: results: [<Google::Ads::GoogleAds::V3::Services::GoogleAdsRow: ...

なんかわーっと色々な情報がコンソール上に流れていれば OK です。内容についてはこの後説明します。

レポートデータを取得する

上記の例ではキャンペーンオブジェクトそのものの取得をしてみましたが、同じようにレポートデータの取得もできます。

以下のようなクエリを発行すると、あるアカウントの全期間の、キャンペーン毎の広告成果を取得することができます。

query = 'select metrics.clicks from campaign'
customer_id = '1112223333'
result = client.service.google_ads.search(customer_id, query)

広告成果レポート系の項目は基本的に metrics を指定することで取得できます。色々ある上に表記が独特なものもあるので注意しましょう。 一覧は以下のページで参照できます。

developers.google.com

が、一覧で見てもあんまり意味はないので…レポートを取得したくなった際は、 そのレポート取得対象のオブジェクトを Reports ページで探しましょう。例えばキャンペーンなら以下のページになります。

developers.google.com

この中にレポート系で指定できる metricssegments がまとまっているので、ここから必要な指標などを探すのが良いと思います。 (このあたりの画面の見方は別の記事で解説予定です)

レスポンスオブジェクトの取り扱い Search編

SearchStream はレスポンスのオブジェクトが Search と異なるものになるので注意してください。別途解説します。

さて、上記のように Search を使うことでオブジェクトの情報やレポートデータが取得できる、ということは分かりました。 ところで、このレスポンス…上記までのサンプルコードで言うところの result には何が入っているのでしょうか。見てみましょう。

> result
=> #<Google::Gax::PagedEnumerable:0x0000556c36755628
 @func=#<Proc:0x0000556c36754908@/.../vendor/bundle/ruby/2.6.0/gems/google-gax-1.8.1/lib/google/gax/api_callable.rb:353>,
 @page=
  #<Google::Gax::PagedEnumerable::Page:0x0000556c367cdc90
   @resource_field="results",
   @response=
    <Google::Ads::GoogleAds::V3::Services::SearchGoogleAdsResponse: results: [<Google::Ads::GoogleAds::V3::Services::GoogleAdsRow: ...

おそらく結構な量の謎のログがわっと表示されたのではないかと思います。上記の例は大量に出てきたログの一部だけ切り取りました。 Google::Gax::PagedEnumerable クラスのオブジェクトが返ってきていることが分かります。次の節ではこのオブジェクトの扱い方について説明します。

Google::Gax::PagedEnumerable

search の結果は Google::Gax::PagedEnumerable クラスのインスタンスとして返ってきます。(リンクは PagedEnumerable の実装へのリンクです)

名前の通り、 Enumerable で、 Ruby の Enumerable を継承しています。また、これも名前の通りですが page の概念があります。 pagesearch の結果のまとまりの単位、です。

例えば検索結果の件数が 100,000 件あったとき、Google からのレスポンスは 10,000 件毎の塊に分割されて取り扱われたりしますが、 この塊が page です。search では、API へのリクエストはこの page 単位で行われます。

1 つの page を何件の塊として扱うかはリクエスト時に指定することができます。上限は 10,000 件です。 あまり大きな page にするとリクエスト毎に大量のデータをやりとりすることになるので、必要十分なサイズに設定するのをオススメします。

Enumerable なので取得結果に対する繰り返し処理を行うためのメソッドが色々使えます。 find, select, sort, map, to_a, each, reduce などなど。

よく使う Enumerable なメソッドとしては、

  • 単純に全ての結果の配列だけ欲しいなら to_a
  • 全ての結果になにか手を加えた結果の配列が欲しいなら map

あたりが挙げられるかと思います。

ちなみに、 Enumerable なメソッドを用いると、 次ページが必要になった時に自動的に次ページ取得リクエストが行われます

なので、 to_a は全結果を取得して配列化するメソッドとして機能します。 また、 find などは結果が見つかった時点で処理を打ち切るので、一部のページの取得だけで処理が終わることもあります。

Google::Gax::PagedEnumerable 独自に定義されているメソッドもありますが、内部で使われているものがほとんどで、 1ページ分の内容やメタデータ、サマリーなどが欲しいときに response を使うぐらいでしょうか。

この response の中身は Google::Ads::GoogleAds::Vx::Services::SearchGoogleAdsResponse というクラスのオブジェクトです。 こちらについては後ほど説明します。

GoogleAdsRow

上述の PagedEnumerable に対して to_a を実行した際に得られる配列の中身や、 map を実行した際にブロック引数として渡されるオブジェクトは GoogleAdsRow というクラスになっています。 この中には クエリで指定した取得対象の情報 が格納されています。

Search で使ったクエリ、特に segment を指定しているかどうかにもよるのですが、 例えばキャンペーンの一覧を取得するようなクエリで Search をした場合、キャンペーン1つにつき GoogleAdsRow が1つ、 対応する形で情報が取得できます。

Search した結果を実際にコード中で使う際はこの GoogleAdsRow から必要なデータを適宜取り出して扱う必要があります。

GoogleAdsRow は以下のページにリファレンスがあります。

developers.google.com

Row の中にはクエリによって取得された各種オブジェクトの情報などが格納されています。 フィールドが大量にありますが、クエリで指定した部分だけしか中身が入らず、指定していないものは nil あるいは空配列で返ってきます。 実際のレスポンス例を以下に示します。 Campaign の情報を取得したときの例です。

<Google::Ads::GoogleAds::V3::Services::GoogleAdsRow: customer: nil, campaign: <Google::Ads::GoogleAds::V3::Resources::Campaign: resource_name: "customers/.../campaigns/...", id: nil, name: <Google::Protobuf::StringValue: value: "テスト用キャンペーン">, status: :UNSPECIFIED, 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: []>, ad_group: nil, metrics: nil, ad_group_ad: nil, ad_group_criterion: nil, bidding_strategy: nil, campaign_budget: nil, campaign_criterion: nil, keyword_view: nil, recommendation: nil, geo_target_constant: nil, ad_group_bid_modifier: nil, campaign_bid_modifier: nil, shared_set: nil, shared_criterion: nil, campaign_shared_set: nil, topic_constant: nil, keyword_plan: nil, keyword_plan_campaign: nil, keyword_plan_negative_keyword: nil, keyword_plan_ad_group: nil, keyword_plan_keyword: nil, change_status: nil, user_list: nil, video: nil, gender_view: nil, billing_setup: nil, account_budget: nil, account_budget_proposal: nil, topic_view: nil, parental_status_view: nil, feed: nil, display_keyword_view: nil, age_range_view: nil, campaign_draft: nil, feed_item: nil, hotel_group_view: nil, label: nil, managed_placement_view: nil, product_group_view: nil, language_constant: nil, ad_group_audience_view: nil, feed_mapping: nil, user_interest: nil, remarketing_action: nil, customer_manager_link: nil, customer_client_link: nil, campaign_feed: nil, customer_feed: nil, carrier_constant: nil, ad_group_feed: nil, search_term_view: nil, campaign_audience_view: nil, customer_client: nil, hotel_performance_view: nil, campaign_experiment: nil, extension_feed_item: nil, operating_system_version_constant: nil, mobile_app_category_constant: nil, customer_negative_criterion: nil, ad_schedule_view: nil, media_file: nil, domain_category: nil, feed_placeholder_view: nil, mobile_device_constant: nil, segments: nil, conversion_action: nil, custom_interest: nil, asset: nil, dynamic_search_ads_search_term_view: nil, ad_group_simulation: nil, campaign_label: nil, product_bidding_category_constant: nil, ad_group_criterion_simulation: nil, campaign_criterion_simulation: nil, ad_group_extension_setting: nil, campaign_extension_setting: nil, customer_extension_setting: nil, ad_group_label: nil, feed_item_target: nil, shopping_performance_view: nil, detail_placement_view: nil, group_placement_view: nil, ad_group_ad_label: nil, ad_group_criterion_label: nil, click_view: nil, location_view: nil, customer_label: nil, geographic_view: nil, landing_page_view: nil, mutate_job: nil, expanded_landing_page_view: nil, paid_organic_search_term_view: nil, ad_parameter: nil, ad_group_ad_asset_view: nil, distance_view: nil, currency_constant: nil, user_location_view: nil>

上記のように殆どの中身が nil になっていることが分かります。(コンソールに表示するとだいたい酷いことになります)

GoogleAdsRow から必要な中身だけ取り出す

上記のように GoogleAdsRow は一見するとちょっと身構えてしまう感じの見た目になっていますが、必要な情報を取り出すのは難しくありません。 例えば上記のキャンペーンのレスポンスからキャンペーン名を取り出すのは以下のようにすれば良いです。

# row という変数に GoogleAdsRow オブジェクトが入っている想定
name = row.campaign.name.value

注意が必要な点が一つあり、キャンペーン名が格納されている row.campaign.name には直接文字列が格納されているわけではなく、 Google::Protobuf::StringValue というクラスのオブジェクトが入っています。 実際の文字列情報はこのオブジェクトの value を取り出す必要があります。ちょっとめんどくさい。

今回取得したのはキャンペーン名なので StringValue というクラスのオブジェクトでしたが、 整数値の場合は IntValue, 浮動小数点値の場合は FloatValue など、別のクラスになります。 その場合でも value で取り出すという点は共通しているので、同じように値を取り出すことが出来ます。

ちなみに、上記の実際のレスポンスを見ると分かるのですが、 resource_nameStringValue ではなく直に文字列が入っているので value を呼ばなくてもよかったりします。

SearchGoogleAdsResponse

PagedEnumerable のところで軽く触れましたが、 response というメソッドを使うと SearchGoogleAdsResponse クラスのオブジェクトが取得できます。

正確に書くと前述の通り Google::Ads::GoogleAds::Vx::Services::SearchGoogleAdsResponse です。 Vx は API バージョンが入ります。V3 みたいな感じ。

この中には主に、

  • 1ページ分の検索結果 results
  • 検索結果総件数 total_results_count
  • 検索結果の集計家結果(サマリー) summary_row

などのデータが格納されています。他にもいくつか入っているデータがありますが、よく使うのは上の3つです。

特に下2つの total_results_count, summary_rowSearchGoogleAdsResponse でしか確認できない情報になっています。

実際にレスポンスの中身を見てみる

では実際にレスポンスオブジェクトの中身を見てみましょう。 前回のコードの実行時に result という変数にレスポンスを格納しておいたので、それを見てみます。

とりあえず検索結果を全て見てみるため、 to_a してみます。

> result.to_a
=> [<Google::Ads::GoogleAds::V3::Services::GoogleAdsRow: customer: nil, campaign: <Google::Ads::GoogleAds::V3::Resources::Campaign: resource_name: "customers/7775714709/campaigns/893252614", id: nil, name: <Google::Protobuf::StringValue: value: "【Seed data】テスト用キャンペーン">, ...

配列が返ってきていることは分かりますが、余計な情報が多くて何が格納されているのか、何が取得できているのか非常に見難いと思います。もうちょっとどうにかしてほしいところですが… 今回の例ではキャンペーン名を取得しているので、そこだけ取り出してみます。

> result.map { |row| row.campaign.name.value }
=> ["テスト用キャンペーン",
...,
...]

取得結果からキャンペーン名だけ抜き出しました。こんな感じに扱うことができます。 ちなみに、今回使っているテスト用アカウントは削除済みキャンペーンなどが多く含まれているので、こんな感じの結果が返ってきます。

response の中も見てみましょう。

> result.response.total_results_count
=> 0
> result.response.summary_row
=> nil

何も取れませんね。 API v3 から、これらの値は明示的に取得することを設定しないと取得できなくなっています。

また、今回のクエリだとそもそもレポートデータが無いので、サマリ行は特に意味のある値は恐らく返ってこないと思われます。

以下のように Search しなおすと total_results_count を取得することが出来るようになります。

> result = client.service.google_ads.search(customer_id, query, return_total_results_count: true)
=> ...
> result.response.total_results_count
=> 22

レスポンスオブジェクトの取り扱い SearchStream 編

ここまでは Search を使った情報取得について説明してきました。 最初の方で Search とは別に SearchStream というものの存在を紹介しましたが、覚えているでしょうか?

SearchStream も Search と同様、 GAQL で取得したい情報についてのクエリを記述し、 それを用いてリクエストを行うことで欲しい情報を取得します。

では何が違うのかというと、以下のような点で違いがあります。

  • SearchStream は、検索結果件数によらずリクエスト回数は1回になる
  • SearchStream は、検索結果全件を取得するため、検索結果を見ながら取得件数を絞る、ということはできない
  • SearchStream は、総検索件数 'total_results_count' を取れない
  • SearchStream は、 Enumerator オブジェクトを返す

違いについての公式ドキュメントでの解説はこちら。

developers.google.com

上記ドキュメントにも

By eliminating the round-trip network time required to request each individual page of a Search response, depending on your application, SearchStream may offer improved performance over paging. This advantage becomes more apparent as reports grow much larger.

とある通り、 使うタイミングとしては「大量のデータを取得したいとき」に使うのが有効です。目安的にはページング処理が入ってしまう 10,000 件以上のときでしょうか。 全件取得することが分かっている時にも SearchStream を使っておいてもいいかもしれません。

文字で色々書いてもイマイチピンとこないので、実際に叩いてみましょう。

> result = client.service.google_ads.search_stream(customer_id, query)
> result
=> #<Enumerator: ...>

Enumerator が返ってきています。とりあえず何も考えず全件取ってみましょう。 to_a してみます。

> arr = result.to_a
=> [<Google::Ads::GoogleAds::V3::Services::SearchGoogleAdsStreamResponse: ...

SearchGoogleAdsStreamResponse オブジェクトが返ってきました。 ちなみに、ここでもう一回 to_a しようとするとエラーになります。ストリームとして一回処理が完了して閉じてしまったので、再度処理を走らせることはできません。

> arr = result.to_a
> result.to_a
=> GRPC::Core::CallError: Cannot run batch on closed call

さて、一回目の to_a の結果に話を戻します。 arr に結果を格納しておいたのでそれを見てみます。

> arr = result.to_a
=> [<Google::Ads::GoogleAds::V3::Services::SearchGoogleAdsStreamResponse: results: [<Google::Ads::GoogleAds::V3::Services::GoogleAdsRow: ...

to_a しているので当たり前といえば当たり前ですが、配列として結果が得られています。中身は SearchGoogleAdsStreamResponse のようです。 SearchGoogleAdsStreamResponse についてのドキュメントは以下。

developers.google.com

SearchGoogleAdsStreamResponse の中には results が居て、 GoogleAdsRow の配列が格納されています。 件数が多いリクエスト(1万件以上)をすると results 配列内の要素数が増えます。ちょうど Search で 10,000 件のページサイズを指定したときと同じ感じに分割されます。

ここでは試しに全ての検索結果の配列を取得したいときの処理を考えてみます。 前述の通り、 to_a した際の配列には SearchGoogleAdsStreamResponse が格納されていました。 これにはページ単位のレスポンスが格納されているので、その中身まで見て取り出してあげないといけません。ちょっと面倒。

result = client.service.google_ads.search_stream(customer_id, query)
result.flat_map do |page|
  pages.results.map do |row|
    ...
  end
end

flat_map という便利なメソッドがあるのでこれを使います。ただの map だと最後に flatten しないといけないのを省略できます。 flat_map の中のブロック引数には page に相当する SearchGoogleAdsStreamResponse が来ますが、これは results というアトリビュート内に配列で GoogleAdsRow を持っているだけです。 なので、 page.results で取り出した配列内の各要素を map で順次取り出してあげる…というような流れの処理になります。

コード的には若干ややこしくなる(クラスメソッドの類があんまり充実してない)のが難点ですが、 大量の検索結果を扱わなければならない時の処理時間は劇的に短くなるので、使い所を選べば強力な武器になりそうです。

例えば、 100,000 件ちょっとあるような検索語句レポート全件の取得を Search で行うと結果の取得だけで30秒以上かかってしまいますが、 SearchStream を使うと 10 秒かからずに全件取得が可能でした。強力さが分かるかと思います。

Get は使ってはいけない

見出しでも書いていますが、 Google Ads API に用意されている各種 Get メソッドは 使用しないで ください。 大事なことなので何回も言います。

ここまで search をずっと見てきましたが、 Google Ads API には各種オブジェクトを取得するための get メソッドが用意されていたりします。 例えば、以下の CampaignService に生えている GetCampaign メソッドがそれです。

developers.google.com

これを使うと、指定したオブジェクトの全てのアトリビュートを一気に取得することができます。

できるのですが、これらの get メソッド群は 使用しない ようにしてください。叩くとしたら開発環境で日に数回使うぐらいに留めることを厳守してください。 下記の公式ドキュメントにも書いてある通り、 get 系メソッドの使用は非推奨です。

developers.google.com

また、下記の API のリミットに関するページにもある通り、 get は1日でたったの 1,000 回しか叩けません。

developers.google.com

get を使わなくてはいけないケースは恐らくほとんど存在せず、 search のみで情報取得は完結出来るはずです。 get は開発時に手元で試すに留め、アプリケーションコード上では使わないことをお勧めします。

[PR] うっかり get を使ってしまうのを防止する Rubocop Custom Cop のご案内

上述の通り、 get の使用は基本的に避けるべき…なのですが、普段から Google Ads API に触れていないと(下手すると触れていても)うっかり使ってしまう可能性があります。 レビューなどで防げれば良いですが、人間は見逃し、ミスをする生き物なので過信は出来ません。

ということで、人間の力に頼らず機械にチェックしてもらうための仕組みを用意しました。

github.com

Rubocop を導入されているプロジェクトなら、上記の Custom Cop を導入するだけで get 系メソッドの使用を検知、警告を出してくれるようになります。ご活用ください。

次回予告

さて、今回は真・Google Ads API 徹底入門 その2 Search 編ということで、 前回の基礎編の内容を踏まえ、 Google Ads API で情報を取得する際の基本について解説しました。

次回は Google Ads API で Google 広告の操作…オブジェクトの作成や編集、削除をする方法について解説していきます。お楽しみに。