← back to Ga Allsites

scripts/batch_create_ga4.py

120 lines

#!/usr/bin/env python3
"""
Batch create GA4 properties + web data streams for all domains in _live_landings.json.
Writes results to cache/_ga4_ids.json  {domain: measurement_id}.
Resilient: one failure doesn't abort batch; exponential backoff on rate limits.
"""
from __future__ import annotations

import json
import os
import sys
import time
from pathlib import Path

# ── auth ──────────────────────────────────────────────────────────────────────
SA_KEY = Path.home() / ".config" / "ga-analytics-agent" / "service-account.json"
if not SA_KEY.exists():
    sys.exit(f"ERROR: service account key not found at {SA_KEY}")
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = str(SA_KEY)

# ── paths ─────────────────────────────────────────────────────────────────────
PROJECT_DIR = Path("/Users/macstudio3/Projects/ga-allsites")
LANDINGS_FILE = PROJECT_DIR / "cache" / "_live_landings.json"
OUTPUT_FILE   = PROJECT_DIR / "cache" / "_ga4_ids.json"

ACCOUNT_ID = "accounts/15714274"   # DesignerWallcoverings.com (SAND LLC)

# ── load domains ──────────────────────────────────────────────────────────────
domains: list[str] = json.loads(LANDINGS_FILE.read_text())

# ── load any previously written results (resume support) ──────────────────────
existing: dict[str, str] = {}
if OUTPUT_FILE.exists():
    try:
        existing = json.loads(OUTPUT_FILE.read_text())
        print(f"Resuming — {len(existing)} already recorded in {OUTPUT_FILE.name}")
    except Exception:
        pass

# ── GA4 client ────────────────────────────────────────────────────────────────
from google.analytics.admin import AnalyticsAdminServiceClient
from google.analytics.admin_v1alpha.types import Property, DataStream
from google.api_core.exceptions import ResourceExhausted, GoogleAPIError

client = AnalyticsAdminServiceClient()

results: dict[str, str] = dict(existing)   # domain → measurement_id
failed: list[str] = []

def create_property_and_stream(domain: str) -> str:
    """Create GA4 property + web stream; return measurement ID."""
    # Property
    prop = Property(
        parent=ACCOUNT_ID,
        display_name=domain,
        time_zone="America/Los_Angeles",
        currency_code="USD",
        industry_category="ONLINE_COMMUNITIES",
    )
    created = client.create_property(property=prop)
    prop_name = created.name

    # Web data stream
    stream = DataStream(
        type_=DataStream.DataStreamType.WEB_DATA_STREAM,
        display_name=f"{domain} Web",
        web_stream_data=DataStream.WebStreamData(
            default_uri=f"https://{domain}",
        ),
    )
    s = client.create_data_stream(parent=prop_name, data_stream=stream)
    return s.web_stream_data.measurement_id


print(f"\nBatch create: {len(domains)} domains under {ACCOUNT_ID}\n")

for i, domain in enumerate(domains, 1):
    if domain in results:
        print(f"[{i:>3}/{len(domains)}] {domain:<45} SKIP (already have {results[domain]})")
        continue

    retries = 0
    max_retries = 5
    while retries <= max_retries:
        try:
            mid = create_property_and_stream(domain)
            results[domain] = mid
            # Write after every success so we can resume on crash
            OUTPUT_FILE.write_text(json.dumps(results, indent=2))
            print(f"[{i:>3}/{len(domains)}] {domain:<45} → {mid}")
            time.sleep(0.6)   # gentle pacing — GA4 Admin API quota
            break
        except ResourceExhausted as e:
            retries += 1
            wait = 2 ** retries
            print(f"[{i:>3}/{len(domains)}] {domain:<45} RATE_LIMIT — retry {retries}/{max_retries} in {wait}s")
            time.sleep(wait)
        except GoogleAPIError as e:
            print(f"[{i:>3}/{len(domains)}] {domain:<45} API_ERROR: {e}")
            failed.append(domain)
            break
        except Exception as e:
            print(f"[{i:>3}/{len(domains)}] {domain:<45} ERROR: {e}")
            failed.append(domain)
            break
    else:
        print(f"[{i:>3}/{len(domains)}] {domain:<45} GAVE UP after {max_retries} retries")
        failed.append(domain)

# ── final write ───────────────────────────────────────────────────────────────
OUTPUT_FILE.write_text(json.dumps(results, indent=2))

print("\n" + "="*70)
print(f"DONE  — Account: {ACCOUNT_ID}")
print(f"  Succeeded : {len(results)}")
print(f"  Failed    : {len(failed)}")
if failed:
    print(f"  Failed domains: {failed}")
print(f"  Output    : {OUTPUT_FILE}")