SteerAds
Google AdsTutorielOptimisation

Automatizace Google Ads API v Pythonu: pro začátečníky

První nastavení Google Ads API v Pythonu: OAuth2, GAQL dotazy, mutace, zpracování chyb a retry logika. Přesně ty snippety, které zkopírujete, s veřejným GitHub repozitářem ke forknutí — spustíte za méně než 30 minut.

Matt
MattTracking & Data Lead
···11 min čtení

Google Ads API podporuje 10 000 operací denně na klientský účet v Basic Access a timeout max 1 hodinu na dotaz, oproti 30 minutám CPU a max 50 skriptům pro Google Ads Scripts (oficiální dokumentace kvót API). U sledovaných účtů ve veřejných benchmarcích Google Ads datové týmy, které přecházejí ze Scriptů na API v Pythonu, ušetří 5 až 12 hodin týdně na reportovacích a auditních pipeline a odemknou případy použití nemožné na Scriptech (BigQuery sync, batch mutace 5 000+ entit, obousměrná integrace s CRM).

Tento průvodce je krok za krokem Python setup pro začátečníky. Přesně ten příkaz ke zkopírování, OAuth snippet funkční napoprvé, první GAQL dotaz a veřejný GitHub repozitář k forknutí pro rychlý start. Žádná marketingová teorie, žádné „objevte nekonečné možnosti API" — jen fungující kód. Předpoklady: Python 3.9+, účet Google Ads, 30 minut. Pokud jste již s Google Ads Scripts zběhlí, přečtěte si nejprve náš průvodce 10 Google Ads skriptů ready-to-copy, který pokládá základy automatizace, jež API rozšíří. Náš kalkulátor plýtvání rozpočtem odhaduje EUR spálená/měsíc broad match bez vyloučených slov nebo nadměrným bounce na LP.

Proč Google Ads API, když existují Scripty?

Google Ads Scripts jsou mocné, ale omezené: max 30 minut CPU na spuštění, max 50 aktivních skriptů na účet, JavaScript ES5 (žádné npm balíčky), žádný přístup k externím vědeckým knihovnám (numpy, pandas, scikit-learn). Google Ads API je stupeň výše: Python, Java, Go, .NET nebo Ruby dle výběru, integrace s libovolným datovým stackem (BigQuery, Snowflake, Airflow, dbt), batch operace, async, vertikální i horizontální škálování bez omezení ze strany Googlu.

Kritérium přepnutí je jednoduché. Zůstaňte na Scriptech, pokud: monitorujete 1 účet nebo omezený MCC, vaše automatizace zvládnete za 30 minut runtime, nepotřebujete externí Python knihovny, váš tým nechce udržovat infrastrukturu. Přejděte na API, pokud: spravujete 10+ účtů paralelně, synchronizujete s datovým skladem (BigQuery, Snowflake), integrujete CRM (HubSpot, Salesforce) obousměrně nebo vystavujete Google Ads operace v interním produktu (dashboard, automation tool).

Operační sweet spot: používejte Scripty pro monitoring jednoho účtu a jednoduché automatizace (upozornění na rozpočet, automatická vyloučená slova), přejděte na API pro datové pipeline, multi-account a integraci CRM. U sledovaných účtů ve veřejných benchmarcích Google Ads přibližně 30 až 40 % zralých struktur (>500 tis. EUR/rok výdajů) kombinuje obojí: Scripty pro každodenní taktiku, API pro týdenní strategii a datové synchronizace.

Častá otázka v školení: „mám už 4–5 Scriptů, musím vše migrovat?" Pragmatická odpověď je ne. Migrace na API se vyplatí pouze tehdy, když získáváte kapacitu (multi-account, data warehouse, vědecké knihovny) nebo ztrácíte čas na omezeních Scriptů (timeout 30 min, žádné pandas). Pro mid-size účet se správně fungujícími Scripty je nejméně rizikovou strategií ponechat stávající kód a přidat API pouze pro nové případy použití. Oba pipeline koexistují bez konfliktu: Scripty běží na straně Googlu, vaše API běží na vaší infrastruktuře, neovlivňují se.

Druhý kompromis, na který se často zapomíná, jsou celkové náklady na vlastnictví. Scripty jsou z pohledu infrastruktury zdarma (hostuje Google), ale vyžadují JavaScript v omezeném sandboxu — tedy hodiny vývoje pro obcházení omezení. API vyžaduje Python, infrastrukturu (Cloud Run, Lambda, EC2 nebo jednoduchý cron na VPS), monitoring, správu tajných klíčů a udržování závislostí. Za 12 měsíců stojí Python API setup pro 3–5 skriptů typicky 300 až 1 200 EUR za cloudovou infrastrukturu plus 20 až 60 hodin vývoje/údržby. Nad 10 skriptů nebo 20 spravovaných účtů se ROI jasně překlopí ve prospěch API.

Nastavení prostředí Python: OAuth2, credentials, knihovna

Nastavení obnáší 6 kroků: vytvořit GCP projekt, vygenerovat OAuth2 credentials, získat developer_token Google Ads, nainstalovat knihovnu, vygenerovat refresh_token, otestovat GAQL dotazem. Počítejte celkem 30 minut. Zde je přesný postup.

Krok 1 — GCP projekt a aktivace API

Na console.cloud.google.com vytvořte nový projekt (libovolný název, např. google-ads-api-prod). V APIs and Services > Library vyhledejte „Google Ads API" a klikněte Enable. API je zdarma, ale vyžaduje explicitní aktivaci na projekt GCP.

Krok 2 — OAuth2 credentials (typ Desktop app)

V APIs and Services > Credentials klikněte Create Credentials > OAuth client ID. Typ: Desktop app. Zadejte výstižný název (např. google-ads-api-cli). Stáhněte JSON, uložte si client_id a client_secret. Obě hodnoty budou součástí google-ads.yaml.

Krok 3 — Developer token v Google Ads

V Google Ads, Tools and Settings > API Center. Pokud ještě nemáte, požádejte o developer_token. Počáteční token je v Test módu (15 000 ops/den, pouze testovací účty). Pro přechod na Basic Access (10 000 ops/den, produkční účty) podejte žádost s popisem případu použití — Google odpoví za 1 až 5 pracovních dní. Pro Standard Access (neomezeno) počítejte 2 až 4 týdny revize.

Krok 4 — Instalace knihovny a vygenerování refresh_tokenu

Instalace oficiální knihovny:

# Vyžadován Python 3.9+
pip install google-ads
# Ověřit verzi (24.0.0+ odpovídá API v17)
pip show google-ads

Pro vygenerování refresh_tokenu je nejjednodušší použít oficiální auth skript poskytnutý Googlem:

# Naklonovat oficiální příklady
git clone https://github.com/googleads/google-ads-python.git
cd google-ads-python/examples/authentication

# Spustit generovací skript
python generate_user_credentials.py \
  --client_id YOUR_CLIENT_ID \
  --client_secret YOUR_CLIENT_SECRET

Skript otevře OAuth stránku ve vašem prohlížeči. Potvrďte přístup ke Google Ads účtu. Skript vypíše refresh_token ve formátu 1//0g...XXXXX. Zkopírujte ho okamžitě, zobrazí se pouze jednou.

Krok 5 — Konfigurace google-ads.yaml

Vytvořte soubor google-ads.yaml v kořenovém adresáři projektu:

# google-ads.yaml — IHNED PŘIDAT DO .gitignore
developer_token: "YOUR_DEVELOPER_TOKEN_22_CHARS"
client_id: "YOUR_CLIENT_ID.apps.googleusercontent.com"
client_secret: "GOCSPX-YOUR_CLIENT_SECRET"
refresh_token: "1//0gYOUR_REFRESH_TOKEN"
login_customer_id: "1234567890"  # Nadřazený MCC bez pomlček
use_proto_plus: true

login_customer_id je ID vašeho nadřazeného MCC bez pomlček (např. 123-456-7890 se stane 1234567890). Pod tímto účtem API autentizuje každý dotaz. Pokud dotazujete klientský účet tohoto MCC, zadáte customer_id klientského účtu přímo v dotazu.

Kritické zabezpečení credentials :

Přidejte google-ads.yaml do .gitignore PŘED prvním commitem. Refresh_token uniklý na veřejný GitHub je boty detekován do 30 minut a může být použit k účtování výdajů na váš účet. V produkci načítejte YAML ze secret manageru (AWS Secrets Manager, GCP Secret Manager, Vault) — nikdy jako plain text na serveru.

Krok 6 — Otestovat nastavení jednoduchým dotazem

# test_setup.py
from google.ads.googleads.client import GoogleAdsClient

CUSTOMER_ID = "1112223333"  # ID klientského účtu (ne MCC)

def test_connection():
    client = GoogleAdsClient.load_from_storage("google-ads.yaml")
    ga_service = client.get_service("GoogleAdsService")

    query = """
        SELECT campaign.id, campaign.name, campaign.status
        FROM campaign
        LIMIT 5
    """

    response = ga_service.search(customer_id=CUSTOMER_ID, query=query)
    for row in response:
        print(f"{row.campaign.id} | {row.campaign.name} | {row.campaign.status.name}")

if __name__ == "__main__":
    test_connection()

Spusťte python test_setup.py. Pokud vidíte 5 názvů kampaní, nastavení je správné. Při chybě INVALID_CUSTOMER_ID ověřte formát (10 číslic bez pomlček). Při chybě NOT_ADS_USER je refresh_token vázán na Google účet bez přístupu k zadanému customer_id.

První GAQL dotaz: výkonnost kampaní

GAQL (Google Ads Query Language) je dotazovací jazyk Google Ads API. Syntaxe blízká SQL, ale se specifiky: žádné explicitní JOIN (zdroje jsou zanořeny), hierarchicky strukturovaná pole (campaign.id, metrics.clicks, segments.date) a DURING pro datové rozsahy místo WHERE date BETWEEN.

Zde je kompletní skript, který stahuje výkonnost ENABLED kampaní za posledních 30 dní s impresemi, kliky, náklady, konverzemi, CTR, CPC, CPA: Náš kalkulátor CPA ve 2 vstupech vrátí hodnotu + medián Francie pro váš vertikál.

# pull_campaign_performance.py
from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException

CUSTOMER_ID = "1112223333"

def pull_performance(client, customer_id):
    ga_service = client.get_service("GoogleAdsService")

    query = """
        SELECT
            campaign.id,
            campaign.name,
            campaign.status,
            metrics.impressions,
            metrics.clicks,
            metrics.cost_micros,
            metrics.conversions,
            metrics.conversions_value,
            metrics.ctr,
            metrics.average_cpc
        FROM campaign
        WHERE campaign.status = 'ENABLED'
          AND segments.date DURING LAST_30_DAYS
        ORDER BY metrics.cost_micros DESC
        LIMIT 50
    """

    try:
        response = ga_service.search(customer_id=customer_id, query=query)

        results = []
        for row in response:
            cost_eur = row.metrics.cost_micros / 1_000_000  # micros -> EUR
            cpa = cost_eur / row.metrics.conversions if row.metrics.conversions > 0 else None

            results.append({
                "id": row.campaign.id,
                "name": row.campaign.name,
                "impressions": row.metrics.impressions,
                "clicks": row.metrics.clicks,
                "cost_eur": round(cost_eur, 2),
                "conversions": row.metrics.conversions,
                "ctr_pct": round(row.metrics.ctr * 100, 2),
                "cpc_eur": round(row.metrics.average_cpc / 1_000_000, 2),
                "cpa_eur": round(cpa, 2) if cpa else None,
            })

        return results

    except GoogleAdsException as ex:
        print(f"Request failed: {ex.error.code().name}")
        for error in ex.failure.errors:
            print(f"  - {error.message}")
        raise

if __name__ == "__main__":
    client = GoogleAdsClient.load_from_storage("google-ads.yaml")
    perf = pull_performance(client, CUSTOMER_ID)

    for c in perf:
        print(f"{c['name'][:40]:40s} | "
              f"{c['impressions']:>8,} impr | "
              f"{c['clicks']:>5,} kliky | "
              f"{c['cost_eur']:>7,.2f} EUR | "
              f"{c['conversions']:>5.1f} konv | "
              f"CPA: {c['cpa_eur']}")

Tři kritické body tohoto dotazu. Zaprvé: náklady jsou v mikro (1 EUR = 1 000 000 mikro). Vždy dělit 1_000_000 pro získání EUR. Zadruhé: metrics.ctr je float mezi 0 a 1, vynásobte 100 pro procento. Zatřetí: klauzule DURING LAST_30_DAYS je ekvivalentem WHERE segments.date BETWEEN '2026-03-28' AND '2026-04-26', ale mnohem čitelnější. Seznam konstant DURING: TODAY, YESTERDAY, LAST_7_DAYS, LAST_30_DAYS, LAST_90_DAYS, THIS_MONTH, LAST_MONTH atd. (úplný seznam).

Tři další pasti, na které začátečníci v GAQL naráží. *Past 1: žádné SELECT . API vyžaduje explicitní deklaraci každého pole. Vypsání 25 polí ručně je zdlouhavé, ale záměrné — Google chce omezit šířku pásma a donutit inzerenty vědět, co konzumují. Udržování znovupoužitelné Python konstanty CAMPAIGN_FIELDS = [...] ušetří přepisování seznamu v každém skriptu. Past 2: segments.date vždy způsobuje row-fanning. Dotaz bez segments.date agreguje za celé DURING období; s segments.date získáte řádek na kampaň na den, tedy 30× více řádků. Vědomě vyberte podle potřeby (souhrny období vs. časové řady). Past 3: ORDER BY je povinné pro konzistentní stránkování. API automaticky stránkuje nad 10 000 řádků; bez explicitního ORDER BY není pořadí stránek zaručeno a při batch zpracování riskujete přeskočení entit.

Pro testování dalších GAQL dotazů interaktivně bez Pythonu poskytuje Google GAQL Query Builder v oficiální dokumentaci — nejrychlejší způsob iterace nad strukturou dotazu před jeho zakódováním. Praktický tip: prototypujte dotaz v query builderu, zkopírujte do Python skriptu a teprve pak přidejte mapování na sloupce BigQuery nebo pandas. Vyhnete se přepisování dotazu třikrát při ladění kvůli zapomenutému poli.

Mutace: vytvoření, aktualizace, pozastavení kampaně

Mutace jsou operace zápisu API: vytvoření kampaně, změna rozpočtu, pozastavení klíčového slova, přidání vyloučeného slova. Procházejí dedikovanými službami (CampaignService, CampaignBudgetService, KeywordPlanService atd.) a používají vzor operation > mutation > response.

Zde je skript, který pozastaví kampaň podle jejího ID:

# pause_campaign.py
from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException

CUSTOMER_ID = "1112223333"
CAMPAIGN_ID = "111222333"

def pause_campaign(client, customer_id, campaign_id):
    campaign_service = client.get_service("CampaignService")

    # Sestavení resource_name (povinný formát)
    resource_name = campaign_service.campaign_path(customer_id, campaign_id)

    # Operace: update
    campaign_operation = client.get_type("CampaignOperation")
    campaign = campaign_operation.update
    campaign.resource_name = resource_name
    campaign.status = client.enums.CampaignStatusEnum.PAUSED

    # Field mask (specifikuje, co aktualizujeme)
    client.copy_from(
        campaign_operation.update_mask,
        protobuf_helpers.field_mask(None, campaign._pb),
    )

    try:
        response = campaign_service.mutate_campaigns(
            customer_id=customer_id,
            operations=[campaign_operation],
        )
        print(f"Paused campaign: {response.results[0].resource_name}")

    except GoogleAdsException as ex:
        for error in ex.failure.errors:
            print(f"Error: {error.message}")
            if error.location:
                for field in error.location.field_path_elements:
                    print(f"  Field: {field.field_name}")
        raise

if __name__ == "__main__":
    from google.api_core import protobuf_helpers
    client = GoogleAdsClient.load_from_storage("google-ads.yaml")
    pause_campaign(client, CUSTOMER_ID, CAMPAIGN_ID)

Kritický vzor pro VŠECHNY mutace: resource_name + update_mask. resource_name identifikuje entitu (customers/{customer_id}/campaigns/{campaign_id}), update_mask specifikuje, která pole měníme (bez něj API vrátí INVALID_FIELD_MASK). protobuf_helpers.field_mask(None, campaign._pb) automaticky vygeneruje masku z modifikovaných polí.

Pro vytvoření nové kampaně (Search Standard, denní rozpočet 100 EUR, Manual CPC):

# create_search_campaign.py
from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException
import uuid

CUSTOMER_ID = "1112223333"

def create_campaign_budget(client, customer_id, daily_budget_eur):
    budget_service = client.get_service("CampaignBudgetService")
    operation = client.get_type("CampaignBudgetOperation")
    budget = operation.create
    budget.name = f"Budget {uuid.uuid4().hex[:8]}"
    budget.amount_micros = int(daily_budget_eur * 1_000_000)
    budget.delivery_method = client.enums.BudgetDeliveryMethodEnum.STANDARD

    response = budget_service.mutate_campaign_budgets(
        customer_id=customer_id, operations=[operation]
    )
    return response.results[0].resource_name

def create_search_campaign(client, customer_id, name, budget_resource):
    campaign_service = client.get_service("CampaignService")
    operation = client.get_type("CampaignOperation")
    campaign = operation.create

    campaign.name = name
    campaign.advertising_channel_type = client.enums.AdvertisingChannelTypeEnum.SEARCH
    campaign.status = client.enums.CampaignStatusEnum.PAUSED  # vytvořit jako PAUSED, aktivovat po kontrole
    campaign.manual_cpc.enhanced_cpc_enabled = False
    campaign.campaign_budget = budget_resource

    # Nastavení sítě
    campaign.network_settings.target_google_search = True
    campaign.network_settings.target_search_network = True
    campaign.network_settings.target_content_network = False
    campaign.network_settings.target_partner_search_network = False

    response = campaign_service.mutate_campaigns(
        customer_id=customer_id, operations=[operation]
    )
    return response.results[0].resource_name

if __name__ == "__main__":
    client = GoogleAdsClient.load_from_storage("google-ads.yaml")

    budget_rn = create_campaign_budget(client, CUSTOMER_ID, daily_budget_eur=100)
    print(f"Budget created: {budget_rn}")

    campaign_rn = create_search_campaign(
        client, CUSTOMER_ID, "Test API Campaign", budget_rn
    )
    print(f"Campaign created (PAUSED): {campaign_rn}")

Osvědčené postupy pro mutace:

  • Vždy vytvářet jako PAUSED na začátku, aktivovat ručně po kontrole. Kampaň omylem vytvořená jako ENABLED může spotřebovat rozpočet za několik hodin.
  • Logovat resource_name vrácený API pro sledovatelnost.
  • Systematicky zabalovat do try/except GoogleAdsException (viz sekce retry).
  • Testovat na testovacím účtu před zásahem do produkce. API nenabízí nativní dry-run mód (na rozdíl od Scriptů).

Zpracování chyb a retry logika v produkci

Google Ads API může vracet 3 kategorie chyb: přechodné (RESOURCE_EXHAUSTED, DEADLINE_EXCEEDED, UNAVAILABLE) odůvodňující retry s backoffem, chyby klienta (INVALID_ARGUMENT, NOT_FOUND, PERMISSION_DENIED), které nikdy neuspějí při opakování, a rate limit (TOO_MANY_REQUESTS) vyžadující čekání na další okno.

Zde je wrapper exponenciálního retry ke zkopírování do všech produkčních skriptů:

# retry_helpers.py
import time
from functools import wraps
from google.ads.googleads.errors import GoogleAdsException
from google.api_core.exceptions import (
    DeadlineExceeded, ServiceUnavailable, ResourceExhausted
)

RETRYABLE_ERRORS = (
    DeadlineExceeded,
    ServiceUnavailable,
    ResourceExhausted,
)

def with_retry(max_retries=3, base_delay=2.0, max_delay=60.0):
    """
    Decorator, který automaticky opakuje s exponenciálním backoffem
    při přechodných chybách Google Ads API.
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries + 1):
                try:
                    return func(*args, **kwargs)

                except RETRYABLE_ERRORS as e:
                    if attempt == max_retries:
                        print(f"[RETRY] Max retries reached: {e}")
                        raise

                    delay = min(base_delay * (2 ** attempt), max_delay)
                    print(f"[RETRY] Attempt {attempt + 1}/{max_retries} "
                          f"failed: {type(e).__name__}. Retry in {delay}s...")
                    time.sleep(delay)

                except GoogleAdsException as e:
                    # Kontrola, zda je chyba opakovatelná (zejm. rate limit)
                    is_retryable = any(
                        err.error_code.quota_error or
                        err.error_code.internal_error
                        for err in e.failure.errors
                    )

                    if is_retryable and attempt < max_retries:
                        delay = min(base_delay * (2 ** attempt), max_delay)
                        print(f"[RETRY] GoogleAdsException retryable. "
                              f"Retry in {delay}s...")
                        time.sleep(delay)
                    else:
                        # Neopakovatelná chyba klienta
                        for error in e.failure.errors:
                            print(f"[ERROR] {error.error_code}: {error.message}")
                        raise

            return None

        return wrapper
    return decorator

# Použití
@with_retry(max_retries=3, base_delay=2.0)
def pull_performance_safe(client, customer_id):
    ga_service = client.get_service("GoogleAdsService")
    query = "SELECT campaign.id, campaign.name FROM campaign LIMIT 100"
    return list(ga_service.search(customer_id=customer_id, query=query))

Vzor: exponenciální backoff (prodleva se při každém retry zdvojí, zastropuje na 60 sekundách) + klasifikace chyb (opakovatelné vs. neopakovatelné). Nikdy neopakovat při INVALID_ARGUMENT — jde o chybu ve vašem kódu, ne o síťový problém. Nikdy neopakovat donekonečna — max 3 až 5 retries, jinak maskujete strukturální problém (revokovaný token, trvale vyčerpaná kvóta).

Pro strukturované logování použijte standardní Python logging s JSON formátem pro ingestion do observability stacku (Datadog, Grafana Loki):

import logging
import json

class JsonFormatter(logging.Formatter):
    def format(self, record):
        log_obj = {
            "ts": self.formatTime(record),
            "level": record.levelname,
            "msg": record.getMessage(),
            "module": record.module,
        }
        if record.exc_info:
            log_obj["exc"] = self.formatException(record.exc_info)
        return json.dumps(log_obj)

logger = logging.getLogger("google_ads_api")
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)

# Použití
logger.info("Pulled 247 campaigns", extra={"customer_id": CUSTOMER_ID})
Klíčový poznatek :

U sledovaných účtů ve veřejných benchmarcích Google Ads API pipeline bez retry logiky selhávají průměrně 2 až 4× za měsíc kvůli přechodným síťovým chybám nebo rate limitu, oproti 0 až 1× za čtvrtletí s retry wrapperem + backoffem. To je investice 30 řádků kódu, která posune skript z „demo" na „produkčně připravený".

OAuth refresh flow: na co se zapomíná v produkci

refresh_token Google Ads nevypráší, pokud je použit alespoň jednou za 6 měsíců. Může však být revokován v několika případech, které je třeba znát. První případ: uživatel Google, který token vygeneroval, změní heslo nebo MFA — všechny refresh_tokeny vázané na tento účet se zneplatní. Druhý případ: Google detekuje podezřelé chování (token použitý z 30 různých IP za 1 hodinu), automaticky revokuje a zašle bezpečnostní email. Třetí případ: překročení limitu 50 simultánně aktivních refresh_tokenů na OAuth klienta spustí tiché FIFO zneplatnění nejstarších. Proto doporučujeme dedikovaný technický účet pro API (nikdy osobní Google účet zaměstnance) se stabilním heslem a MFA hardware klíčem.

Vzor retry při auth chybě musí být odlišný od přechodného síťového retry. Pokud obdržíte UNAUTHENTICATED nebo PERMISSION_DENIED vázané na token, NIKDY neopakujte s backoffem — token neobživne. Místo toho spusťte upozornění (PagerDuty, Slack ops) a nechte člověka vygenerovat nový refresh_token skriptem generate_user_credentials.py. Záměna těchto dvou případů může stát hodiny ladění a ztracené konverze. Pro omezení překvapení monitorujte zdraví tokenu jednoduchým cronem: minimalistický GAQL dotaz (SELECT customer.id FROM customer LIMIT 1) každých 6 hodin, upozornění při selhání auth.

Rate limiting: pochopení kvót v praxi

API kvóty jsou strukturovány na dvou úrovních: operace za den (Test 15 000, Basic 10 000 na klientský účet, Standard oficálně neomezeno, ale throttlování od cca 1 milionu/den) a dotazy za sekundu (soft limit kolem 50 RPS udržovaných na OAuth klienta, pod touto hodnotou API vrací bez chyby). Jedna operace = jeden mutovaný řádek, jeden get nebo jedna stránka výsledků GAQL. GAQL dotaz vracející 5 000 řádků se počítá jako 1 operace, ne jako 5 000.

Nejčastější past v produkci: batch mutace překračující 5 000 operací v jediném volání. API pak vrátí RESOURCE_EXHAUSTED ne kvůli denní kvótě, ale kvůli per-call limitu 5 000 operací. Správný vzor je rozdělení listů do chunků: pokud máte 12 000 vyloučených klíčových slov k přidání, proveďte 3 volání po 4 000 klíčových slovech místo jednoho s 12 000. Chunking přidá 5 řádků kódu a zabrání 90 % chyb při bulk operacích.

# Chunking vzor pro batch mutace
def chunked(iterable, size=4000):
    for i in range(0, len(iterable), size):
        yield iterable[i:i + size]

for chunk in chunked(all_operations, size=4000):
    response = service.mutate_negatives(
        customer_id=customer_id, operations=chunk
    )
    print(f"Chunk processed: {len(response.results)} mutations")

Pro noční batch pipeline přidejte time.sleep(0.5) mezi chunky pro vyhlazení zátěže bez degradace celkového throughputs. U sledovaných účtů ve veřejných benchmarcích Google Ads batch 50 000 mutací v chuncích po 4 000 + sleep 500 ms trvá přibližně 8–10 minut oproti 4–5 minutám v chuncích po 4 000 bez sleep — ale s mírou selhání dělenou 5. Kompromis čas/spolehlivost za sleep rozhodně stojí.

6 Python skriptů připravených k forknutí

Pro urychlení startu publikujeme veřejný GitHub repozitář github.com/steerads/google-ads-python-starter s 6 zdokumentovanými Python skripty připravenými k forknutí. Každý skript je autonomní, konfigurovaný přes env proměnné a obsahuje retry logiku. Zde je seznam s ukázkovým snippetem pro každý.

Skript 1 — Pull výkonnosti kampaní LAST_30_DAYS

Stahuje kompletní KPI (imprese, kliky, náklady, konverze, ROAS) ENABLED kampaní, exportuje do CSV nebo ukládá do BigQuery. Frekvence: denně nebo hodinově. Viz kompletní snippet v sekci 3.

Skript 2 — Hromadná aktualizace rozpočtu kampaní

Mění denní rozpočty N kampaní v jediné batch operaci. Užitečné pro měsíční rebalancing rozpočtu nebo automatické sezónní úpravy.

# bulk_update_budgets.py
def bulk_update_budgets(client, customer_id, updates):
    """
    updates = [{"budget_id": "111", "new_amount_eur": 150}, ...]
    """
    service = client.get_service("CampaignBudgetService")
    operations = []

    for u in updates:
        op = client.get_type("CampaignBudgetOperation")
        budget = op.update
        budget.resource_name = service.campaign_budget_path(
            customer_id, u["budget_id"]
        )
        budget.amount_micros = int(u["new_amount_eur"] * 1_000_000)

        client.copy_from(
            op.update_mask,
            protobuf_helpers.field_mask(None, budget._pb),
        )
        operations.append(op)

    response = service.mutate_campaign_budgets(
        customer_id=customer_id, operations=operations
    )
    return [r.resource_name for r in response.results]

Skript 3 — Přidání vyloučených klíčových slov ze search query reportu

Stahuje Search Term Performance Report za 30 dní, identifikuje dotazy s 0 konverzemi a více než 15 kliky, přidává je jako vyloučená slova na úrovni kampaně. Ekvivalent skriptu 2 z našeho průvodce 10 Scripts ready-to-copy, ale přes API pro zpracování 100+ kampaní v jediném spuštění.

Skript 4 — Pozastavení podvýkonných klíčových slov (CTR + CPA)

Identifikuje klíčová slova s CTR pod 1 % A CPA nad 2× cílovém CPA za 30 dní, automaticky je pozastaví s logem před akcí. Multidimenzionální kritérium nemožné na Scriptech (kde je nutné iterovat přes AdsApp.keywords()), v GAQL triviální.

Skript 5 — Export reportu do BigQuery (data warehousing)

Stahuje agregované metriky, transformuje přes pandas, načítá do BigQuery pomocí google-cloud-bigquery. To je případ použití, kde API předčí Scripty: připojit Scripty k BigQuery správně je nemožné, zatímco v Pythonu je to 10 řádků.

# bigquery_export.py
from google.cloud import bigquery
import pandas as pd

def export_to_bigquery(client, customer_id, dataset_id, table_id):
    # 1. Pull GAQL dat
    perf = pull_performance(client, customer_id)
    df = pd.DataFrame(perf)
    df["snapshot_date"] = pd.Timestamp.today().normalize()

    # 2. BigQuery klient
    bq = bigquery.Client()
    table_ref = f"{bq.project}.{dataset_id}.{table_id}"

    # 3. Načtení s append
    job_config = bigquery.LoadJobConfig(
        write_disposition=bigquery.WriteDisposition.WRITE_APPEND,
        autodetect=True,
    )
    job = bq.load_table_from_dataframe(df, table_ref, job_config=job_config)
    job.result()  # čekat na dokončení

    print(f"Loaded {len(df)} rows to {table_ref}")

Skript 6 — Denní monitoring se Slack upozorněními

Kombinuje pull výkonnosti + kontrolu anomálií (výdaje, CPA, konverze) + push do Slacku přes webhook při detekci anomálie. Průmyslový ekvivalent cron jobu denního monitoringu.

# daily_monitoring.py
import requests

SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"

def detect_anomalies(perf):
    alerts = []
    for c in perf:
        # Výdaje > 2× průměr 7 dní
        if c.get("cost_eur", 0) > c.get("avg_cost_7d", 0) * 2:
            alerts.append(f"Výdaje +100%: {c['name']}")
        # CPA > 3× cíl
        if c.get("cpa_eur") and c.get("target_cpa") and \
           c["cpa_eur"] > c["target_cpa"] * 3:
            alerts.append(f"CPA 3× cíl: {c['name']}")
    return alerts

def send_slack(alerts):
    if not alerts:
        return
    msg = "Google Ads anomalie detekovány:\n" + "\n".join(f"- {a}" for a in alerts)
    requests.post(SLACK_WEBHOOK, json={"text": msg})

if __name__ == "__main__":
    client = GoogleAdsClient.load_from_storage("google-ads.yaml")
    perf = pull_performance(client, CUSTOMER_ID)
    alerts = detect_anomalies(perf)
    send_slack(alerts)

Repozitář github.com/steerads/google-ads-python-starter obsahuje pro každý skript: kompletní zdokumentovaný kód, README s krok za krokem setupem, soubor .env.example pro proměnné a requirements.txt s pevně danými verzemi pro reprodukovatelnost. Pro cross-platform automatizaci viz také náš průvodce n8n Google Ads automation flows a náš průvodce MCP Google Ads + Claude Desktop pro řízení Google Ads konverzačně z Claude.

Kdy přejít ze Scriptů na API

Přechod ze Scriptů na API není systematický upgrade — je to rozhodnutí o kompromisu. Zde je 5 konkrétních spouštěčů sledovaných ve veřejných benchmarcích při auditech.

Spouštěč 1: multi-account (10+ účtů k paralelnímu řízení). Scripty jsou ze své podstaty mono-account (jeden skript vázaný na účet nebo MCC). Nad 10 účtů k synchronizaci se Python API se smyčkou for na seznamu customer_ids stává mnohem lépe udržovatelným.

Spouštěč 2: data warehousing. Pokud chcete ukládat metriky do BigQuery, Snowflake, Redshift, je API povinné. Scripty UrlFetchApp technicky mohou volat REST API, ale autentizace GCP, batching, ETL s pandas — to vyžaduje Python.

Spouštěč 3: obousměrná integrace CRM. Pro push offline konverzí ze Salesforce/HubSpotu podporuje Google Ads API OfflineUserDataJobService pro zabezpečený batch upload. Viz náš průvodce conversion tracking Google Ads pro funkční rámec.

Spouštěč 4: ML / pokročilé statistické potřeby. Statistická detekce anomálií (ARIMA, Prophet), segmentace klíčových slov (clustering), prognózování výdajů — všechny tyto úkoly vyžadují numpy/pandas/scikit-learn, nemožné ve Scriptech.

Spouštěč 5: interní produkt. Vytváříte dashboard nebo interní nástroj zobrazující/manipulující data Google Ads. Nutně API — Scripty nemohou vystavit UI ani odpovídat na HTTP dotaz.

Pro opačných 5 případů (jeden účet, jednoduché monitorování, žádné datové potřeby, netechnický tým, omezená infrastruktura) zůstávají Scripty lepší z hlediska nákladů/údržby. Náš průvodce Microsoft Ads Scripts pokrývá ekvivalent na straně Microsoftu.

Pro účty, které chtějí průmyslovou automatizaci bez psaní vlastního stacku, náš modul Auto-optimalizace pokrývá ekvivalent 6 výše uvedených skriptů v managed módu: pull výkonnosti multi-account, detekce anomálií, Slack upozornění, BigQuery sync — bez jediného řádku Pythonu k údržbě. Viz také náš checklist auditu Google Ads pro základ auditu, který musí předcházet každé automatizaci, a náš srovnání Zapier vs Make pro doplňkové no-code automatizace.

Časté chyby při začátcích s API

Pět opakujících se chyb zpomaluje začátečníky v Pythonu na Google Ads API. Každá stojí průměrně několik hodin zbytečného ladění, pokud znáte past předem. Zde je seznam s diagnózou a přímou opravou.

1. Záměna customer_id a login_customer_id. Diagnóza: skript vrátí INVALID_CUSTOMER_ID nebo NOT_ADS_USER, přestože vše vypadá správně nakonfigurované. Oprava: login_customer_id (v YAML) = ID nadřazeného MCC, pod kterým se API autentizuje, bez pomlček. customer_id (v dotazu) = ID klientského účtu, který chcete dotazovat, bez pomlček. Pokud dotazujete přímo účet, který není pod MCC, zadejte jeho ID na obě místa. Vždy odstraňujte pomlčky formátu 123-456-7890 -> 1234567890.

2. Zapomenutí update_mask u mutací. Diagnóza: mutace selže s MISSING_REQUIRED_FIELD: update_mask nebo všechna pole entity jsou nahrazena výchozími hodnotami (katastrofa v produkci). Oprava: pro každou operaci update vygenerujte masku přes protobuf_helpers.field_mask(None, entity._pb) po nastavení polí. Maska říká API, která pole měníte; bez ní API buď odmítne, nebo interpretuje jako „všechna pole jsou na výchozí hodnotě" a vše vynuluje.

3. Individuální pull ve smyčce místo batch. Diagnóza: skript stahující 500 kampaní trvá 25 minut místo 30 sekund. Oprava: NIKDY nepoužívat for campaign_id in ids: ga_service.search(...) s jedním dotazem na kampaň. Místo toho jeden GAQL dotaz filtrující WHERE campaign.id IN (...) nebo stažení všeho a filtrování na straně Pythonu. API není penalizováno za velikost dotazu, je za počet volání.

4. Testování přímo v produkci bez testovacího účtu. Diagnóza: chyba zaokrouhlení v kódu mutace rozpočtu omylem sníží všechny rozpočty na 1 EUR. Produkce se zhroutí. Oprava: vytvořte testovací Google Ads účet (zdarma, bez přidružené platební karty) pro všechny vývojové mutace. Developer_token Test pokrývá pouze testovací účty — což je vítaná neúmyslná ochrana. Přepněte na Basic Access až po validaci kódu v sandboxu na 2–3 skutečných případech použití.

5. Ignorování stránkování GAQL nad 10 000 řádků. Diagnóza: skript stahující klíčová slova velkého účtu selže nad 10 000 řádků nebo vrátí neúplné výsledky bez chyby. Oprava: používejte ga_service.search_stream(...), který automaticky stránkuje bez načítání celého výsledku do paměti. Pro stahování 50 000+ řádků je povinné — search() načte vše do paměti a selže na strojích s malou RAM.

Pro oficiální dokumentaci viz portál Google Ads API a oficiální repozitář google-ads-python, který obsahuje desítky příkladů udržovaných Googlem.

Zdroje

Oficiální zdroje použité v tomto průvodci:

FAQ

Je potřeba developer token pro použití Google Ads API?

Ano, je povinný. Developer token je vázán na MCC (manager) účet a získá se přes Tools and Settings > API Center v Google Ads. Počáteční token začíná v Test módu (limit 15 000 operací/den, pouze testovací účty), pak Basic Access (10 000 operací/den, produkční účty) na vyžádání, pak Standard Access (neomezeno) po revizi Googlem. Počítejte 1 až 5 pracovních dní pro Basic, 2 až 4 týdny pro Standard s dokumentací vašeho případu použití. Pro začátek token Test plně postačuje: testování OAuth, psaní prvních GAQL dotazů, ladění mutací na testovacím účtu. Přechod na Basic nastane až po validaci kódu v sandboxu.

Jaký je rozdíl mezi google-ads-python (oficiální) a googleads (legacy)?

google-ads-python je moderní oficiální knihovna konzumující REST/gRPC API v17+ (verze 2024–2026). googleads byla legacy knihovna konzumující SOAP API v201809 (deprecated od roku 2022, zcela vypnuta koncem roku 2023). Pokud narazíte na kód s googleads nebo AdWords API na fórech, je zastaralý. Pro rok 2026 používejte VÝHRADNĚ google-ads-python (pip install google-ads), minimálně verzi 24.0.0 odpovídající API v17. Knihovna vystavuje klienta GoogleAdsClient inicializovaného z YAML nebo env vars se typovanými službami (CampaignService, KeywordService, GoogleAdsService pro GAQL dotazy). Oficiální dokumentace na developers.google.com/google-ads/api/docs/client-libs/python.

Kolik GAQL dotazů za sekundu API zvládne?

Kvóty závisí na úrovni vašeho developer tokenu. Test = 15 000 operací/den celkem, Basic = 10 000 operací/den na klientský účet, Standard = žádná oficiální kvóta, ale Google throttluje při zneužití. Jedna operace = jeden GAQL dotaz nebo jedna mutace. U sledovaných účtů ve veřejných benchmarcích Google Ads dominuje vzor 200 až 800 dotazů/den pro skript denního monitoringu na mid-size účtu. Hodinový rate limit je max 10 000 dotazů/hodinu na OAuth klienta. Nad touto hodnotou API vrátí RESOURCE_EXHAUSTED, který je nutné ošetřit exponenciálním backoffem (viz sekce retry logika). Pro skript vyžadující 5 000+ mutací plánujte batch processing se sleep mezi batchy.

Dokáže API spravovat Smart Bidding a Performance Max?

Ano, API vystavuje všechny funkce Google Ads včetně nejnovějších (Performance Max, Smart Bidding, Demand Gen). Pro Performance Max pracujete s CampaignService (campaignType=PERFORMANCE_MAX), AssetGroupService (skupiny assets PMax) a ConversionGoalService. Pro Smart Bidding jsou to bidding_strategy fields kampaní (TARGET_CPA, TARGET_ROAS, MAXIMIZE_CONVERSIONS). Omezení: vytvoření PMax kampaně přes API vyžaduje všechny assets zároveň (obrázky, headlines, popisy, sitelinky), což kód komplikuje oproti vytváření Search kampaní. Pro začátek preferujte tvorbu/úpravu Search nebo Shopping kampaní a na PMax přejděte až po stabilizaci pipeline. Viz náš průvodce Performance Max pro strategii asset group.

Jak zabezpečit OAuth credentials v produkci?

Tři principy: NIKDY necommitovat google-ads.yaml ani refresh_tokeny do Gitu (přidejte je do .gitignore), používejte secret managery (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault) pro produkční uložení a otáčejte refresh_tokeny periodicky (doporučeno max 90 dní). Pro lokální dev setup je YAML v home directory OK. Pro produkci načítejte YAML ze secret manageru při startu aplikace. developer_token, client_id, client_secret mohou být v env vars; refresh_token musí být v přísném secret manageru. Pokud refresh_token unikne (náhodný commit na GitHub), okamžitě ho zrušte přes Google Cloud Console > APIs and Services > Credentials, vygenerujte nový a auditujte API logy pro detekci případných škodlivých volání.

Ready to optimize your campaigns?

Start a free audit in 2 minutes and discover the ROI potential of your accounts.

Start my free audit

Free audit — no credit card required

Keep reading