google-adwords-api 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog +3 -0
- data/examples/v201109/parallel_report_download.rb +159 -0
- data/lib/adwords_api/api_config.rb +1 -1
- data/lib/adwords_api/report_utils.rb +38 -12
- metadata +3 -2
data/ChangeLog
CHANGED
@@ -0,0 +1,159 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#
|
3
|
+
# Author:: api.dklimkin@gmail.com (Danial Klimkin)
|
4
|
+
#
|
5
|
+
# Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# License:: Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
16
|
+
# implied.
|
17
|
+
# See the License for the specific language governing permissions and
|
18
|
+
# limitations under the License.
|
19
|
+
#
|
20
|
+
# This example gets and downloads an Ad Hoc report from a XML report definition.
|
21
|
+
|
22
|
+
require 'rubygems'
|
23
|
+
require 'thread'
|
24
|
+
|
25
|
+
require 'adwords_api'
|
26
|
+
require 'adwords_api/utils'
|
27
|
+
|
28
|
+
API_VERSION = :v201109
|
29
|
+
# Number of parallel threads to spawn.
|
30
|
+
THREADS = 10
|
31
|
+
# Maximum number of retries for 500 errors.
|
32
|
+
MAX_RETRIES = 5
|
33
|
+
# Timeout between retries in seconds.
|
34
|
+
BACKOFF_FACTOR = 5
|
35
|
+
|
36
|
+
def parallel_report_download()
|
37
|
+
# AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
|
38
|
+
# when called without parameters.
|
39
|
+
adwords = AdwordsApi::Api.new
|
40
|
+
|
41
|
+
# To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
|
42
|
+
# the configuration file or provide your own logger:
|
43
|
+
# adwords.logger = Logger.new('adwords_xml.log')
|
44
|
+
|
45
|
+
# Determine list of customer IDs to retrieve report for. For this example we
|
46
|
+
# will use ServicedAccountService to get all IDs in hierarchy.
|
47
|
+
|
48
|
+
serviced_account_srv = adwords.service(:ServicedAccountService, API_VERSION)
|
49
|
+
|
50
|
+
# Get the account hierarchy for this account.
|
51
|
+
selector = {:enable_paging => false}
|
52
|
+
|
53
|
+
# Run the request at the MCC level.
|
54
|
+
graph = adwords.use_mcc() {serviced_account_srv.get(selector)}
|
55
|
+
|
56
|
+
# Using queue to balance load between threads.
|
57
|
+
queue = Queue.new()
|
58
|
+
|
59
|
+
if graph and graph[:accounts] and !graph[:accounts].empty?
|
60
|
+
graph[:accounts].each {|account| queue << account[:customer_id]}
|
61
|
+
else
|
62
|
+
raise StandardError, 'Can not retrieve any customer ID'
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get report utilities for the version.
|
66
|
+
report_utils = adwords.report_utils(API_VERSION)
|
67
|
+
|
68
|
+
# Define report definition. You can also pass your own XML text as a string.
|
69
|
+
report_definition = {
|
70
|
+
:selector => {
|
71
|
+
:fields => ['CampaignId', 'Id', 'Impressions', 'Clicks', 'Cost'],
|
72
|
+
# Predicates are optional.
|
73
|
+
:predicates => {
|
74
|
+
:field => 'Status',
|
75
|
+
:operator => 'IN',
|
76
|
+
:values => ['ENABLED', 'PAUSED']
|
77
|
+
}
|
78
|
+
},
|
79
|
+
:report_name => 'Custom ADGROUP_PERFORMANCE_REPORT',
|
80
|
+
:report_type => 'ADGROUP_PERFORMANCE_REPORT',
|
81
|
+
:download_format => 'CSV',
|
82
|
+
:date_range_type => 'LAST_7_DAYS',
|
83
|
+
# Enable to get rows with zero impressions.
|
84
|
+
:include_zero_impressions => false
|
85
|
+
}
|
86
|
+
|
87
|
+
puts "Retrieving %d reports with %d threads:" % [queue.size, THREADS]
|
88
|
+
|
89
|
+
reports_succeeded = Queue.new()
|
90
|
+
reports_failed = Queue.new()
|
91
|
+
|
92
|
+
# Creating a mutex to control access to the queue.
|
93
|
+
queue_mutex = Mutex.new
|
94
|
+
|
95
|
+
# Start all the threads.
|
96
|
+
threads = (1..THREADS).map do |thread_id|
|
97
|
+
Thread.new(report_definition) do |local_def|
|
98
|
+
cid = nil
|
99
|
+
begin
|
100
|
+
cid = queue_mutex.synchronize {(queue.empty?) ? nil : queue.pop(true)}
|
101
|
+
if cid
|
102
|
+
retry_count = 0
|
103
|
+
file_name = "adgroup_%010d.csv" % cid
|
104
|
+
puts "[%2d/%d] Loading report for customer ID %s into '%s'..." %
|
105
|
+
[thread_id, retry_count,
|
106
|
+
AdwordsApi::Utils.format_id(cid), file_name]
|
107
|
+
begin
|
108
|
+
report_utils.download_report_as_file(local_def, file_name, cid)
|
109
|
+
reports_succeeded << {:cid => cid, :file_name => file_name}
|
110
|
+
rescue AdwordsApi::Errors::ReportError => e
|
111
|
+
if e.http_code == 500 && retry_count < MAX_RETRIES
|
112
|
+
retry_count += 1
|
113
|
+
sleep(retry_count * BACKOFF_FACTOR)
|
114
|
+
retry
|
115
|
+
else
|
116
|
+
puts(("Report failed for customer ID %s with code %d after %d " +
|
117
|
+
"retries.") % [cid, e.http_code, retry_count + 1])
|
118
|
+
reports_failed <<
|
119
|
+
{:cid => cid, :http_code => e.http_code, :message => e.message}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end while (cid != nil)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Wait for all threads to finish.
|
128
|
+
threads.each { |aThread| aThread.join }
|
129
|
+
|
130
|
+
puts 'Download completed, results:'
|
131
|
+
puts 'Successful reports:'
|
132
|
+
while !reports_succeeded.empty? do
|
133
|
+
result = reports_succeeded.pop()
|
134
|
+
puts "\tClient ID %s => '%s'" %
|
135
|
+
[AdwordsApi::Utils.format_id(result[:cid]), result[:file_name]]
|
136
|
+
end
|
137
|
+
puts 'Failed reports:'
|
138
|
+
while !reports_failed.empty? do
|
139
|
+
result = reports_failed.pop()
|
140
|
+
puts "\tClient ID %s => Code: %d, Message: '%s'" %
|
141
|
+
[AdwordsApi::Utils.format_id(result[:cid]),
|
142
|
+
result[:http_code], result[:message]]
|
143
|
+
end
|
144
|
+
puts 'End of results.'
|
145
|
+
end
|
146
|
+
|
147
|
+
if __FILE__ == $0
|
148
|
+
begin
|
149
|
+
parallel_report_download()
|
150
|
+
|
151
|
+
# HTTP errors.
|
152
|
+
rescue AdsCommon::Errors::HttpError => e
|
153
|
+
puts "HTTP Error: %s" % e
|
154
|
+
|
155
|
+
# API errors.
|
156
|
+
rescue AdwordsApi::Errors::ReportError => e
|
157
|
+
puts "Reporting Error: %s" % e.message
|
158
|
+
end
|
159
|
+
end
|
@@ -40,6 +40,7 @@ module AdwordsApi
|
|
40
40
|
#
|
41
41
|
# Args:
|
42
42
|
# - report_definition: definition of the report in XML text or hash
|
43
|
+
# - cid: optional customer ID to run against
|
43
44
|
#
|
44
45
|
# Returns:
|
45
46
|
# - report body
|
@@ -49,8 +50,8 @@ module AdwordsApi
|
|
49
50
|
# definition is invalid
|
50
51
|
# - AdwordsApi::Errors::ReportError if server-side error occurred
|
51
52
|
#
|
52
|
-
def download_report(report_definition)
|
53
|
-
return get_report_response(report_definition).body
|
53
|
+
def download_report(report_definition, cid = nil)
|
54
|
+
return get_report_response(report_definition, cid).body
|
54
55
|
end
|
55
56
|
|
56
57
|
# Downloads a report and saves it to a file.
|
@@ -58,6 +59,7 @@ module AdwordsApi
|
|
58
59
|
# Args:
|
59
60
|
# - report_definition: definition of the report in XML text or hash
|
60
61
|
# - path: path to save report to
|
62
|
+
# - cid: optional customer ID to run against
|
61
63
|
#
|
62
64
|
# Returns:
|
63
65
|
# - nil
|
@@ -67,8 +69,8 @@ module AdwordsApi
|
|
67
69
|
# definition is invalid
|
68
70
|
# - AdwordsApi::Errors::ReportError if server-side error occurred
|
69
71
|
#
|
70
|
-
def download_report_as_file(report_definition, path)
|
71
|
-
report_body = download_report(report_definition)
|
72
|
+
def download_report_as_file(report_definition, path, cid = nil)
|
73
|
+
report_body = download_report(report_definition, cid)
|
72
74
|
save_to_file(report_body, path)
|
73
75
|
return nil
|
74
76
|
end
|
@@ -83,17 +85,19 @@ module AdwordsApi
|
|
83
85
|
REPORT_DEFINITION_ORDER = {
|
84
86
|
:root => [:selector, :report_name, :report_type, :date_range_type,
|
85
87
|
:download_format, :include_zero_impressions],
|
86
|
-
:selector => [:fields, :predicates, :date_range],
|
87
|
-
:predicates => [:field, :operator, :values]
|
88
|
+
:selector => [:fields, :predicates, :date_range, :ordering, :paging],
|
89
|
+
:predicates => [:field, :operator, :values],
|
90
|
+
:ordering => [:field, :sort_order],
|
91
|
+
:paging => [:start_index, :number_results]
|
88
92
|
}
|
89
93
|
|
90
94
|
# Send POST request for a report and returns Response object.
|
91
|
-
def get_report_response(report_definition)
|
95
|
+
def get_report_response(report_definition, cid = nil)
|
92
96
|
definition_text = get_report_definition_text(report_definition)
|
93
97
|
data = {'__rdxml' => definition_text}
|
94
98
|
url = @api.api_config.adhoc_report_download_url(
|
95
99
|
@api.config.read('service.environment'), @version)
|
96
|
-
headers = get_report_request_headers(url)
|
100
|
+
headers = get_report_request_headers(url, cid)
|
97
101
|
response = AdsCommon::Http.post_response(url, data, @api.config, headers)
|
98
102
|
check_for_errors(response)
|
99
103
|
return response
|
@@ -113,15 +117,16 @@ module AdwordsApi
|
|
113
117
|
end
|
114
118
|
|
115
119
|
# Prepares headers for report request.
|
116
|
-
def get_report_request_headers(url)
|
120
|
+
def get_report_request_headers(url, cid = nil)
|
117
121
|
credentials = @api.credential_handler.credentials
|
118
122
|
auth_handler = @api.get_auth_handler(
|
119
123
|
@api.config.read('service.environment'), @version)
|
120
124
|
auth_string = auth_handler.auth_string(
|
121
125
|
credentials, HTTPI::Request.new(url))
|
126
|
+
customer_id = validate_cid(cid || credentials[:clientCustomerId])
|
122
127
|
headers = {
|
123
128
|
'Authorization' => auth_string,
|
124
|
-
'ClientCustomerId' =>
|
129
|
+
'ClientCustomerId' => customer_id,
|
125
130
|
'Content-Type' => 'multipart/form-data',
|
126
131
|
'developerToken' => credentials[:developerToken]
|
127
132
|
}
|
@@ -133,6 +138,20 @@ module AdwordsApi
|
|
133
138
|
open(path, 'wb') { |file| file.puts(data) } if path
|
134
139
|
end
|
135
140
|
|
141
|
+
# Validates the customer ID specified is correct.
|
142
|
+
def validate_cid(cid)
|
143
|
+
if (cid.kind_of?(Integer) || (
|
144
|
+
cid.kind_of?(String) && (
|
145
|
+
((/\d{3}-\d{3}-\d{4}/ =~ cid) == 0) ||
|
146
|
+
((/\d{10}/ =~ cid) == 0)
|
147
|
+
)))
|
148
|
+
return cid
|
149
|
+
else
|
150
|
+
raise AdwordsApi::Errors::BadCredentialsError,
|
151
|
+
"Invalid client customer ID: %s" % cid
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
136
155
|
# Checks downloaded data for error signature. Raises ReportError if it
|
137
156
|
# detects an error.
|
138
157
|
def check_for_errors(response)
|
@@ -195,8 +214,15 @@ module AdwordsApi
|
|
195
214
|
def_order = REPORT_DEFINITION_ORDER[name]
|
196
215
|
var_order = def_order.reject {|field| !node.include?(field)}
|
197
216
|
node.keys.each do |key|
|
198
|
-
if
|
199
|
-
|
217
|
+
if REPORT_DEFINITION_ORDER.include?(key)
|
218
|
+
case node[key]
|
219
|
+
when Hash
|
220
|
+
add_report_definition_hash_order(node[key], key)
|
221
|
+
when Array
|
222
|
+
node[key].each do |item|
|
223
|
+
add_report_definition_hash_order(item, key)
|
224
|
+
end
|
225
|
+
end
|
200
226
|
end
|
201
227
|
end
|
202
228
|
node[:order!] = var_order
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 4
|
8
|
-
-
|
9
|
-
version: 0.4.
|
8
|
+
- 3
|
9
|
+
version: 0.4.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Danial Klimkin
|
@@ -535,6 +535,7 @@ files:
|
|
535
535
|
- examples/v201109/get_all_paused_campaigns.rb
|
536
536
|
- examples/v201109/handle_partial_failures.rb
|
537
537
|
- examples/v201109/get_related_keywords.rb
|
538
|
+
- examples/v201109/parallel_report_download.rb
|
538
539
|
- examples/v201109/oauth_handling.rb
|
539
540
|
- examples/v201109/add_ads.rb
|
540
541
|
- examples/v201109/get_campaign_alerts.rb
|