google-adwords-api 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|