# Google Trends API & Scraper — pytrends Alternative (`westerly_breaker/google-trends-scraper`) Actor

Google Trends interest over time, interest by region, and related queries (top + rising) for any keyword, country, time range or explore URL. Direct API, automatic 429 retry, and you only pay for results that contain data.

- **URL**: https://apify.com/westerly_breaker/google-trends-scraper.md
- **Developed by:** [Daniel Posztos](https://apify.com/westerly_breaker) (community)
- **Categories:** SEO tools, Marketing, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.01 / 1,000 dataset items

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Google Trends API & Scraper — a pytrends Alternative Without 429 Errors

**SEO title:** Google Trends API — pytrends alternative, no 429 errors
**SEO description:** Google Trends API for Python, JavaScript or no code: interest over time, by region and related queries for any keyword. 429 rate limits handled for you; pay only for complete results.

A Google Trends API you can call from Python, JavaScript, any HTTP client or an AI agent, and a drop-in replacement for pytrends when it keeps failing with `TooManyRequestsError` (HTTP 429). Give it keywords (or Google Trends explore URLs) and get Google Trends data back as clean JSON, one row per query:

- **Interest over time**: the full 0-100 time series plus a per-term summary (average, peak and when it happened, latest value)
- **Interest by region**: countries, states/regions, cities (with coordinates) or US metro areas
- **Related queries**: top and rising searches, including "Breakout" terms

Compare up to 5 terms on one shared scale, or look up hundreds of terms one by one. Works for web, image, news, YouTube and Google Shopping search.

### Why this exists

Google Trends has no official API, and scraping it is unreliable: Google answers bursts of requests with **HTTP 429 "Too Many Requests"**. In our tests, 28 of 30 back-to-back data requests from one IP were rejected. Browser-based Trends scrapers run into this constantly, and they are slow.

This actor works differently:

- **No browser.** It calls the same JSON endpoints the Google Trends web page uses, and runs 3 queries in parallel.
- **Automatic 429 handling.** Each request goes out directly first. When Google rejects it, the actor waits and retries on fresh datacenter proxy sessions (new IP, new cookies), and as a last resort on residential IPs. Anything that still fails gets one more try at the end of the run, after a cool-down.
- **You only pay for complete results.** A term with no search volume, a query that still fails, or a row with a section missing still appears in the dataset (so you can see what happened), but it is not charged.

Cloud test, 40 US keywords: 39 complete results, 1 with no search volume (not charged), 0 failed, in 37 seconds. Why the retry ladder matters: in a test the same day, the shared datacenter proxy alone got throttled by Google (2 of 5 keywords failed after 42 retries), while direct requests from the same cloud kept working (5 of 5). So every request now starts direct, and proxies are only the fallback.

### Input

| Field | What it does | Default |
|---|---|---|
| `searchTerms` | Keywords, e.g. `["bitcoin", "ethereum"]`. One row per term. | — |
| `compareTerms` | Compare terms in groups of up to 5 on one shared 0-100 scale (one row per group). | `false` |
| `startUrls` | Google Trends explore URLs; each keeps its own settings. | — |
| `geo` | `""` = worldwide, `"US"`, `"US-CA"`, `"US-CA-807"` (metro) | `""` |
| `timeRange` | `now 1-H`, `now 4-H`, `now 1-d`, `now 7-d`, `today 1-m`, `today 3-m`, `today 12-m`, `today 5-y`, `all`, or `"2024-01-01 2024-12-31"` | `today 12-m` |
| `category` | Google Trends category ID (`cat=` in a Trends URL), 0 = all | `0` |
| `searchProperty` | `web`, `images`, `news`, `youtube`, `shopping` | `web` |
| `include` | Any of `interestOverTime`, `interestByRegion`, `relatedQueries` | all three |
| `regionResolution` | `COUNTRY`, `REGION`, `CITY`, `DMA`, or empty = Google's default | `""` |
| `language` | Language of region names and queries, e.g. `en-US`, `de`, `hu` | `en-US` |
| `timezoneOffsetMinutes` | Google's `tz`: minutes west of UTC (New York = 300) | `0` |
| `maxConcurrency` | Queries running in parallel (1-5) | `3` |
| `proxyConfiguration` | Proxy for retries: datacenter first, residential as last resort (custom groups replace both) | Apify Proxy |

You need at least one search term or one start URL.

#### Worked example — input

```json
{
  "searchTerms": ["bitcoin"],
  "geo": "US",
  "timeRange": "today 3-m"
}
```

#### Worked example — output (real data, shortened with "...")

```json
{
  "searchTerms": ["bitcoin"],
  "geo": "US",
  "timeRange": "today 3-m",
  "category": 0,
  "searchProperty": "web",
  "trendsUrl": "https://trends.google.com/trends/explore?q=bitcoin&date=today%203-m&geo=US",
  "summary": {
    "bitcoin": {
      "average": 53,
      "peakValue": 100,
      "peakDate": "2026-08-21T00:00:00Z",
      "latestValue": 53,
      "latestDate": "2026-10-12T00:00:00Z",
      "latestIsPartial": true
    }
  },
  "interestOverTime": [
    {"date": "2026-07-12T00:00:00Z", "timestamp": 1783814400, "formattedTime": "Jul 12, 2026", "values": {"bitcoin": 66}, "isPartial": false},
    {"date": "2026-07-13T00:00:00Z", "timestamp": 1783900800, "formattedTime": "Jul 13, 2026", "values": {"bitcoin": 77}, "isPartial": false},
    "...",
    {"date": "2026-10-12T00:00:00Z", "timestamp": 1791763200, "formattedTime": "Oct 12, 2026", "values": {"bitcoin": 53}, "isPartial": true}
  ],
  "averages": {"bitcoin": 53},
  "interestByRegion": [
    {"geoCode": "US-KS", "geoName": "Kansas", "values": {"bitcoin": 100}},
    {"geoCode": "US-NV", "geoName": "Nevada", "values": {"bitcoin": 77}},
    {"geoCode": "US-CA", "geoName": "California", "values": {"bitcoin": 74}},
    "..."
  ],
  "relatedQueries": {
    "bitcoin": {
      "top": [
        {"query": "bitcoin price", "value": 100, "link": "https://trends.google.com/trends/explore?q=bitcoin+price&date=today+3-m&geo=US"},
        {"query": "bitcoin stock", "value": 20, "link": "https://trends.google.com/trends/explore?q=bitcoin+stock&date=today+3-m&geo=US"},
        "..."
      ],
      "rising": [
        {"query": "british man recovers lost bitcoin", "value": 12250, "formattedValue": "Breakout", "link": "https://trends.google.com/trends/explore?q=british+man+recovers+lost+bitcoin&date=today+3-m&geo=US"},
        {"query": "suriname", "value": 1450, "formattedValue": "+1,450%", "link": "https://trends.google.com/trends/explore?q=suriname&date=today+3-m&geo=US"},
        "..."
      ]
    }
  },
  "hasData": true,
  "scrapedAt": "2026-10-12T13:56:02Z"
}
```

### Output

One dataset row per query (one term, or one group of compared terms):

| Field | Meaning |
|---|---|
| `searchTerms`, `geo`, `timeRange`, `category`, `searchProperty` | What was queried (`geo` is `WORLDWIDE` when empty) |
| `trendsUrl` | The same query on trends.google.com, for checking by eye |
| `summary` | Per term: `average`, `peakValue`, `peakDate`, `latestValue`, `latestDate`, `latestIsPartial` |
| `interestOverTime` | Points with `date` (UTC), `timestamp`, `formattedTime`, `values` per term, `isPartial` |
| `averages` | Average interest per term over the period |
| `interestByRegion` | Regions with data: `geoCode`, `geoName`, `values`; `coordinates` at city level |
| `relatedQueries` | Per term: `top` and `rising` lists (`formattedValue` like `+250%` or `Breakout`) |
| `hasData` | `false` means Google returned nothing (not charged) |
| `missingSections` | Sections that kept failing; the rest of the row is there (not charged) |
| `error` | Only present when the query, or a section of it, failed after all retries |

Rows come in input order, except queries that needed the end-of-run retry: those come last.

**How to read the numbers:** Google Trends values are relative, not search counts. 100 is the peak within the query, and everything else is scaled to it. Compared terms share one scale. Terms queried one by one each have their own scale, so `compareTerms: true` is what you want when the question is "which is searched more?". A `latestIsPartial: true` point covers a period that isn't over yet, so it usually reads low.

### Use it from code or an AI agent

Run it synchronously and get the rows in one HTTP call:

```bash
curl -X POST "https://api.apify.com/v2/acts/westerly_breaker~google-trends-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchTerms": ["chatgpt", "claude", "gemini"], "compareTerms": true, "timeRange": "today 3-m"}'
```

From Python (`pip install apify-client`), the pytrends equivalents are `interestOverTime` (pytrends `interest_over_time`), `interestByRegion` (`interest_by_region`) and `relatedQueries` (`related_queries`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("westerly_breaker/google-trends-scraper").call(
    run_input={"searchTerms": ["chatgpt", "claude"], "compareTerms": True, "timeRange": "today 3-m"}
)
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["searchTerms"], row["averages"])
```

The input fields are plain JSON with descriptive error messages. AI agents (Apify MCP server, LangChain, n8n, Make) can call the actor without a custom adapter, and if an input is wrong, the error message says what to fix.

### Pricing (pay-per-event)

**$0.002 per complete result**. That's one row: one term, or one group of up to 5 compared terms, with every section you asked for. Platform usage is included; there is no separate compute charge.

- 100 keywords, 97 complete, 2 with no search volume, 1 failed → 97 × $0.002 = **$0.194**
- Rows without data, failed rows and partial rows are delivered but **never charged**.
- If you set a maximum cost per run, the actor stops cleanly at that limit. Every row you paid for is delivered, and you are never billed for a row you didn't get.

### Error messages

Bad input stops the run before any request, with a message that names the field and the fix, for example:

- `'geo' 'USA' is not a valid Google Trends location code. Use '' (worldwide), an ISO 3166 country code like 'US' or 'HU', a region like 'US-CA', or a US metro (DMA) like 'US-CA-807'.`
- `Search term 'a,b' contains a comma. Put each term in its own list entry instead, and set 'compareTerms' to true if you want them compared.`

### Known limitations

- **Related topics are not available.** Google returns an empty topic list to clients that aren't a full browser, so this actor doesn't offer the section rather than return empty data. Related *queries* work normally.
- **Values are relative (0-100), not absolute search volumes.** This is how Google Trends works, not a limitation of the actor.
- **Low-volume terms can come back empty.** Google hides data below its privacy threshold. These rows have `hasData: false` and are free.
- **Per-term regional maps of compared terms are not fetched.** The compared map (all terms on one scale) is. Query the terms one by one if you need each term's own regional ranking.

### Data source

Public Google Trends data from trends.google.com, the same JSON the website loads. No login, no API key, no personal data.

# Actor input Schema

## `searchTerms` (type: `array`):

Keywords to look up, e.g. \["bitcoin", "ethereum"]. Each term becomes its own result row unless 'compareTerms' is on. Max 500 queries per run. Commas are not allowed inside a term (Google uses them to separate compared terms).

## `compareTerms` (type: `boolean`):

Off (default): every term is queried alone, so each term's values are scaled 0-100 against its own peak. On: terms are compared in groups of up to 5 (Google's limit), all on one shared 0-100 scale, and each group is one result row.

## `startUrls` (type: `array`):

Optional. Paste Google Trends explore URLs, e.g. https://trends.google.com/trends/explore?q=bitcoin,ethereum\&geo=US\&date=today%203-m. Each URL is one result row and keeps its own terms, location, time range, category and property; the settings below do not apply to it.

## `geo` (type: `string`):

Leave empty for worldwide. Otherwise an ISO 3166 country code ("US", "DE", "HU"), a region ("US-CA"), or a US metro area ("US-CA-807").

## `timeRange` (type: `string`):

Period to cover. Use one of the preset values, or a custom range written as 'YYYY-MM-DD YYYY-MM-DD' (e.g. '2024-01-01 2024-12-31'). Google picks the resolution: hourly or minute data for short ranges, daily for up to ~9 months, weekly for longer ranges, and monthly for 5+ years.

## `category` (type: `integer`):

Optional Google Trends category ID to narrow the meaning of a term (0 = all categories). Examples: 7 = Finance, 71 = Food & Drink, 5 = Computers & Electronics. You can copy the 'cat=' value from a Google Trends URL.

## `searchProperty` (type: `string`):

Which Google search to measure: web search (default), image search, news search, YouTube search, or Google Shopping.

## `include` (type: `array`):

Which sections to fetch. Fewer sections make the run faster but do not change the price. interestOverTime = time series + per-term summary; interestByRegion = breakdown by country/region; relatedQueries = top and rising related searches.

## `regionResolution` (type: `string`):

Level of the interest-by-region breakdown. Empty = Google's default (countries for worldwide, regions inside a country). CITY and DMA (US metro areas) only work inside a country; if Google rejects the level, the default is used and the log says so.

## `language` (type: `string`):

Language for region names and related queries, e.g. 'en-US', 'de', 'hu'.

## `timezoneOffsetMinutes` (type: `integer`):

Google's 'tz' parameter: minutes WEST of UTC, so New York (UTC-5) = 300 and Berlin (UTC+1) = -60. Only affects how hourly data is bucketed. Timestamps in the output are always UTC.

## `maxConcurrency` (type: `integer`):

How many queries run in parallel. 3 is a good balance; lower it if you see many retries in the log. Does not change the price.

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

Every request goes out directly first. If Google answers 'too many requests', retries use fresh Apify datacenter proxy sessions and, as a last resort, residential IPs. Choosing specific proxy groups here replaces both retry tiers; turning the proxy off makes every attempt direct.

## Actor input object example

```json
{
  "searchTerms": [
    "bitcoin",
    "ethereum"
  ],
  "compareTerms": false,
  "startUrls": [
    {
      "url": "https://trends.google.com/trends/explore?q=bitcoin,ethereum&geo=US&date=today%203-m"
    }
  ],
  "geo": "US",
  "timeRange": "today 3-m",
  "category": 0,
  "searchProperty": "web",
  "include": [
    "interestOverTime",
    "interestByRegion",
    "relatedQueries"
  ],
  "regionResolution": "",
  "language": "en-US",
  "timezoneOffsetMinutes": 0,
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per query (term or compared group): interest over time with a summary, interest by region, and related queries, stored in the run's default dataset.

# 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 = {
    "searchTerms": [
        "bitcoin",
        "ethereum"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("westerly_breaker/google-trends-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 = {
    "searchTerms": [
        "bitcoin",
        "ethereum",
    ],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("westerly_breaker/google-trends-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 '{
  "searchTerms": [
    "bitcoin",
    "ethereum"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call westerly_breaker/google-trends-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,westerly_breaker/google-trends-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/NyIbW6UbDicKppjQg/builds/sSC9id4GT3wtx3Tq8/openapi.json
