← 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}")