# Google Trends Scraper - Interest Over Time, Region & Queries (`neverempty/google-trends-scraper`) Actor

For SEO teams, market researchers and AI agents: Google Trends for up to 5 terms compared on one 0-100 scale - interest over time, by region, and top and rising related queries. One term took 3.8-6.7 s, 30 terms 54 s, no browser. Watch mode returns only new data points; unanswerable terms are free.

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

## Pricing

from $2.80 / 1,000 search term results

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/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, Region & Queries

Get Google Trends data for your keywords **in seconds**: interest over time, interest by region, and top and rising related queries, one clean row per search term. Compare up to 5 terms on the same 0-100 scale, exactly like the Google Trends comparison view. Built for SEO and content teams, market researchers, dashboards and AI agents that call Google Trends from code or on a schedule.

- **Fast.** No browser. A one-term run took 3.8-6.7 s of run time in production (256 MB); 30 terms took 54 s.
- **Terms Google cannot answer are free, with the reason.** A term with too little search volume, a request Google rejects (for example an unknown country code) or a request Google refuses comes back as a free row that says why. If Google refused every term or none could be read (blocked, unreadable or timed out), the run is not charged and ends with an error so your schedule sees it. A run whose terms were only rejected (HTTP 400) or had no data ends normally with those free rows and is not charged either.
- **Retries on Google's rate limit.** Requests go out directly first; when Google answers HTTP 429 or 403, the request is sent again from another IP address through Apify's US residential proxy (up to 6 attempts per request). Check pages and CAPTCHAs are never solved.
- **Compare terms.** Turn on `compareTerms` and the terms share one scale (100 = the highest point of any term in the group), so the values of different terms can be compared directly instead of each term peaking at its own 100.
- **Watch mode for schedules.** With `onlyChanges`, later runs return a term only when Google Trends has a new complete data point since the last run; everything else is one free `no-change` row.
- **Extra columns Google does not give you directly:** latest value and whether it is a partial (still counting) period, last complete value, change from the previous complete point, average, peak value and date, top region.

Unofficial. Uses the same public, logged-out data the Google Trends website loads. No login and no Google account needed.

### What you get

One row per search term. Example (production run 0x7mZVKqy5460YfyQ, 2026-09-24, `apify` in the US, past 12 months, lists shortened to their first entries):

```json
{
  "status": "ok",
  "changeType": null,
  "searchTerm": "apify",
  "comparedWith": [],
  "geo": "US",
  "timeRange": "today 12-m",
  "timeRangeResolved": "2025-09-24 2026-09-24",
  "resolution": "WEEK",
  "trendsUrl": "https://trends.google.com/trends/explore?date=today%2012-m&geo=US&q=apify&hl=en-US",
  "latestValue": 32,
  "latestDate": "2026-09-20T00:00:00.000Z",
  "latestIsPartial": true,
  "lastCompleteValue": 34,
  "lastCompleteDate": "2026-09-13T00:00:00.000Z",
  "previousCompleteValue": 35,
  "changeFromPrevious": -1,
  "averageValue": 55.9,
  "peakValue": 100,
  "peakDate": "2026-06-07T00:00:00.000Z",
  "topRegion": "Wyoming",
  "topRegionCode": "US-WY",
  "topRegionValue": 100,
  "interestOverTime": [
    { "date": "2025-09-21T00:00:00.000Z", "label": "Sep 21 – 27, 2025", "value": 35, "isPartial": false }
  ],
  "interestByRegion": [
    { "geoCode": "US-WY", "geoName": "Wyoming", "value": 100 },
    { "geoCode": "US-DC", "geoName": "District of Columbia", "value": 35 }
  ],
  "relatedQueriesTop": [
    { "query": "apify scraper", "value": 100 },
    { "query": "api", "value": 59 }
  ],
  "relatedQueriesRising": [
    { "query": "tavily", "growth": "Breakout", "value": 24350 },
    { "query": "composio", "growth": "Breakout", "value": 8750 }
  ],
  "checkedAt": "2026-09-24T09:31:11.257Z"
}
```

| Column | Meaning |
|---|---|
| `status` | `ok` for a result row. Other values are free rows (below) |
| `changeType` | Watch mode only: `first-check` or `changed`. Null otherwise |
| `searchTerm`, `comparedWith` | The term, and the other terms of its comparison group (empty when not comparing) |
| `geo`, `timeRange`, `timeRangeResolved`, `resolution` | What was asked, the exact dates Google used, and the point size (`MINUTE`, `EIGHT_MINUTE`, `HOUR`, `DAY`, `WEEK`, `MONTH`) |
| `category`, `searchType` | Category ID and search type (web, images, news, youtube, shopping) |
| `trendsUrl` | The same query on the Google Trends website |
| `latestValue`, `latestDate`, `latestIsPartial` | The newest point. Google marks the current, still-counting period as partial |
| `lastCompleteValue`, `lastCompleteDate` | The newest point that is not partial |
| `previousCompleteValue`, `changeFromPrevious` | The complete point before it, and the difference |
| `averageValue`, `peakValue`, `peakDate` | Over all points with data in the range |
| `pointsCount`, `pointsWithData` | Number of points, and how many have data (Google leaves a point empty when volume is too low) |
| `interestOverTime` | Every point: `date` (ISO, UTC), `label` (Google's label), `value` (0-100, null = no data), `isPartial` |
| `interestByRegion`, `topRegion`, `topRegionCode`, `topRegionValue`, `regionResolution`, `regionsWithData` | Regions with data, highest first: countries for worldwide, states or provinces for a country, metro areas for a US state |
| `relatedQueriesTop`, `topQueriesCount` | Google's top related queries (`value` 0-100) |
| `relatedQueriesRising`, `risingQueriesCount` | Google's rising related queries with `growth` as Google shows it (`+250%`, `Breakout`) |
| `changes`, `isFirstCheck`, `previousCheckAt` | Watch mode: `newCompletePoints` since the last run, plus `newRisingQueries` and `newTopQueries` not seen in earlier runs of this watch |
| `note` | Free rows only: why nothing (or not everything) was returned |
| `checkedAt` | Time of the check |

Values are Google Trends' relative interest (0-100), not search volumes. Related topics are not included: on 2026-09-24 Google's endpoint for them returned an empty list for every term we tried outside the website.

#### Free rows (not charged)

| `status` | When |
|---|---|
| `no-data` | Google Trends returned no data points for this term with these settings (too little search volume) |
| `no-change` | Watch mode: no new complete data point since the last run |
| `partial` | The interest over time was read, but interest by region or related queries could not be read. The row is returned with those columns null, free of charge, and watch mode does not remember it |
| `rejected` | Google answered HTTP 400: usually an unknown geo code, a date range Google does not accept, or a category number that does not exist |
| `blocked` | Google refused every attempt, also from other IP addresses |
| `unreadable` | Google's answer could not be read, or Google showed a check page (never solved, not retried) |
| `bad-input` | The input could not be used; nothing was requested |
| `timed-out` | The run was about to reach its timeout, so it stopped asking Google Trends and returned the rows it already had. Run fewer terms per run or raise the timeout |
| `budget-reached` | The run hit the maximum total charge you set. Terms not returned are not charged, and in watch mode not remembered, so the next run returns them |

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `searchTerms` | array of strings | `coffee` | Keywords, up to 100 per run. Duplicates (ignoring case) are looked up once |
| `compareTerms` | boolean | false | Compare the terms together in groups of up to 5 on one scale |
| `timeRange` | string | `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` |
| `customTimeRange` | string | (none) | `2024-01-01 2024-12-31` style dates; overrides `timeRange` |
| `geo` | string | worldwide | Country (`US`, `GB`, `JP`) or region (`US-CA`, `GB-ENG`) code |
| `category` | integer | 0 | Google Trends category ID (the `cat=` number in a Google Trends URL); 0 = all |
| `searchType` | string | `web` | `web`, `images`, `news`, `youtube`, `shopping` |
| `includeInterestByRegion` | boolean | true | Add interest by region |
| `includeRelatedQueries` | boolean | true | Add top and rising related queries |
| `onlyChanges` | boolean | false | Watch mode (below) |
| `watchName` | string | (none) | Separate memories for two schedules that watch the same terms. A watch remembers each term together with its comparison group, geo, time range, category and search type: changing any of them starts a new first check |
| `resetMonitoringState` | boolean | false | Forget this watch's memory at the start of the run |

Example:

```json
{ "searchTerms": ["iphone", "samsung galaxy", "google pixel"], "compareTerms": true, "geo": "US", "timeRange": "today 12-m" }
```

### How close is it to the Google Trends website?

We opened the same 4 queries (bitcoin, iphone, taylor swift, python; US; past 12 months) in a browser on 2026-09-24 and compared them with a production run of this Actor:

- **Interest over time:** the peak week was the same week in all 4, the shape correlated at 0.990-0.998, and single weeks differed by 0.8-2.1 points on average (at most 7). Google serves requests that do not come from its website from a separate sample, so a few points of difference are expected; the same request repeated from different IP addresses returned identical numbers (53 of 53 weeks).
- **Top region:** the region this Actor reports first appeared on the page in all 4.
- **Rising related queries:** Google samples them, so they change between requests (0-3 of the first 5 were also on the page). Use them as ideas, not as a stable ranking.

### Watch mode

1. Run once with `onlyChanges: true` and your terms: every term comes back with `changeType: "first-check"`.
2. Create a **Schedule** with the same input (daily for `today 12-m` or `today 1-m`, hourly for `now 7-d`).
3. Later runs return a term only when Google Trends has a **new complete data point** since the last run (a new week for the 12-month range, a new day for 30 days, a new hour for 7 days). The row lists the new points in `changes.newCompletePoints`, and related queries this watch has not seen before in `changes.newRisingQueries` and `changes.newTopQueries`.

Related queries and regions alone never count as a change: Google samples them, and in production two requests 11 seconds apart returned the same interest over time but four different rising queries. A term without a new point comes back as one free `no-change` row.

### Pricing

Pay per event:

- **Run start** - charged once per run, only when at least one term was answered (a result row or a watch-mode `no-change`). Not charged when every term is `no-data`, `partial`, `rejected`, `blocked`, `unreadable` or `timed-out`, or when the run's maximum total charge has no room for the start fee plus one result row (then nothing is requested at all).
- **Search term result** - one per `ok` row. Free rows are never charged.

The exact prices are shown on the Pricing tab.

### Measured in production (2026-09-24, build 0.1, 256 MB)

| Input | Result | Run time |
|---|---|---|
| `{}` (coffee, worldwide, 12 months) | 1 row, 53 weekly points | 6.7 s |
| `apify`, US | 1 row | 4.0 s |
| `iphone`, `samsung galaxy`, `google pixel`, compared, US | 3 rows on one scale | 3.8 s |
| `taylor swift`, `travis kelce`, `now 7-d`, watch mode | 2 `first-check` rows | 6.4 s |
| 30 popular terms, one run | 30 rows, 121 requests, no retry needed | 54 s |
| `coffee` with geo `ZZ` | 1 free `rejected` row | 2.0 s |

From inside Apify, direct requests, datacenter proxy and US residential proxy each returned the full data 6 of 6 times (2026-09-24).

### Support

Found a term that returns something different from the Google Trends website, or need another field? Open an issue on the Issues tab with the input you used.

# Actor input Schema

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

Keywords to look up on Google Trends, one per line (up to 100 per run). Each term becomes one result row with interest over time, interest by region and related queries. Empty = coffee.

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

On = the terms are compared together in groups of up to 5, exactly like typing them into one Google Trends comparison, so their values share the same 0-100 scale (100 = the highest point of any term in the group). Off (or empty) = each term is looked up on its own, 100 = its own peak.

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

The Google Trends time range. Empty = Past 12 months. Ignored when Custom time range is set.

## `customTimeRange` (type: `string`):

Optional. A start and end date separated by a space, for example 2024-01-01 2024-12-31. Overrides Time range.

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

Optional. A two-letter country code (US, GB, DE, JP) or a region code (US-CA, GB-ENG). Empty = worldwide. Interest by region is by country for worldwide, by state or province for a country, and by metro area for a US state.

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

Optional. The Google Trends category number, the same number as cat= in a Google Trends URL. Empty or 0 = all categories.

## `searchType` (type: `string`):

Which Google search the popularity is measured on. Empty = Web Search.

## `includeInterestByRegion` (type: `boolean`):

On (or empty) = add the regions where the term is most popular (interestByRegion, topRegion). Off = skip it for a faster run.

## `includeRelatedQueries` (type: `boolean`):

On (or empty) = add related queries: top (relatedQueriesTop) and rising (relatedQueriesRising, with the growth Google shows, such as +250% or Breakout). Off = skip them for a faster run.

## `onlyChanges` (type: `boolean`):

On = watch mode for scheduled runs. The first run returns every term (changeType first-check). Later runs return a term only when Google Trends has a new complete data point since the last run (a new week for Past 12 months, a new day for Past 30 days, a new hour for Past 7 days), with the new points in the changes column (changeType changed). Related queries not seen before are listed there too, but do not count as a change: Google samples related queries, so they shift between requests. Terms without a change come back as a free no-change row. Off (or empty) = always return every term.

## `watchName` (type: `string`):

Optional, used with Only return terms that changed. Runs with the same watch name share what has already been returned. Give different names to two schedules that watch the same terms for different purposes. Letters, digits, dot, dash and underscore.

## `resetMonitoringState` (type: `boolean`):

Forget what this watch remembered at the start of this run, so every term is a first check again. Turn it off again for scheduled runs.

## Actor input object example

```json
{
  "searchTerms": [
    "coffee"
  ]
}
```

# Actor output Schema

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

One row per search term: interest over time (every point, with partial points marked), the latest, last complete and peak values, change from the previous point, average, interest by region, top and rising related queries, and in watch mode what changed since the last run. A term with too little search volume, a request Google rejected or could not answer, or a run that hit its maximum charge comes back as a free row that says why.

# 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": [
        "coffee"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/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": ["coffee"] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/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": [
    "coffee"
  ]
}' |
apify call neverempty/google-trends-scraper --silent --output-dataset

```

## MCP server setup

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