Skip to content
Nosey Parkers
Knowledge Centre
Python Scripts1 min read

Python script: export Google Ads performance to a warehouse

A production-shaped Python pattern for pulling daily campaign performance from the Google Ads API — with the retry, paging and schema habits that keep it alive.

The Google Ads API has a learning curve, but daily campaign extraction is a solved pattern. Here's the shape we deploy, trimmed to its teachable core.

The query

Google Ads uses GAQL, a SQL-ish query language:

QUERY = """
    SELECT
      segments.date,
      campaign.id,
      campaign.name,
      campaign.advertising_channel_type,
      metrics.cost_micros,
      metrics.impressions,
      metrics.clicks,
      metrics.conversions,
      metrics.conversions_value
    FROM campaign
    WHERE segments.date DURING LAST_7_DAYS
      AND campaign.status != 'REMOVED'
"""

Pull seven days on every run, not one. Ads conversions are restated for days after the click (conversion lag), so re-fetching a rolling window self-heals your history.

The extraction loop

from google.ads.googleads.client import GoogleAdsClient

def fetch_campaign_performance(client: GoogleAdsClient, customer_id: str):
    service = client.get_service("GoogleAdsService")
    rows = []
    for batch in service.search_stream(customer_id=customer_id, query=QUERY):
        for row in batch.results:
            rows.append({
                "date": row.segments.date,
                "campaign_id": row.campaign.id,
                "campaign_name": row.campaign.name,
                "channel": row.campaign.advertising_channel_type.name,
                "cost": row.metrics.cost_micros / 1_000_000,  # micros → currency
                "impressions": row.metrics.impressions,
                "clicks": row.metrics.clicks,
                "conversions": row.metrics.conversions,
                "conversion_value": row.metrics.conversions_value,
            })
    return rows

Loading: idempotent or nothing

Write to the warehouse with a merge/upsert keyed on (date, campaign_id) — never a blind append. Combined with the rolling window, this makes the pipeline safely re-runnable: crash it, rerun it, run it twice — the data is identical.

MERGE INTO ads.google_campaign_daily AS t
USING staging.google_campaign_daily AS s
  ON t.date = s.date AND t.campaign_id = s.campaign_id
WHEN MATCHED THEN UPDATE SET
  cost = s.cost, clicks = s.clicks,
  conversions = s.conversions, conversion_value = s.conversion_value
WHEN NOT MATCHED THEN INSERT ROW

Production checklist

  • Retries with backoff on InternalError and quota exceptions — transient failures are routine.
  • Alert on zero rows. Silence is a failure mode; an account that returns nothing usually means auth expired.
  • Log request counts. The API has daily operation limits; know your headroom before you add ten more accounts.
  • Store cost_micros division in one place. Half the "our numbers are 1,000,000x off" tickets in history come from doing this twice.

Related guides

Ready to see what your data has been trying to tell you?

Book a free discovery call. We'll look at your current setup, tell you what's broken, and show you exactly what better looks like — no obligation, no fluff.