7
7
import re
8
8
import shutil
9
9
import sys
10
+ import io
10
11
import tempfile
11
12
import json
12
13
import time
@@ -84,8 +85,8 @@ def _fetch_client_customers(self):
84
85
85
86
def download_data ():
86
87
"""Creates an AdWordsApiClient and downloads the data"""
87
- logger = logging .basicConfig (level = logging .INFO ,
88
- format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )
88
+ logging .basicConfig (level = logging .INFO ,
89
+ format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )
89
90
90
91
logging .info ('Adwords API version: ' + str (config .api_version ()))
91
92
@@ -100,22 +101,32 @@ def download_data_sets(api_client: AdWordsApiClient):
100
101
api_client: AdWordsApiClient
101
102
102
103
"""
104
+
105
+ predicates = [{'field' : 'Status' ,
106
+ 'operator' : 'IN' ,
107
+ 'values' : ['ENABLED' ,
108
+ 'PAUSED' ,
109
+ 'DISABLED' ]
110
+ }, {
111
+ 'field' : 'Impressions' ,
112
+ 'operator' : 'GREATER_THAN' ,
113
+ 'values' : [0 ]
114
+ }]
115
+
116
+ if config .ignore_removed_campaigns ():
117
+ predicates .append ({
118
+ 'field' : 'CampaignStatus' ,
119
+ 'operator' : 'NOT_EQUALS' ,
120
+ 'values' : 'REMOVED'
121
+ })
122
+
103
123
download_performance (api_client ,
104
124
PerformanceReportType .AD_PERFORMANCE_REPORT ,
105
- fields = ['Date' , 'Id' , 'Device' , 'AdNetworkType2' ,
125
+ fields = ['Date' , 'Id' , 'AdGroupId' , ' Device' , 'AdNetworkType2' ,
106
126
'ActiveViewImpressions' , 'AveragePosition' ,
107
127
'Clicks' , 'Conversions' , 'ConversionValue' ,
108
128
'Cost' , 'Impressions' ],
109
- predicates = [{'field' : 'Status' ,
110
- 'operator' : 'IN' ,
111
- 'values' : ['ENABLED' ,
112
- 'PAUSED' ,
113
- 'DISABLED' ]
114
- }, {
115
- 'field' : 'Impressions' ,
116
- 'operator' : 'GREATER_THAN' ,
117
- 'values' : [0 ]
118
- }]
129
+ predicates = predicates
119
130
)
120
131
121
132
download_account_structure (api_client )
@@ -132,7 +143,6 @@ def download_performance(api_client: AdWordsApiClient,
132
143
performance_report_type: A PerformanceReportType object
133
144
fields: A list of fields to be included in the report
134
145
predicates: A list of filters for the report
135
- redownload_window: The number of days the performance is redownloaded
136
146
"""
137
147
client_customer_ids = api_client .client_customers .keys ()
138
148
@@ -194,7 +204,7 @@ def get_performance_for_single_day(api_client: AdWordsApiClient,
194
204
fields = fields ,
195
205
predicates = predicates ,
196
206
)
197
- report_list .extend (_convert_report_to_list (report ))
207
+ report_list .extend (list (report ))
198
208
return report_list
199
209
200
210
@@ -224,7 +234,8 @@ def download_account_structure(api_client: AdWordsApiClient):
224
234
ad_group_attributes = get_ad_group_attributes (api_client , client_customer_id )
225
235
ad_data = get_ad_data (api_client , client_customer_id )
226
236
227
- for ad_id , ad_data_dict in ad_data .items ():
237
+ for ad_data_dict in ad_data :
238
+ ad_id = ad_data_dict ['Ad ID' ]
228
239
campaign_id = ad_data_dict ['Campaign ID' ]
229
240
ad_group_id = ad_data_dict ['Ad group ID' ]
230
241
currency_code = client_customer ['Currency Code' ]
@@ -272,10 +283,7 @@ def get_campaign_attributes(api_client: AdWordsApiClient, client_customer_id: in
272
283
'PAUSED' ,
273
284
'REMOVED' ]
274
285
})
275
- report_list = _convert_report_to_list (report )
276
-
277
- return {row ['Campaign ID' ]: parse_labels (row ['Labels' ]) for row in
278
- report_list }
286
+ return {row ['Campaign ID' ]: parse_labels (row ['Labels' ]) for row in report }
279
287
280
288
281
289
def get_ad_group_attributes (api_client : AdWordsApiClient , client_customer_id : int ) -> {}:
@@ -300,13 +308,11 @@ def get_ad_group_attributes(api_client: AdWordsApiClient, client_customer_id: in
300
308
'PAUSED' ,
301
309
'REMOVED' ]
302
310
})
303
- report_list = _convert_report_to_list (report )
304
311
305
- return {row ['Ad group ID' ]: parse_labels (row ['Labels' ]) for row in
306
- report_list }
312
+ return {row ['Ad group ID' ]: parse_labels (row ['Labels' ]) for row in report }
307
313
308
314
309
- def get_ad_data (api_client : AdWordsApiClient , client_customer_id : int ) -> {} :
315
+ def get_ad_data (api_client : AdWordsApiClient , client_customer_id : int ) -> [{}] :
310
316
"""Downloads the ad data from the Google AdWords API for a given client_customer_id
311
317
https://developers.google.com/adwords/api/docs/appendix/reports/ad-performance-report
312
318
@@ -318,30 +324,42 @@ def get_ad_data(api_client: AdWordsApiClient, client_customer_id: int) -> {}:
318
324
A dictionary of the form {ad_id: {key: value}}
319
325
"""
320
326
logging .info ('get ad data for account {}' .format (client_customer_id ))
321
- ad_data = {}
322
327
323
328
api_client .SetClientCustomerId (client_customer_id )
329
+
330
+ predicates = [
331
+ {
332
+ 'field' : 'Status' ,
333
+ 'operator' : 'IN' ,
334
+ 'values' : ['ENABLED' ,
335
+ 'PAUSED' ,
336
+ 'DISABLED' ]
337
+ }
338
+ ]
339
+
340
+ if config .ignore_removed_campaigns ():
341
+ predicates .append ({
342
+ 'field' : 'CampaignStatus' ,
343
+ 'operator' : 'NOT_EQUALS' ,
344
+ 'values' : 'REMOVED'
345
+ })
346
+
324
347
report = _download_adwords_report (api_client ,
325
348
report_type = 'AD_PERFORMANCE_REPORT' ,
326
349
fields = ['Id' , 'AdGroupId' , 'AdGroupName' ,
327
350
'CampaignId' , 'CampaignName' ,
328
351
'Labels' , 'Headline' , 'AdType' ,
329
352
'Status' ],
330
- predicates = {'field' : 'Status' ,
331
- 'operator' : 'IN' ,
332
- 'values' : ['ENABLED' ,
333
- 'PAUSED' ,
334
- 'DISABLED' ]
335
- })
336
- report_list = _convert_report_to_list (report )
353
+ predicates = predicates )
337
354
338
- for row in report_list :
355
+ ad_data = []
356
+ for row in report :
339
357
attributes = parse_labels (row ['Labels' ])
340
358
if row ['Ad type' ] is not None :
341
359
attributes = {** attributes , 'Ad type' : row ['Ad type' ]}
342
360
if row ['Ad state' ] is not None :
343
361
attributes = {** attributes , 'Ad state' : row ['Ad state' ]}
344
- ad_data [ row [ 'Ad ID' ]] = {** row , 'attributes' : attributes }
362
+ ad_data . append ( {** row , 'attributes' : attributes })
345
363
346
364
return ad_data
347
365
@@ -350,7 +368,7 @@ def _download_adwords_report(api_client: AdWordsApiClient,
350
368
report_type : str ,
351
369
fields : [str ],
352
370
predicates : {},
353
- current_date : datetime = None ) -> [] :
371
+ current_date : datetime = None ) -> csv . DictReader :
354
372
"""Downloads an Google Ads report from the Google Ads API
355
373
356
374
Args:
@@ -371,7 +389,7 @@ def _download_adwords_report(api_client: AdWordsApiClient,
371
389
'reportName' : '{}_#' .format (report_type ),
372
390
'dateRangeType' : 'CUSTOM_DATE' ,
373
391
'reportType' : report_type ,
374
- 'downloadFormat' : 'TSV ' ,
392
+ 'downloadFormat' : 'CSV ' ,
375
393
'selector' : {
376
394
'fields' : fields ,
377
395
'predicates' : predicates
@@ -393,11 +411,14 @@ def _download_adwords_report(api_client: AdWordsApiClient,
393
411
while True :
394
412
retry_count += 1
395
413
try :
396
- report = report_downloader .DownloadReportAsString (report_filter ,
397
- skip_report_header = False ,
398
- skip_column_header = False ,
399
- skip_report_summary = False )
400
- return report
414
+ report = io .StringIO ()
415
+ report_downloader .DownloadReport (report_filter ,
416
+ output = report ,
417
+ skip_report_header = True ,
418
+ skip_column_header = False ,
419
+ skip_report_summary = True )
420
+ report .seek (0 )
421
+ return csv .DictReader (report )
401
422
except errors .AdWordsReportError as e :
402
423
if retry_count < config .max_retries ():
403
424
@@ -435,7 +456,7 @@ def __init__(self, client_type=None, client_id=None, client_secret=None,
435
456
self .auth_uri = auth_uri
436
457
self .token_uri = token_uri
437
458
438
- def Build (self ):
459
+ def build (self ):
439
460
"""Builds a client config dictionary used in the OAuth 2.0 flow."""
440
461
if all ((self .client_type , self .client_id , self .client_secret ,
441
462
self .auth_uri , self .token_uri )):
@@ -459,7 +480,7 @@ def refresh_oauth_token():
459
480
client_config = ClientConfigBuilder (
460
481
client_type = ClientConfigBuilder .CLIENT_TYPE_WEB , client_id = config .oauth2_client_id (),
461
482
client_secret = config .oauth2_client_secret ())
462
- flow = InstalledAppFlow .from_client_config (client_config .Build (),
483
+ flow = InstalledAppFlow .from_client_config (client_config .build (),
463
484
scopes = ['https://www.googleapis.com/auth/adwords' ])
464
485
flow .redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
465
486
authorize_url , _ = flow .authorization_url (prompt = 'consent' )
@@ -493,26 +514,6 @@ def parse_labels(labels: str) -> {str: str}:
493
514
return labels
494
515
495
516
496
- def _convert_report_to_list (report : str ) -> [{}]:
497
- """Converts a Google AdWords report to a list of dictionaries
498
-
499
- Args:
500
- report: A Google AdWords report as a string
501
-
502
- Returns:
503
- A list containing dictionaries with the data from the report
504
-
505
- """
506
- # Discard the first line as it only contains meta information.
507
- # The last two lines only display summaries
508
- rows = list (csv .reader (report .split ('\n ' )[1 :- 2 ], dialect = 'excel-tab' ))
509
-
510
- # The second line holds the column names
511
- keys = rows [0 ]
512
-
513
- return [dict (zip (keys , row )) for row in rows [1 :]]
514
-
515
-
516
517
def ensure_data_directory (relative_path : Path = None ) -> Path :
517
518
"""Checks if a directory in the data dir path exists. Creates it if necessary
518
519
0 commit comments