Данные из Google Ads по API в BigQuery (сервисный аккаунт)

26.11.2025

Эта статья – продолжение моей статьи по выгрузке данных из Google Ads по API. Это второй способ подключения, который полностью решит проблему с истекающим refresh token.


Проблема: refresh token, необходимый для подключения к Google Ads API имеет срок жизни – 7 дней. Если приложение не вывести из стадии тестирования в публикацию и не подтвердить – токен будет истекать и его необходимо постоянно обновлять для работы. Что не подходит для автоматизации.А процесс публикации и подтверждения приложения может быть долгим.

В этом способе я покажу: как заменить Oauth-авторизацию на сервисный аккаунт и сделать подключение к API постоянным. + Бонусом автоматизация запуска скриптов с помощью Github Actions!

Так как эта статья — продолжение, мы пропускаем базовую настройку. Прежде чем переходить к созданию сервисного аккаунта, убедитесь, что у вас уже выполнены шаги из первой части:

  1. Проект в Google Cloud Platform (GCP) — создан.
  2. Google Ads API — включен в библиотеке API вашего проекта.
  3. Developer Token (Токен разработчика) — получен в управляющем аккаунте (MCC).
  4. 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

Шаг 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.

Алена Шваб
Алена Шваб

Основатель digital-агентства sas-company.by, digital-маркетолог и автор телеграм-канала по контекстной рекламе https://t.me/sas_company_cases тг для связи @sas_digital

Добавить комментарий