# Google Ads Transparency Scraper (cached) (`leonguyen2808/google-ads-transparency`) Actor

Every ad an advertiser is running, from Google's Ads Transparency Center — creative id, format, preview URL, first/last shown date. Search by advertiser name or AR… id. No API key, no browser.

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

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 (cached)

Every ad an advertiser is running, straight from Google's **Ads Transparency Center** — creative id,
format, preview URL, and the first/last date each ad was shown. Search advertisers by name, or go
straight to an `AR…` id. No API key, no browser.

### What you get

Advertiser rows (from `keywords`):

| Field | Example |
|---|---|
| `advertiserName` | `NIKE SRL` |
| `advertiserId` | `AR17365672681860497409` |
| `advertiserCountry` | `IT` |
| `declaredAdCount` | `17` — Google's own count |
| `advertiserUrl` | link to the Transparency Center page |

Ad rows (one per creative):

| Field | Example |
|---|---|
| `creativeId` | `CR18300947740944760833` |
| `format` | `image` or `rendered_preview` |
| `previewUrl` | direct image URL, or Google's rendered-preview URL |
| `imageHtml` | the `<img>` snippet Google returns, for image ads |
| `firstShownAt`, `lastShownAt` | ISO 8601 |
| `creativeUrl` | link to the ad in the Transparency Center |
| `fromCache` | whether this came from cache or a live fetch |

### How it behaves

Measured against the live endpoints on 2026-08-06:

- **Zero ads is a real answer, not a failure.** Google keeps advertisers whose ads have stopped
  running and still reports a count for them — 3 of 20 advertisers tested returned no creatives for
  that reason. Those come back as an explicit row with `adCount: 0` and `declaredAdCount` beside it,
  so you can see the difference. A scraper that returned silence here would look broken.
- **An advertiser that fails does not fail the run.** It comes back as a row with `error`, so a
  20-advertiser job returns the other 19.
- **Pagination stops when the advertiser runs out**, not when a page counter says so. Asking for 100
  ads from an advertiser with 9 returns 9 and spends nothing extra.
- **Google rate-limits this endpoint per IP** — about 25 requests from a single address was enough
  to earn a 429 in testing. So this Actor paces itself, caches every result, and **rotates to a
  fresh proxy IP instead of sleeping** through a limit. Two runs asking for the same advertiser
  within the cache window cost **one** request to Google, not two.

### Limits — read before buying

- `region` is Google's **internal enum, not a country code**, and a wrong value returns zero ads
  with no error. Leave it empty (the default) to get ads from all regions. This Actor will not guess
  a value for you, because guessing would silently look like "this advertiser has no ads".
- `format` is reported from the ad content actually present. `formatCode` passes Google's raw code
  through untouched — the full enum is not published, and inventing labels for unseen values would
  be a guess dressed up as data.
- The Transparency Center covers ads Google chooses to disclose. It is not a complete record of an
  advertiser's spend, and it carries no impression or budget figures.
- Ads Transparency has **no official public API** and Google's Terms of Service restrict automated
  access. You are responsible for how you use the output.
- No personal data is collected — advertiser and creative records only.

### Input

```json
{
  "keywords": ["nike"],
  "advertisersPerKeyword": 5,
  "maxAdsPerAdvertiser": 100
}
```

Already know the advertiser? Skip the lookup:

```json
{
  "advertisers": ["https://adstransparency.google.com/advertiser/AR13536189912721653761"],
  "maxAdsPerAdvertiser": 40
}
```

Only need the ids, not the ads (much cheaper):

```json
{ "keywords": ["nike", "adidas"], "fetchAds": false }
```

### Development

```bash
pip install -r requirements.txt
mkdir -p storage/key_value_stores/default
echo '{"keywords":["nike"],"useApifyProxy":false}' > storage/key_value_stores/default/INPUT.json
python -m src
```

`src/ads.py` is plain standard library and can be exercised without the Apify SDK — useful when
checking whether Google changed the request shape:

```bash
python -c "from src.ads import AdsClient; c=AdsClient(); print(c.search_advertisers('nike')[:3])"
```

# Actor input Schema

## `keywords` (type: `array`):

One per line. Looks advertisers up by name and returns their id, country and Google's own declared ad count — those rows are emitted whether or not you also fetch ads.

## `advertisers` (type: `array`):

One per line. Accepts an AR… id or a full Transparency Center URL (https://adstransparency.google.com/advertiser/AR…). Use this when you already know the advertiser; it skips the name lookup.

## `advertisersPerKeyword` (type: `integer`):

How many name matches to keep per keyword. Google returns up to 20; each one kept then costs its own ads request.

## `fetchAds` (type: `boolean`):

On (default): return every ad for each advertiser. Off: resolve advertiser ids only — much cheaper when all you need is which advertiser matches a brand name.

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

Google returns up to 40 per request, so this rounds up to whole pages. Pagination stops early when the advertiser runs out — it never wastes requests padding to your number.

## `region` (type: `string`):

Google's own internal region enum, NOT a country code. Leave empty to get ads from every region. A wrong value returns zero ads with no error from Google, so this Actor does not guess one for you.

## `cacheMaxAgeMinutes` (type: `integer`):

An advertiser's ad set changes over days, not minutes, so a recent cached answer is the same answer — and serving it costs Google nothing, which is why this Actor rarely gets rate-limited. Set 0 to force a live fetch.

## `useApifyProxy` (type: `boolean`):

Recommended. Google rate-limits this endpoint per IP: ~25 requests from one address was enough to earn a 429 in testing. Rotating IPs avoids reaching that point.

## `apifyProxyGroups` (type: `array`):

Leave empty for datacenter proxies — sufficient here, the endpoint has no browser challenge. Use RESIDENTIAL only if datacenter IPs start getting 429s.

## `apifyProxyCountry` (type: `string`):

Optional two-letter code. Leave empty unless you need requests to originate from a specific country.

## `minRequestGapSeconds` (type: `string`):

Default 1.1s. Lower it only with your own proxy pool.

## Actor input object example

```json
{
  "keywords": [
    "nike"
  ],
  "advertisers": [],
  "advertisersPerKeyword": 5,
  "fetchAds": true,
  "maxAdsPerAdvertiser": 100,
  "region": "",
  "cacheMaxAgeMinutes": 360,
  "useApifyProxy": true,
  "apifyProxyGroups": [],
  "apifyProxyCountry": "",
  "minRequestGapSeconds": "1.1"
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `resultsCsv` (type: `string`):

No description

## `cache` (type: `string`):

No description

# 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 = {
    "keywords": [
        "nike"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("leonguyen2808/google-ads-transparency").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 = { "keywords": ["nike"] }

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

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

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

```

## CLI example

```bash
echo '{
  "keywords": [
    "nike"
  ]
}' |
apify call leonguyen2808/google-ads-transparency --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=leonguyen2808/google-ads-transparency",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/7szhXotvQ5p4CWX3U/builds/QwXMgJLEKJeag2Olx/openapi.json
