# Google Trends Scraper: Interest Over Time, Regions & Queries (`changefeeds/google-trends-scraper`) Actor

Google Trends interest over time, interest by region, related queries and daily trending searches for any term, region and time range. Handles Google's 429s by rotating residential sessions with a fresh cookie; a blocked or failed query is never charged.

- **URL**: https://apify.com/changefeeds/google-trends-scraper.md
- **Developed by:** [Changefeeds Tools](https://apify.com/changefeeds) (community)
- **Categories:** SEO tools, News
- **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.

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 Scraper: Interest Over Time, Regions & Related Queries

A Google Trends scraper and Google Trends API alternative in one actor. Give
it search terms and get back, per term and region, the same data the
trends.google.com Explore page shows: interest over time, interest by region
(countries, states or metros), and top and rising related queries. It can
also export today's trending searches for a country. It reads the JSON
endpoints the Trends page itself calls. No Google account, cookie or API key
needed.

Google Trends is quick to answer automated clients with HTTP 429 ("too many
requests" or "unusual traffic"). Reliability is what this actor is built
around:

- Requests go through Apify residential proxy by default.
- Google's first-contact 429 (the one that hands out an `NID` cookie) is
  recognised as a cookie handshake and retried at once with that cookie.
- A real block (a 429 "unusual traffic" page, or a redirect to Google's
  consent or sorry page) makes the actor wait, move to a new proxy session
  (a new IP) with a new cookie of its own, and try again. It does this up
  to 8 times, with backoff that grows between attempts.
- A query that is still blocked after that becomes a free `blocked` status
  row and the run moves on. You are never charged for it, and one bad
  query never fails the whole run.

It never tries to solve Google's CAPTCHA.

### Who it's for

SEO and content teams checking whether a topic is growing, market
researchers comparing brands or products across countries, analysts who
need Trends series in a spreadsheet or pipeline, and anyone whose pytrends
script keeps dying on 429s.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `searchTerms` | list of strings | — | Terms to look up. You can also paste `trends.google.com/trends/explore?q=...` URLs: a URL is fetched with its own terms, `geo`, `date`, `cat` and `gprop`, and the terms in one URL are compared together. Topic ids like `/m/0dl567` work too. |
| `compareTogether` | boolean | `false` | Off: each term is queried alone (scaled 0-100 against itself). On: terms are compared in groups of up to 5 (Google's limit), one row per group, scaled against each other. |
| `geo` | string | `""` | Empty = worldwide. A country (`US`), subregion (`US-CA`, `GB-ENG`) or metro (`US-CA-807`). Comma-separated codes give one row per term per region. |
| `timeRange` | select | `today 12-m` | `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` (2004 to present). A custom `YYYY-MM-DD YYYY-MM-DD` range is also accepted. |
| `category` | integer | `0` | A Trends category id (the `cat=` value in a Trends URL). 0 = all categories. |
| `property` | select | `""` | `""` web search, `images`, `news`, `froogle` (Google Shopping), `youtube`. |
| `includeRegions` | boolean | `true` | Add interest by region. One extra request per row. |
| `includeRelated` | boolean | `true` | Add top and rising related queries (and related topics for single terms). |
| `trendingNow` | boolean | `false` | Also output today's trending searches for each region in `geo` (US when `geo` is empty). Works with no search terms. |
| `proxy` | proxy | residential, US | Leave on. Turn off only when running from your own network. |

Example: three AI assistants compared in the US over 90 days.

```json
{ "searchTerms": ["chatgpt", "gemini", "claude"], "compareTogether": true, "geo": "US", "timeRange": "today 3-m" }
```

### Output

#### `trend` rows

One row per term (or per compared group) per region:

Built from Google's live responses for the prefill input (`chatgpt`, `US`,
`today 3-m`) on 2026-09-30, trimmed:

```jsonc
{
  "type": "trend",
  "input": "chatgpt",
  "terms": [
    "chatgpt"
  ],
  "term": "chatgpt",
  "compared": false,
  "geo": "US",
  "timeRange": "today 3-m",
  "resolvedTime": "2026-06-30 2026-09-30",
  "resolution": "DAY",
  "category": 0,
  "property": "",
  "timeline": [ // 93 daily points; last 3 shown
    {
      "time": 1790553600,
      "date": "2026-09-28T00:00:00.000Z",
      "formattedTime": "Sep 28, 2026",
      "value": 72,
      "values": [
        72
      ],
      "isPartial": false
    },
    {
      "time": 1790640000,
      "date": "2026-09-29T00:00:00.000Z",
      "formattedTime": "Sep 29, 2026",
      "value": 71,
      "values": [
        71
      ],
      "isPartial": false
    },
    {
      "time": 1790726400,
      "date": "2026-09-30T00:00:00.000Z",
      "formattedTime": "Sep 30, 2026",
      "value": 71,
      "values": [
        71
      ],
      "isPartial": true
    }
  ],
  "averages": null,
  "regions": [ // 51 (50 states + DC); top 3 shown
    {
      "geoCode": "US-CA",
      "geoName": "California",
      "value": 100,
      "values": [
        100
      ]
    },
    {
      "geoCode": "US-GA",
      "geoName": "Georgia",
      "value": 88,
      "values": [
        88
      ]
    },
    {
      "geoCode": "US-DC",
      "geoName": "District of Columbia",
      "value": 87,
      "values": [
        87
      ]
    }
  ],
  "regionResolution": "REGION",
  "relatedQueries": {
    "top": [ // 25; first 2 shown
      {
        "term": "chatgpt",
        "query": "what is chatgpt",
        "value": 100,
        "formattedValue": "100",
        "link": "https://trends.google.com/trends/explore?q=what+is+chatgpt&date=today+3-m&geo=US"
      },
      {
        "term": "chatgpt",
        "query": "chatgpt ai",
        "value": 98,
        "formattedValue": "98",
        "link": "https://trends.google.com/trends/explore?q=chatgpt+ai&date=today+3-m&geo=US"
      }
    ],
    "rising": [ // 4; first 2 shown
      {
        "term": "chatgpt",
        "query": "chatgpt astra",
        "value": 1800,
        "formattedValue": "+1,800%",
        "link": "https://trends.google.com/trends/explore?q=chatgpt+astra&date=today+3-m&geo=US"
      },
      {
        "term": "chatgpt",
        "query": "chatgpt student offer",
        "value": 350,
        "formattedValue": "+350%",
        "link": "https://trends.google.com/trends/explore?q=chatgpt+student+offer&date=today+3-m&geo=US"
      }
    ]
  },
  "relatedTopics": {
    "top": [],
    "rising": []
  },
  "exploreUrl": "https://trends.google.com/trends/explore?date=today+3-m&geo=US&q=chatgpt&hl=en-US",
  "notes": [
    "Google returned an empty related-topics list for \"chatgpt\"."
  ],
  "noData": false,
  "scraped_at": "2026-09-30T05:06:10.000Z"
}
```

- `timeline[].value` is the 0-100 interest for a single-term row. In a
  comparison row it is `null`: use `values`, one number per entry in
  `terms`, in the same order. A bucket Google marks as having no data is
  `null`, not `0`. `isPartial: true` marks the latest, still-incomplete bucket.
- `averages` is Google's per-term average for the range. Google only
  returns it for comparisons; single-term rows have `null`.
- `regions` in a single-term row are 0-100 relative to the top region. In a
  comparison row they are Google's "compared breakdown": each term's share
  of that region's searches among the compared terms, in percent. Regions
  with no data are left out. `regionResolution` tells you whether these are
  countries, regions (states) or DMA metros.
- `relatedQueries.top` values are 0-100 relative. `rising` values are the
  percent increase. Google labels increases over 5000 % as "Breakout", and
  that label is kept in `formattedValue`. Every item carries the `term` it
  belongs to, so comparison rows stay flat.
- `notes` says why a field is empty. For example, the term has too little
  search volume, or Google returned no related-topics widget.
- `exploreUrl` opens the same comparison on trends.google.com.

When Google has no data at all for a query (a very rare term, say), you
still get the row, with empty fields and a note. It has `noData: true` and
**is not charged**.

#### `trending` rows

From a live run (`trendingNow: true`, `geo: "US,GB"`, 2026-09-30: 10 rows per
region), trimmed:

```jsonc
{
  "type": "trending",
  "geo": "US",
  "title": "evan bouchard",
  "approxTraffic": "500+",
  "approxTrafficMin": 500,
  "pubDate": "2026-09-30T05:00:00.000Z",
  "picture": "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcRY6KarQldgLOIhenYpGzGBRIYWIYlvzsuSV99HI_Y-MKGguer3T3D_orYEPAo",
  "pictureSource": "NHL.com",
  "newsItems": [ // 3; first shown
    {
      "title": "Bouchard earns second hat trick",
      "snippet": null,
      "url": "https://www.nhl.com/video/van-edm-bouchard-has-a-hat-trick-against-the-canucks-6405939435112",
      "picture": "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcRY6KarQldgLOIhenYpGzGBRIYWIYlvzsuSV99HI_Y-MKGguer3T3D_orYEPAo",
      "source": "NHL.com"
    }
  ],
  "exploreUrl": "https://trends.google.com/trends/explore?date=now+1-d&geo=US&q=evan+bouchard&hl=en-US",
  "scraped_at": "2026-09-30T05:25:34.633Z"
}
```

#### `status` rows (free)

Problem inputs get a free `status` row instead of data:

```json
{ "type": "status", "input": "chatgpt", "mode": "trend", "geo": "US", "status": "blocked", "error": "HTTP 429: Google reported unusual traffic from this IP; still blocked after 8 proxy rotations", "checked_at": "2026-09-30T05:25:18.678Z" }
```

`status` is one of:

- `invalid`: the entry is not a usable term, Trends URL or geo code, or
  Google rejected the query with HTTP 400 (bad category or time range).
- `blocked`: Google kept answering 429 after every retry and rotation.
- `not_found`: the trending feed for a region had no items.
- `error`: any other HTTP or network failure.

The key-value store record `OUTPUT` holds a run summary: rows by type,
charged counts per event, `stopped_reason`, `truncated_inputs` (what was
skipped because the budget ran out), HTTP requests, retries and proxy
rotations. Every run writes at least one dataset row.

### Pricing

Pay per event. **Proxy traffic is included in the price.**

- **$0.004 per trend row** (`trend-returned`), which is $4 per 1,000 rows.
  One row covers a term (or a group of up to 5 compared terms) in one
  region, with its timeline, regions and related queries.
- **$0.001 per trending row** (`trending-returned`).

You are never charged for status rows (blocked, invalid, error), for
no-data trend rows, or for the `run_info` row. If you set a maximum total
charge, the actor checks the remaining budget before every query. It never
fetches a row it could not bill, stops cleanly when the budget is used up,
and records `"max_total_charge_reached"` in `OUTPUT.stopped_reason`.

### Limits, stated plainly

- **Google's numbers are relative, not search counts.** Every value is
  scaled 0-100 within its own query. The same term queried alone and inside
  a comparison gives different numbers, and so do two runs with different
  time ranges. Compare terms in one row (`compareTogether`) when you need
  them on one scale.
- **Google's own sampling.** Trends data is a sample. Re-running the same
  query can shift values by a point or two, and very small terms flicker
  between some data and none.
- **Related topics usually come back empty.** In every live test so far
  (September 2026), Google returned an empty related-topics list to this
  client, for big terms like "chatgpt" and "tesla" too. The actor passes on
  whatever Google returns and says so in `notes`, but do not count on
  topics. Google's comparison view has no related topics at all.
- **Trending searches are Google's RSS feed.** That is about 10 current
  trending searches per region, with approximate traffic buckets like
  `"500+"` and a few news links each. It is not the full trending list on
  the trends.google.com website and not historical.
- **Blocking can still win.** Rotation and backoff are the whole point of
  this actor, but they cannot guarantee that Google answers. A query that is still blocked after
  8 new sessions is reported as `blocked` (free), not retried forever. Runs
  without a proxy (your own IP) only back off and retry 3 times.
- **It stays polite.** It runs one query at a time with at least 0.5 s
  between requests. 5xx errors are retried with backoff, and Retry-After is
  honoured up to 60 s.

### FAQ

**Do I need a Google account or API key?** No.

**Why are my numbers different from the website?** Check that you use the
same region, time range, category and search type (the `exploreUrl` on each
row opens exactly what was queried), and that you compared the same terms
together. Google also re-samples, so small differences are normal.

**Can I get more than 5 terms on one scale?** Google compares at most 5
terms at a time. A common workaround is to include one anchor term in every
group and rescale the groups against it. The actor does not do that for you.

**Is this the official Google Trends API?** No. It reads the same public
endpoints the trends.google.com page uses.

### Troubleshooting: pytrends 429 TooManyRequestsError

If you're using [`pytrends`](https://github.com/GeneralMills/pytrends) and hitting `ResponseError: The request failed: Google returned a response with code 429` (TooManyRequestsError), you're experiencing one of the library's longest-running issues. It happens whether you're making one request or a thousand, and it has shown up for years.

**Why it happens**

`pytrends` is an unofficial wrapper: there is no public Google Trends API. The library reverse-engineers the requests the `trends.google.com` website itself makes, which means every call is an anonymous, cookie-less request against an interface Google didn't design for programmatic use. Google rate-limits that pattern aggressively — by IP and seemingly by request velocity and shape.

**Free fixes**

- **Add retries with backoff at construction time:**

```python
from pytrends.request import TrendReq

pytrends = TrendReq(
    hl='en-US',
    tz=360,
    retries=2,
    backoff_factor=0.5,
)
```

- **Space out requests** and batch fewer keywords per call (Google Trends compares up to 5 terms at a time).
- **Route through your own proxy list.** `TrendReq` accepts a `proxies` parameter directly; use rotating residential proxies to avoid quick blocks on static IPs.
- **Add jitter and cache aggressively.** If pulling the same terms repeatedly, cache results and only re-fetch what's stale. Every avoided request is one less chance to get rate-limited.

None of this guarantees success — Google's limiter looks at more than raw request count. Re-test whenever Google's backend changes (which it does without notice, as the README itself warns).

**This actor handles it**

This actor uses residential proxy with per-session cookie handling and rotation, retrying through fresh IPs up to 8 times with backoff. Blocked or no-data queries are not charged.

### Local development

```bash
pnpm --filter @mmnm/gtrends test        # unit tests, no network
pnpm --filter @mmnm/gtrends build
```

`node src/main.ts` runs the actor locally with Apify's local storage
(`./storage`). `ACTOR_TEST_PAY_PER_EVENT=true ACTOR_MAX_TOTAL_CHARGE_USD=1`
exercises the charging path.

# Actor input Schema

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

Terms to look up, e.g. "chatgpt". You can also paste trends.google.com/trends/explore?q=... URLs: a URL is fetched with its own terms, geo, date, category and property (terms in one URL are compared together). Topic ids such as /m/0dl567 work too.

## `compareTogether` (type: `boolean`):

Off: each term is queried alone, so its values are scaled 0-100 against itself. On: terms are compared in groups of up to 5 (Google's limit), one row per group, with values scaled against each other.

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

Leave empty for worldwide. A country code (US, GB, DE), a subregion (US-CA, GB-ENG) or a metro (US-CA-807). Several comma-separated codes give one row per term per region.

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

The window of the interest-over-time series (and the period regions and related queries cover).

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

Google Trends category id (the cat= value in a Trends URL). 0 = all categories.

## `property` (type: `string`):

Which Google search the interest comes from.

## `includeRegions` (type: `boolean`):

Add the interest-by-region breakdown (countries worldwide, or subregions / metros inside a country). One extra request per row.

## `includeRelated` (type: `boolean`):

Add Google's top and rising related queries (and related topics for single terms). One or two extra requests per term.

## `trendingNow` (type: `boolean`):

Add today's trending searches (Google's trending RSS feed, about 10 per region) for each region in `geo`; US when `geo` is empty. Works with no search terms too.

## `proxy` (type: `object`):

Google Trends answers cloud-server IPs with HTTP 429, so requests go through Apify residential proxy by default, and a blocked session is swapped for a new one. Proxy traffic is included in the price. Turn it off only when running the Actor from your own network.

## Actor input object example

```json
{
  "searchTerms": [
    "chatgpt"
  ],
  "compareTogether": false,
  "geo": "US",
  "timeRange": "today 3-m",
  "category": 0,
  "property": "",
  "includeRegions": true,
  "includeRelated": true,
  "trendingNow": false,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "searchTerms": [
        "chatgpt"
    ],
    "geo": "US",
    "timeRange": "today 3-m",
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("changefeeds/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": ["chatgpt"],
    "geo": "US",
    "timeRange": "today 3-m",
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("changefeeds/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": [
    "chatgpt"
  ],
  "geo": "US",
  "timeRange": "today 3-m",
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call changefeeds/google-trends-scraper --silent --output-dataset

```

## MCP server setup

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