I'm trying to access Google Ads campaing reports from Python folowing this tutorial.
I've requested my Developer Token with Basic Access. I think it has enough privileges to execute the script. I Can see my token active when I go to "API Center" in google ads.
I've created a project in google cloud and an Oauth Token.
In google Cloud:
- Created a new project
- Activated the Google Ads API.
- When I go to Manage-> Credentials I see that the Oauth token is compatible with that API.
- I have successfully created a refresh token.
I'm using this script as proof of concept:
import os import json import sys from google.ads.google_ads.errors import GoogleAdsException # Put an account id to download stats from. Note: not MCC, no dash lines CUSTOMER_ID = "xxxxxxxxxx" def get_account_id(account_id, check_only=False): """ Converts int to str, checks if str has dashes. Returns 10 chars str or raises error :check_only - if True, returns None instead of Error """ if isinstance(account_id, int) and len(str(account_id)) == 10: return str(account_id) if isinstance(account_id, str) and len(account_id.replace("-", "")) == 10: return account_id.replace("-", "") if check_only: return None raise ValueError(f"Couldn't recognize account id from {account_id}") def micros_to_currency(micros): return micros / 1000000.0 def main(client, customer_id): ga_service = client.get_service("GoogleAdsService")# , version="v5") query = """ SELECT campaign.id, campaign.name, ad_group.id, ad_group.name, ad_group_criterion.criterion_id, ad_group_criterion.keyword.text, ad_group_criterion.keyword.match_type, metrics.impressions, metrics.clicks, metrics.cost_micros FROM keyword_view WHERE segments.date DURING LAST_7_DAYS AND campaign.advertising_channel_type = 'SEARCH' AND ad_group.status = 'ENABLED' AND ad_group_criterion.status IN ('ENABLED', 'PAUSED') ORDER BY metrics.impressions DESC LIMIT 50""" # Issues a search request using streaming. response = ga_service.search_stream(customer_id, query) #THIS LINE GENERATES THE ERROR keyword_match_type_enum = client.get_type( "KeywordMatchTypeEnum" ).KeywordMatchType try: for batch in response: for row in batch.results: campaign = row.campaign ad_group = row.ad_group criterion = row.ad_group_criterion metrics = row.metrics keyword_match_type = keyword_match_type_enum.Name( criterion.keyword.match_type ) print( f'Keyword text "{criterion.keyword.text}" with ' f'match type "{keyword_match_type}" ' f"and ID {criterion.criterion_id} in " f'ad group "{ad_group.name}" ' f'with ID "{ad_group.id}" ' f'in campaign "{campaign.name}" ' f"with ID {campaign.id} " f"had {metrics.impressions} impression(s), " f"{metrics.clicks} click(s), and " f"{metrics.cost_micros} cost (in micros) during " "the last 7 days." ) except GoogleAdsException as ex: print( f'Request with ID "{ex.request_id}" failed with status ' f'"{ex.error.code().name}" and includes the following errors:' ) for error in ex.failure.errors: print(f'\tError with message "{error.message}".') if error.location: for field_path_element in error.location.field_path_elements: print(f"\t\tOn field: {field_path_element.field_name}") sys.exit(1) if __name__ == "__main__": # credentials dictonary creds = {"google_ads": "googleads.yaml"} if not os.path.isfile(creds["google_ads"]): raise FileExistsError("File googleads.yaml doesn't exists. ") resources = {"config": "config.json"} # This logging allows to see additional information on debugging import logging logging.basicConfig(level=logging.INFO, format='[%(asctime)s - %(levelname)s] %(message).5000s') logging.getLogger('google.ads.google_ads.client').setLevel(logging.DEBUG) # Initialize the google_ads client from google.ads.google_ads.client import GoogleAdsClient gads_client = GoogleAdsClient.load_from_storage(creds["google_ads"]) id_to_load = get_account_id(CUSTOMER_ID) main(gads_client, id_to_load)
- I've changed CUSTOMER_ID to the account number that appears on the upper left corner
- I've created a googleads.yaml and I've loaded the aforementioned information.
When I execute the script I get this error:
Traceback (most recent call last): File "download_keywords_from_account.py", line 138, in <module> main(gads_client, id_to_load) File "download_keywords_from_account.py", line 70, in main response = ga_service.search_stream(customer_id, query) File "google/ads/google_ads/v6/services/google_ads_service_client.py", line 366, in search_stream return self._inner_api_calls['search_stream'](request, retry=retry, timeout=timeout, metadata=metadata) File google/api_core/gapic_v1/method.py", line 145, in __call__ return wrapped_func(*args, **kwargs) File "google/api_core/retry.py", line 281, in retry_wrapped_func return retry_target( File "google/api_core/retry.py", line 184, in retry_target return target() File "google/api_core/timeout.py", line 214, in func_with_timeout return func(*args, **kwargs) File "google/api_core/grpc_helpers.py", line 152, in error_remapped_callable six.raise_from(exceptions.from_grpc_error(exc), exc) File "<string>", line 3, in raise_from google.api_core.exceptions.PermissionDenied: 403 Request had insufficient authentication scopes
The googleads.yaml file looks like this:
############################################################################# # Required Fields # ############################################################################# developer_token: {developer token as seen in google ads -> tools -> api center} ############################################################################# # Optional Fields # ############################################################################# login_customer_id: {Id from the top left corner in google ads, only numbers} # user_agent: INSERT_USER_AGENT_HERE # partial_failure: True validate_only: False ############################################################################# # OAuth2 Configuration # # Below you may provide credentials for either the installed application or # # service account flows. Remove or comment the lines for the flow you're # # not using. # ############################################################################# # The following values configure the client for the installed application # flow. client_id: {Oauth client id taken from gcloud -> api -> credentials} ends with apps.googleusercontent.com client_secret: {got it while generating the token} refresh_token: 1//0hr.... made with generate_refresh_token.py # The following values configure the client for the service account flow. path_to_private_key_file: ads.json # delegated_account: INSERT_DOMAIN_WIDE_DELEGATION_ACCOUNT ############################################################################# # ReportDownloader Headers # # Below you may specify boolean values for optional headers that will be # # applied to all requests made by the ReportDownloader utility by default. # ############################################################################# # report_downloader_headers: # skip_report_header: False # skip_column_header: False # skip_report_summary: False # use_raw_enum_values: False
NOTES:
The file ads.json contains the private key downloaded from the credentials page in gcloud.
I've seen some posts on this issue but none of them are Python + GoogleADs and I couldn't find a solution there either.
I have also tried other Python + GoogleAds examples getting the same error. This makes me think that I must be configuring something wrong in gcloud / google ads. But I don't understand what.
Please help me make the query I'm really stuck.
Thanks a lot!