Эта статья – продолжение моей статьи по выгрузке данных из Google Ads по API. Это второй способ подключения, который полностью решит проблему с истекающим refresh token.
Проблема: refresh token, необходимый для подключения к Google Ads API имеет срок жизни – 7 дней. Если приложение не вывести из стадии тестирования в публикацию и не подтвердить – токен будет истекать и его необходимо постоянно обновлять для работы. Что не подходит для автоматизации.А процесс публикации и подтверждения приложения может быть долгим.
В этом способе я покажу: как заменить Oauth-авторизацию на сервисный аккаунт и сделать подключение к API постоянным. + Бонусом автоматизация запуска скриптов с помощью Github Actions!
Так как эта статья — продолжение, мы пропускаем базовую настройку. Прежде чем переходить к созданию сервисного аккаунта, убедитесь, что у вас уже выполнены шаги из первой части:
- Проект в Google Cloud Platform (GCP) — создан.
- Google Ads API — включен в библиотеке API вашего проекта.
- Developer Token (Токен разработчика) — получен в управляющем аккаунте (MCC).
- Python — установлен и настроено виртуальное окружение.
Если всё это есть — переходим сразу к Шагу 3.
В оригинальной инструкции третьим шагом шла настройка OAuth 2.0. Но поскольку наша цель — стабильное подключение без вечно истекающих токенов, мы заменяем этот этап на работу с сервисным аккаунтом. Остальная логика остается прежней, меняется только способ авторизации.
Шаг 3 – создание сервисного аккаунта, получение json-ключа и добавление сервисного аккаунта в рекламные аккаунты.
Для создания сервисного аккаунта необходимо перейти в Google Cloud Console – APIs & Services – Credentials.
Далее нажать кнопку Create credentials – Service account
Задаем имя сервисного аккаунта
Затем выбираем роли. В целом можно дать роль Owner и тогда у вашего аккаунта будут все права, но для соблюдения безопасности можно дать 2 роли: Bigquery Job User и Bigquery Data Editor.
Следующий шаг пропускаем и нажимаем Done.
Далее проваливаемся во внутрь нашей созданной сервисной почты, переходим на вкладку Keys – Add key – Create new key.
После нажатия этой кнопки генерируется ключ в формате json, который скачается на ваш ПК. Сохраните его, в последующем он нам понадобится.
Шаг 4 – Добавляем сервисный аккаунт в рекламные кабинеты.
Этот шаг необходим для связи рекламных кабинетов и Google Bigquery. В зависимости от ваших задач – вы можете добавить скрипт и на управляющий аккаунт MCC, и на отдельные рекламные кабинеты, которые необходимо выгружать.
Доступ открывается точно также как и для обычного пользователя, типа доступа достаточно чтения.
Шаг 5 – создание итогового YAML файла
Создаем файл формата .yaml, в него вставляем следующие данные:
# Required Fields # developer_token: 'YOUR DEVELOPER TOKEN' login_customer_id: 'YOUR CUSTOMER ID' json_key_file_path: use_proto_plus: True
- developer_token – токен разработчика с аккаунта Google (стандартный доступ). Подробнее о его получении, можно прочитать в предыдущей статье. https://lib.osipenkov.ru/google-ads-api-to-google-bigquery/#1_Настройка_аккаунта_Google_Ads_и_получения_токена_разработчика
- login_customer_id – ID рекламного аккаунта для выгрузки (клиентский или MCC)
- json_path – абсолютный путь к вашему ключу json в проекте.
- use_proto_plus: True – параметр, который остается без изменений
Шаг 6 – Загрузка JSON ключа в проект
В корневой папке проекта в PyCharm создайте папки src/creds – сюда загрузите файл с json ключом, а затем скопируйте абсолютный путь и вставьте YAML.
Шаги с установкой PyCharm, Python и библиотек я повторять не буду. Они актуальны по первой статье – можете вернуться и повторить начиная с шага 5 по шаг 7.
Начнем сразу со скриптов.
Шаг 7 – Python скрипты для выгрузки данных
Я приведу пример скриптов, некоторые из них претерпели изменения в плане подключения.
Здесь приведен пример скрипта для выгрузки данных по клиентскому аккаунту. Ссылка на GitHub – https://github.com/kontekstsas/google-ads-scripts/blob/main/src/ga_api_upd/ga_client_download_data.py
from datetime import datetime, timedelta
import argparse
import sys
import pandas as pd
from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException
from google.cloud import bigquery
def load_pmax_data(client, customer_id, start_date_str, end_date_str):
"""Загружает данные по кампаниям Performance Max."""
ga_service = client.get_service("GoogleAdsService")
print("\n1.1. Запрос данных по кампаниям Performance Max из Google Ads API...")
query_general_pmax = f"""
SELECT campaign.name, segments.date, metrics.clicks, metrics.cost_micros, metrics.impressions
FROM campaign
WHERE segments.date BETWEEN '{start_date_str}' AND '{end_date_str}'
AND campaign.advertising_channel_type = 'PERFORMANCE_MAX'
"""
query_conversions_pmax = f"""
SELECT campaign.name, segments.date, segments.conversion_action_name, metrics.conversions
FROM campaign
WHERE segments.date BETWEEN '{start_date_str}' AND '{end_date_str}'
AND campaign.advertising_channel_type = 'PERFORMANCE_MAX'
"""
try:
response_general = ga_service.search_stream(customer_id=customer_id, query=query_general_pmax)
rows_general = [{
"campaign_name": row.campaign.name,
"ad_group_name": "Performance Max",
"date": row.segments.date,
"clicks": row.metrics.clicks,
"cost": row.metrics.cost_micros / 1000000,
"impressions": row.metrics.impressions
} for batch in response_general for row in batch.results]
pmax_df = pd.DataFrame(rows_general)
if not pmax_df.empty:
print(f"Получено {len(pmax_df)} строк с данными по кликам и стоимости для PMax.")
response_conversions = ga_service.search_stream(customer_id=customer_id, query=query_conversions_pmax)
rows_conversions = [{
"campaign_name": row.campaign.name,
"ad_group_name": "Performance Max",
"date": row.segments.date,
"conversion_name": row.segments.conversion_action_name,
"conversions": row.metrics.conversions
} for batch in response_conversions for row in batch.results]
pmax_conversions_df = pd.DataFrame(rows_conversions)
if not pmax_conversions_df.empty:
print(f"Получено {len(pmax_conversions_df)} строк с данными по конверсиям для PMax.")
except GoogleAdsException as ex:
print(f'Ошибка запроса PMax с ID "{ex.request_id}": {ex.error.code().name}')
return pd.DataFrame()
if pmax_df.empty and pmax_conversions_df.empty:
print("Данные по Performance Max не найдены.")
return pd.DataFrame()
if pmax_df.empty:
final_pmax_df = pmax_conversions_df
elif pmax_conversions_df.empty:
final_pmax_df = pmax_df
else:
final_pmax_df = pd.merge(
pmax_df, pmax_conversions_df,
on=["campaign_name", "ad_group_name", "date"],
how="outer"
)
final_pmax_df[['clicks', 'cost', 'conversions', 'impressions']] = \
final_pmax_df[['clicks', 'cost', 'conversions', 'impressions']].fillna(0)
return final_pmax_df
def load_all_campaign_data(client, customer_id, project_id, table_id, start_date_str, end_date_str):
"""Загружает данные по всем типам кампаний и группам объявлений."""
ga_service = client.get_service("GoogleAdsService")
print("\n1. Запрос данных по стандартным кампаниям и группам из Google Ads API...")
query_general = f"""
SELECT campaign.name, ad_group.name, segments.date, metrics.clicks, metrics.cost_micros, metrics.impressions
FROM ad_group
WHERE segments.date BETWEEN '{start_date_str}' AND '{end_date_str}'
AND campaign.advertising_channel_type != 'PERFORMANCE_MAX'
"""
query_conversions = f"""
SELECT campaign.name, ad_group.name, segments.date, segments.conversion_action_name, metrics.conversions
FROM ad_group
WHERE segments.date BETWEEN '{start_date_str}' AND '{end_date_str}'
AND campaign.advertising_channel_type != 'PERFORMANCE_MAX'
"""
try:
response_general = ga_service.search_stream(customer_id=customer_id, query=query_general)
rows_general = [{
"campaign_name": row.campaign.name,
"ad_group_name": row.ad_group.name,
"date": row.segments.date,
"clicks": row.metrics.clicks,
"impressions": row.metrics.impressions,
"cost": row.metrics.cost_micros / 1000000
} for batch in response_general for row in batch.results]
campaign_df = pd.DataFrame(rows_general)
print(f"Получено {len(campaign_df)} строк по кликам и стоимости (стандартные кампании).")
response_conversions = ga_service.search_stream(customer_id=customer_id, query=query_conversions)
rows_conversions = [{
"campaign_name": row.campaign.name,
"ad_group_name": row.ad_group.name,
"date": row.segments.date,
"conversion_name": row.segments.conversion_action_name,
"conversions": row.metrics.conversions
} for batch in response_conversions for row in batch.results]
conversions_df = pd.DataFrame(rows_conversions)
print(f"Получено {len(conversions_df)} строк по конверсиям (стандартные кампании).")
except GoogleAdsException as ex:
print(f'Ошибка запроса с ID "{ex.request_id}": {ex.error.code().name}')
sys.exit(1)
print("2. Объединение данных по стандартным кампаниям...")
if campaign_df.empty and conversions_df.empty:
standard_final_df = pd.DataFrame()
elif campaign_df.empty:
standard_final_df = conversions_df
elif conversions_df.empty:
standard_final_df = campaign_df
else:
standard_final_df = pd.merge(
campaign_df, conversions_df,
on=["campaign_name", "ad_group_name", "date"],
how="outer"
)
pmax_final_df = load_pmax_data(client, customer_id, start_date_str, end_date_str)
print("3. Объединение данных стандартных и Performance Max кампаний...")
final_df = pd.concat([standard_final_df, pmax_final_df], ignore_index=True)
final_df[['clicks', 'cost', 'conversions', 'impressions']] = \
final_df[['clicks', 'cost', 'conversions', 'impressions']].fillna(0)
if final_df.empty:
print("Нет данных для основной таблицы.")
return
print(f"4. Загрузка {len(final_df)} строк в таблицу {table_id}...")
try:
bq_client = bigquery.Client(project=project_id)
job_config = bigquery.LoadJobConfig(write_disposition="WRITE_TRUNCATE")
job = bq_client.load_table_from_dataframe(final_df, table_id, job_config=job_config)
job.result()
print("🎉 Данные успешно загружены в BigQuery!")
except Exception as e:
print(f"Ошибка при загрузке в BigQuery: {e}")
sys.exit(1)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Загружает данные из Google Ads в Google BigQuery.")
parser.add_argument("-c", "--customer_id", type=str, required=True)
parser.add_argument("-p", "--project_id", type=str, required=True)
parser.add_argument("-t", "--table_id", type=str, required=True)
parser.add_argument("--config_file", type=str, required=True)
args = parser.parse_args()
try:
googleads_client = GoogleAdsClient.load_from_storage(args.config_file, version="v21")
print("Клиент Google Ads успешно инициализирован.")
except Exception as e:
print(f"Не удалось загрузить конфигурацию: {e}")
sys.exit(1)
today = datetime.now()
end_date = today - timedelta(days=1)
start_date = today - timedelta(days=90)
start_date_str = start_date.strftime('%Y-%m-%d')
end_date_str = end_date.strftime('%Y-%m-%d')
print(f"Выгружаем данные за {start_date_str} — {end_date_str}")
load_all_campaign_data(
googleads_client,
args.customer_id,
args.project_id,
args.table_id,
start_date_str,
end_date_str
)
Пример скрипта для mcc аккаунта – https://github.com/kontekstsas/google-ads-scripts/blob/main/src/ga_api_upd/ga_mcc_download_data.py
Шаг 8 – Запуск скрипта
Запустить скрипт можно с помощью команды в терминале PyCharm, в моей структуре данных это так:
Для клиентского:
python src/test_scripts/test_ga_download_data.py --customer_id 111111111 --project_id your_project_id --table_id your_table_id --config_file "src/creds/googleads.yaml"
Обязательные аргументы:
- customer_id (id клиента google ads)
- project_id (id проекта bigquery)
- table_id (id таблицы bigquery)
- config_file (путь к конфиг файлу)
Для MCC:
python src/test_scripts/test_mcc_download_data.py --mcc_id 111111111 --project_id your_project_id --dataset_id your_dataset_id --config_file "src/creds/googleads.yaml" --key_file "src/creds/key.json"
Обязательные аргументы:
- customer_id (id клиента google ads)
- project_id (id проекта bigquery)
- dataset_id (id датасета bigquery)
- config_file(путь к конфиг файлу)
- key_file (путь к ключу json)
Бонусный раздел – Автоматизация работы скриптов с помощью GitHub Actions
Запуская скрипты локально на ПК вы не можете автоматизировать процесс, чтобы выгружать данные на регулярной основе. Для автоматизации обычно покупается сервер, на котором вы уже размещаете скрипты и устанавливаете правила выгрузки (cron), что может быть сложно, дорого и затратно. Я же предлагаю бесплатное решение, с которым вы можете справиться самостоятельно.
Здесь я не буду описывать весь процесс работы с GitHub (как связать проект, как пушить изменения и пр.) Я покажу лишь небольшой принцип работы.
Шаг 1 – Загрузка всех скриптов на GitHub
Думаю с этим шагом вы справитесь, но сказать о нем немаловажно) Работая с PyCharm настоятельно рекомендую включить версионирование и связать проект с GitHub. Весь этот шаг можно сделать как и с помощью команд git, или в ручную.
Шаг 2 – Создание секретов
Здесь остановимся подробнее. Секреты – это все ваши ключи (json, yaml файл и прочее), они хранятся в зашифрованном виде и их нельзя просмотреть (только переписать заново). Для чего? Для безопасности ваших данных – никогда не храните токены доступа в открытом виде в скриптах (даже в закрытом репозитории).
Для создания необходимо перейти в настройки репозитория – Actions – Add New Secret.
Мы создадим 2 секрета, куда добавим след. данные:
- GCP_SA_KEY – ключ json полностью,
- GOOGLE_ADS_YAML – ранее созданный yaml файл.
Теперь наши данные могут безопасно храниться, а мы к ним подключаться):
и:
Шаг 3 – создание GitHub Workflow
Workflow (поток работ) — это последовательность шагов, действий или задач, необходимых для достижения определенной цели в рамках рабочего процесса. В нашем случае – это конкретный файл инструкция, которая будет выполнять задачу по запуску скрипта.
Для этого я создала отдельную директорию .github/workfows, где создим отдельный файл yml (он и будет запускать скрипты)
Пример такого файла:
name: Google Ads Data Load
on:
schedule:
- cron: '0 4 * * *'
workflow_dispatch:
jobs:
run_google_etl:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install google-ads google-cloud-bigquery pandas pandas-gbq pyyaml requests google-auth
- name: Create Credentials Files
shell: bash
env:
GCP_KEY: ${{ secrets.GCP_SA_KEY }}
ADS_YAML: ${{ secrets.GOOGLE_ADS_YAML }}
run: |
echo "$GCP_KEY" > google-ads-key.json
echo "$ADS_YAML" > googleads.yaml
- name: Run Google Ads Loader
run: |
python src/test_scripts/test_mcc_download_data.py \
--mcc_id 111111111 \
--project_id your_project_id \
--table_id your_table_id \
--config_file "googleads.yaml" \
--key_file "google-ads-key.json"
Здесь можно установить расписание выгрузки, поставить параметры на установки библиотек и python, настроить параметры скрипта (путь к нему, к какому аккаунту подключаться, в какую таблицу в биквери грузить данные, путь к секретам)
Я также оставлю ссылку на GitHub – https://github.com/kontekstsas/google-ads-scripts/blob/main/.github/workflow/test_download_data.yml
После настройки параметров выгрузки вы можете протестировать работу скриптов.
Для этого перейдем на Actions из репозитория:
Слева в списке у вас появится название процесса:
Переходим в него и запускаем workflow вручную:
После вы увидите процесс, в который можно провалиться и посмотреть по шагам, что делает ваш скрипт. При необходимости можно раскрывать все шаги и смотреть, где есть проблемы. Таким образом проводить отладку.
На этом все. Надеюсь моя статья вам понравилась и у вас получится автоматизировать некоторые вещи, все приведенные коды рабочие. Но не являются исчерпывающим вариантом – вы можете оптимизировать и доработать их под ваши процессы. Ссылка на полный репозиторий с кодами из этой статьи здесь – https://github.com/kontekstsas/google-ads-scripts.











