# Google Ads Transparency Scraper (`egeusta/google-ads-transparency-scraper`) Actor

Extract ad creatives from the Google Ads Transparency Center and see what changed since your last run. Other scrapers list an advertiser's ads; this one flags which are new, which stopped, and which have run longest. Schedule it and the change log builds itself.

- **URL**: https://apify.com/egeusta/google-ads-transparency-scraper.md
- **Developed by:** [Ege](https://apify.com/egeusta) (community)
- **Categories:** SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 scraped ads

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Google Ads Transparency Scraper

Extract ad creatives from the Google Ads Transparency Center — and see what
changed since your last run.

Other scrapers answer *"what ads is this advertiser running?"*. This one also
answers *"what did they change?"*: which creatives are new, which stopped, and
which have been running longest. Schedule it and the change log builds itself.

### Output

| Field | Description |
|---|---|
| `advertiserId` | Google advertiser ID (`AR...`) |
| `advertiserName` | Advertiser display name |
| `creativeId` | Creative ID (`CR...`) |
| `format` | `text`, `image`, or `video` |
| `formatCode` | Raw format code from the API |
| `previewUrl` | Google-hosted creative preview |
| `transparencyUrl` | Link back to the ad in the Transparency Center |
| `firstShown` | Date the ad first ran |
| `lastShown` | Date the ad last ran |
| `daysActive` | Days between first and last shown |
| `isActive` | Whether `lastShown` falls inside your active window |
| `changeType` | `new`, `stopped`, `continuing`, or `baseline` |
| `previousLastShown` | `lastShown` recorded on the previous run |

Every record carries a `transparencyUrl`, so you can verify any row against the
real page in one click.

### Input

Find an advertiser at [adstransparency.google.com](https://adstransparency.google.com)
and paste either its URL or its advertiser ID:

```json
{
  "queries": ["AR17828074650563772417"],
  "mode": "changes",
  "activeWithinDays": 7,
  "stateKey": "default"
}
```

| Option | Default | Meaning |
|---|---|---|
| `queries` | — | Advertiser IDs or Transparency Center advertiser URLs |
| `mode` | `changes` | `snapshot` for a plain extract, `changes` to track movement |
| `activeWithinDays` | `7` | An ad last shown before this counts as stopped |
| `maxAdsPerAdvertiser` | `500` | Cap per advertiser |
| `stateKey` | `default` | Namespace for change detection — one per tracked portfolio |
| `regions` | `["anywhere"]` | Only `anywhere` is supported today |

#### How change detection works

Each run stores a fingerprint of every creative it saw. The next run compares
against it:

- **new** — not present last time
- **stopped** — gone from the results, or last shown before your active window
- **continuing** — still running

The first run on a new `stateKey` has nothing to compare against, so every ad
comes back as `baseline` and the run summary sets `baselineRun: true`. It does
not pretend your whole back catalogue just launched.

Use a separate `stateKey` per portfolio you track. Histories are isolated.

#### Current limitations

Stated plainly so nothing surprises you:

- **Domain and brand-name lookup is not supported.** Pass an advertiser ID or
  URL. Anything else is listed in `advertisersUnresolved` and is never charged.
- **Region filtering is not supported.** All results are worldwide.

### Run summary

Written to the run's own key-value store as `RUN_SUMMARY`, so each run keeps its
own copy:

```json
{
  "advertisersRequested": 20,
  "advertisersResolved": 20,
  "advertisersUnresolved": [],
  "advertisersSucceeded": 20,
  "advertisersFailed": {},
  "advertisersWithNoAds": ["AR047...", "AR075..."],
  "successRate": 1.0,
  "itemsPushed": 360,
  "changesDetected": 0,
  "baselineRun": false,
  "truncatedAdvertisers": ["AR183..."]
}
```

A failure on one advertiser never discards the others' results.

`advertisersWithNoAds` matters more than it looks: an advertiser can be scraped
successfully and simply have no ads running. That is a different outcome from a
failure, and you should not have to guess which one you got. `truncatedAdvertisers`
names advertisers that hit your `maxAdsPerAdvertiser` cap and have more available.

### Pricing

$0.002 per ad, $0.004 per detected change. You are charged only for items
actually delivered to your dataset.

### Development

```bash
pip install -r requirements-dev.txt
pytest                  # 57 tests, no network calls
pytest -m live          # contract tests against the real endpoint
apify validate-schema   # run before every push; the build rejects on one bad key
```

Publishing gotchas are recorded in `docs/publishing-notes.md`.

`normalize.py` and `differ.py` are pure and import neither `httpx` nor `apify`,
so the logic that produces the change data is tested entirely from fixtures.

# Actor input Schema

## `queries` (type: `array`):

Advertiser IDs (AR17828074650563772417) or Transparency Center advertiser URLs. Find an advertiser at adstransparency.google.com and copy the URL. Domain and brand-name lookup is not supported in v1.

## `mode` (type: `string`):

Snapshot returns every ad currently found. Changes compares against your previous run for this state key and labels each ad new, stopped or continuing.

## `regions` (type: `array`):

Only 'anywhere' is supported in v1. Per-country filtering needs a region enum mapping that is not yet resolved.

## `maxAdsPerAdvertiser` (type: `integer`):

Cap on ads collected per advertiser. Advertisers that hit the cap are listed in truncatedAdvertisers in the run summary.

## `activeWithinDays` (type: `integer`):

An ad whose last-shown date is older than this counts as stopped.

## `stateKey` (type: `string`):

Namespace for change detection. Use one per tracked portfolio.

## `proxyConfiguration` (type: `object`):

Datacenter proxy is enough for this endpoint; residential is not required. If proxy setup fails the run continues without it.

## Actor input object example

```json
{
  "queries": [
    "AR17828074650563772417"
  ],
  "mode": "changes",
  "regions": [
    "anywhere"
  ],
  "maxAdsPerAdvertiser": 500,
  "activeWithinDays": 7,
  "stateKey": "default",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `ads` (type: `string`):

One record per ad creative, carrying changeType (new, stopped, continuing or baseline), how long the ad has run, and a transparencyUrl for verifying any row against the real page.

## `runSummary` (type: `string`):

Counts of advertisers requested, resolved, succeeded and failed, plus advertisers that had no ads and advertisers truncated by the per-advertiser cap. Read this to tell a genuine empty result apart from a failure.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "queries": [
        "AR17828074650563772417"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("egeusta/google-ads-transparency-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "queries": ["AR17828074650563772417"] }

# Run the Actor and wait for it to finish
run = client.actor("egeusta/google-ads-transparency-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "queries": [
    "AR17828074650563772417"
  ]
}' |
apify call egeusta/google-ads-transparency-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,egeusta/google-ads-transparency-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/4726CxeRWm8nVl1YX/builds/aIm92V7DbahaRJL0F/openapi.json
