# Google Trends Scraper (`cairnware/google-trends-scraper`) Actor

Get Google Trends interest over time, interest by region and related queries for any list of keywords. Compare more than 5 terms on one 0-100 scale with an anchor term. Smart retries on rate limits; failed terms are never charged.

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

## Pricing

$4.00 / 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.

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

Get **Google Trends** data for any list of keywords: **interest over time**, **interest by region**, **related queries** (top and rising) and **related topics**. Every search term becomes **one clean row** in your dataset, ready for JSON, CSV, Excel, Google Sheets or the Apify API.

Built for runs you do not have to babysit:

- **Smart retries.** Google Trends rate-limits busy IP addresses (HTTP 429). Each failed request is retried with exponential backoff and jitter, on a **fresh residential IP** every time.
- **No silent failures.** Every term you enter appears in the output. If a term still fails after all retries, you get a row with `status: "error"` and the reason, and **you are not charged for it**. The run itself still finishes, with a summary such as *"9 of 10 results delivered; 1 failed after retries and was not charged"*.
- **Clean output.** One row per term (or per comparison group), with dates in ISO format and a link to the same chart on trends.google.com.
- **Compare more than 5 terms.** Give an **anchor term** and every term lands on **one 0-100 scale**, even 50 of them (see below). The combined row is free.
- **Respects your budget.** The Actor stops scraping when your *maximum cost per run* is reached and lists the remaining terms as free, unscraped rows.

### What data you get

| Section | What it contains | Default |
|---|---|---|
| Interest over time | Relative interest 0-100 per week / day / hour, with a flag for the current, incomplete period | On |
| Interest by region | Relative interest 0-100 per country, state/province, US metro (DMA) or city | On |
| Related queries | Top and rising searches related to the term ("Breakout" included) | On |
| Related topics | Top and rising topics (entities) related to the term. Google currently returns these for few searches (see Limitations) | Off |

### Use cases

- **SEO and content planning:** spot rising queries before they peak and plan content around seasonality.
- **E-commerce and inventory:** see when demand for a product starts climbing each year, and where.
- **Market and brand research:** compare 5 brands or products on one scale by region, or any number of them over time with an anchor term.
- **Dashboards and data pipelines:** schedule daily or weekly runs and send results to Google Sheets, BigQuery, Make, n8n or Zapier with Apify integrations.
- **Research and journalism:** pull consistent time series of public interest in a topic.

### How to use

1. Enter one or more **search terms**, or paste the link of a chart you made on trends.google.com (Explore page): its terms, location, time range, category and search type are taken over.
2. Pick a **location** (for example `US`, `GB`, `US-CA`, or leave empty for worldwide) and a **time range**.
3. Click **Start**. Download the results as JSON, CSV, Excel or HTML, or read them through the Apify API.

Want the terms on one shared scale, like typing `coffee, tea` on trends.google.com? Turn on **Compare terms together** (up to 5 terms per comparison).

#### Compare more than 5 terms on one scale (anchor term)

Google Trends compares at most 5 terms at once, and each chart is scaled so its own peak is 100. So `tea` in one chart and `milk` in another can't be compared directly. Fill in **Anchor term** with one term that is about as popular as yours (for drinks, `coffee`):

1. The anchor is added to every comparison group of 4 terms.
2. Each group is rescaled by how big the anchor is in it, so all groups share the first group's scale.
3. The result is scaled so the overall peak is 100 again.

You get the usual one row per group, plus **one extra row, `combined: ...`, with the interest over time of every term on one 0-100 scale** (one decimal). That row is free. Ratios between terms are kept exactly: in our test run (US, past 90 days, `tea, juice, soda, water, milk`, anchor `coffee`) milk was 0.438 times coffee in its own chart and 0.438 times coffee in the combined row.

Choose the anchor well. If it is tiny next to a group's terms (it peaks below 10 in that group), Google's whole-number rounding makes the rescaling coarse, and the combined row gets a "Low precision" warning. If it is 0 in a group, that group is left out with a warning. The combined row covers interest over time only: regional values in comparisons are shares of each region, so they can't be rescaled this way.

```json
{ "searchTerms": ["tea", "juice", "soda", "water", "milk"], "anchorTerm": "coffee", "geo": "US", "timeRange": "today 3-m" }
```

### Input example

```json
{
    "searchTerms": ["coffee", "matcha", "cold brew"],
    "geo": "US",
    "timeRange": "today 12-m",
    "category": 0,
    "property": "",
    "compareTogether": false,
    "includeInterestOverTime": true,
    "includeInterestByRegion": true,
    "regionResolution": "AUTO",
    "includeRelatedQueries": true,
    "includeRelatedTopics": false,
    "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

| Field | Notes |
|---|---|
| `searchTerms` | Required. Duplicates and empty lines are removed. |
| `geo` | 2-letter country (`US`), region (`US-CA`, `GB-ENG`) or US metro (`US-CA-807`). Empty = worldwide. |
| `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` (2004 to now). |
| `customTimeRange` | Optional exact period, `"2024-01-01 2024-12-31"`. Overrides `timeRange`. |
| `category` | Google Trends category ID, `0` = all. Useful to separate meanings, for example `apple` in Food & Drink (`71`). |
| `property` | `""` web search, `news`, `images`, `youtube` or `froogle` (Google Shopping). |
| `compareTogether` | Put terms on one chart (max 5 per group; longer lists are split into groups of 5). |
| `anchorTerm` | Optional. Compare any number of terms on one scale: groups of 4 + the anchor, plus a free `combined` row (see above). Turns on `compareTogether`. |
| `regionResolution` | `AUTO`, `COUNTRY`, `REGION`, `DMA` (US only) or `CITY`. |
| `maxRetries` | Retries per request before a term is marked `error` (default 8). |
| `maxConcurrency` | Terms scraped in parallel (default 3). |

### Output example

Real output for `coffee`, `US`, past 12 months, collected on 2026-10-03 (arrays shortened; the full row has 53 weekly points, 51 regions and 25 top + 25 rising queries):

```json
{
    "searchTerm": "coffee",
    "searchTerms": [
        "coffee"
    ],
    "status": "ok",
    "geo": "US",
    "timeRange": "today 12-m",
    "category": 0,
    "property": "",
    "interestOverTime": [
        {
            "date": "2025-09-28",
            "timestamp": 1759017600,
            "value": 64,
            "isPartial": false,
            "formattedTime": "Sep 28 – Oct 4, 2025"
        },
        {
            "date": "2025-10-05",
            "timestamp": 1759622400,
            "value": 57,
            "isPartial": false,
            "formattedTime": "Oct 5 – 11, 2025"
        },
        {
            "date": "2026-09-27",
            "timestamp": 1790467200,
            "value": 63,
            "isPartial": true,
            "formattedTime": "Sep 27 – Oct 3, 2026"
        }
    ],
    "interestByRegion": [
        {
            "geoCode": "US-WY",
            "geoName": "Wyoming",
            "value": 100
        },
        {
            "geoCode": "US-HI",
            "geoName": "Hawaii",
            "value": 46
        },
        {
            "geoCode": "US-DC",
            "geoName": "District of Columbia",
            "value": 41
        }
    ],
    "regionResolution": "REGION",
    "relatedQueries": {
        "top": [
            {
                "query": "coffee near me",
                "value": 100,
                "link": "https://trends.google.com/trends/explore?q=coffee+near+me&date=today+12-m&geo=US"
            },
            {
                "query": "coffee table",
                "value": 84,
                "link": "https://trends.google.com/trends/explore?q=coffee+table&date=today+12-m&geo=US"
            }
        ],
        "rising": [
            {
                "query": "pet care tips",
                "value": 5300,
                "formattedValue": "Breakout",
                "link": "https://trends.google.com/trends/explore?q=pet+care+tips&date=today+12-m&geo=US"
            },
            {
                "query": "how to remove coffee stain from carpet",
                "value": 5000,
                "formattedValue": "+5,000%",
                "link": "https://trends.google.com/trends/explore?q=how+to+remove+coffee+stain+from+carpet&date=today+12-m&geo=US"
            }
        ]
    },
    "relatedTopics": null,
    "warnings": [],
    "errorMessage": null,
    "trendsUrl": "https://trends.google.com/trends/explore?date=today%2012-m&q=coffee&geo=US",
    "scrapedAt": "2026-10-03T08:06:41Z"
}
```

For comparison rows (`compareTogether: true`), `searchTerm` is `"coffee vs tea"`, each time point and region has `values` (term → number) instead of `value`, and each related query or topic has a `searchTerm` field. Real example: `{"date": "2025-09-28", "values": {"coffee": 64, "tea": 30}, ...}`. In comparison rows, region values are each term's share of that region, so they add up to 100 (for example Wyoming: coffee 70, tea 30).

#### The `status` field

| Status | Meaning | Charged? |
|---|---|---|
| `ok` | Data delivered. | Yes |
| `no_data` | Google Trends has too little search volume for this term, location and period (it shows "Hmm, your search doesn't have enough data" on the website). | Yes |
| `error` | The term failed after all retries, or was not scraped because your maximum cost per run was reached. `errorMessage` says which. | **No** |

A section is `null` when you did not request it, and an empty list when Google returned nothing for it.

### Pricing

**$4 per 1,000 search terms** ($0.004 per result). **No start fee. Failed terms are free.**

- You pay once per delivered row with status `ok` or `no_data`.
- A comparison group of up to 5 terms is one row, so it costs the same as one term.
- With an anchor term, 20 terms = 5 groups = 5 paid rows ($0.02), and the `combined` row is free.
- Residential proxy traffic is included in the price.
- Set a **maximum cost per run** in the run options; the Actor never goes over it.

Example: 250 keywords every week ≈ 1,000 results a month ≈ $4.

### FAQ

**Why do I see "HTTP 429" in the log?**
Google Trends limits how many requests one IP address can make. A 429 means "too many requests from this IP". The Actor waits a short, randomised time and retries on a new residential IP, up to `maxRetries` times. Seeing some 429 retries in the log is normal; you are only affected if a term still fails after all retries, in which case it is returned as a free `error` row.

**Why is a residential proxy the default?**
Google rate-limits datacenter IP ranges much more aggressively. Residential IPs, with a new IP on every retry, are what keeps the failure rate low. You can switch to a datacenter proxy or your own proxy URLs in *Proxy configuration*, but expect more retries and errors.

**Are the numbers search volumes?**
No. Like the Google Trends website, values are **relative, from 0 to 100**. 100 is the peak popularity of the term within the chosen location and period; 50 means half as popular. Values from different runs are not on the same scale. To compare terms on one scale, use **Compare terms together**.

**How do I compare more than 5 keywords in Google Trends?**
Use an **anchor term**: one keyword that appears in every group of 5 links the groups together. Fill in *Anchor term* and the Actor does the grouping and the math, and returns one `combined` row with all your terms on one 0-100 scale. By hand you would download one CSV per group from trends.google.com, divide each group by its anchor column and multiply by the anchor from the first group. For a one-off, our free [Google Trends comparison page](https://cairnware.com/compare-google-trends) does that math in your browser for CSVs you already downloaded.

**Why can the numbers differ slightly from the website or from an earlier run?**
Google Trends computes values from a sample of searches, so values can shift by a few points between requests or days. The most recent period is often incomplete; those points have `isPartial: true`.

**What time zone are the dates in?**
UTC. Daily and weekly data use `YYYY-MM-DD` (the start of the period); hourly and minute data use full timestamps like `2026-10-03T14:00:00Z`. Each point also has a Unix `timestamp` and Google's own `formattedTime` label.

**Can I schedule it or call it from code?**
Yes. Use Apify Schedules for recurring runs, and the API tab of this Actor for ready-made examples in Python, JavaScript and cURL. Integrations exist for Google Sheets, Make, n8n, Zapier and webhooks.

### Limitations

- Values are relative (0-100), not absolute search volumes. Google does not publish absolute numbers in Trends.
- Google compares at most 5 terms at once. Values are only comparable within one comparison group, except in the `combined` row of an anchor-term run (interest over time only).
- Very rare terms return `no_data`; related queries are often empty for small terms or small regions.
- Related topics: in our October 2026 tests Google returned an empty related-topics list even for "coffee" in the US, and does not offer them at all in comparisons. The option is kept for searches where Google still provides them; when it does not, the row gets a warning and the result costs the same as without topics.
- The time granularity is chosen by Google: typically minutes for the past hours, hourly for 7 days, daily up to 90 days, weekly up to 5 years, monthly for 2004 to now.
- "Trending now" / daily trending searches are not included; this Actor looks up the terms you provide.
- The Actor uses the same public endpoints as the Google Trends website. If Google changes them, results may break until the Actor is updated. Please report it and we will fix it.
- It reads only aggregated, anonymous statistics that Google Trends shows publicly; it does not collect personal data. You are responsible for using the data in line with Google's terms and the laws that apply to you.

### Support

Questions, bugs or feature requests: open an issue on the **Issues** tab or email **hello@cairnware.com**. Please include the run ID if something went wrong. Support replies may be AI-assisted.

Made by [Cairnware](https://cairnware.com), small tools that keep working.

# Actor input Schema

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

Keywords to look up on Google Trends, one per line. Each term is scraped separately (one result row each) unless you turn on "Compare terms together". You can also paste a chart link from trends.google.com/trends/explore: its terms become one comparison and its location, time range, category and search type are used for the whole run.

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

Put the terms on one Google Trends chart (like typing "coffee, tea" on trends.google.com) so their values are relative to each other. Google allows up to 5 terms per comparison; longer lists are split into groups of 5 and each group becomes one result row.

## `anchorTerm` (type: `string`):

Optional. Google compares at most 5 terms at once. Enter one term that is about as popular as your terms (for example "coffee" when you compare drinks): it is added to every comparison group of 4, and at the end you get one extra free row ("combined: ...") with the interest over time of ALL your terms on one 0-100 scale. Turns on "Compare terms together".

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

Google Trends location code: a 2-letter country code like US, GB, DE, a region like US-CA or GB-ENG, or a US metro (DMA) like US-CA-807. Leave empty for worldwide.

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

Preset period, same as the time menu on trends.google.com. Ignored when "Custom time range" is filled in.

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

Optional exact period as "YYYY-MM-DD YYYY-MM-DD" (for example "2024-01-01 2024-12-31"). Overrides "Time range". Earliest start is 2004-01-01.

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

Google Trends category ID to narrow the meaning of a term (0 = all categories). Examples: 71 Food & Drink, 7 Finance, 5 Computers & Electronics, 18 Shopping, 3 Arts & Entertainment. You can read the ID from the "cat=" part of a trends.google.com URL.

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

Which Google search to measure, same as the "Web search" menu on trends.google.com.

## `includeInterestOverTime` (type: `boolean`):

Time series of relative search interest (0-100).

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

Relative search interest per country, region, metro or city (0-100).

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

Level of detail for interest by region. Automatic = countries for worldwide, sub-regions (states, provinces) for a country. DMA (metro areas) is only available for the US. If Google does not offer the chosen level for your location, the automatic level is returned and a warning is added to the row.

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

Top and rising related search queries for each term.

## `includeRelatedTopics` (type: `boolean`):

Top and rising related topics (entities) for each term. Note: Google Trends currently returns related topics for few searches and never for comparisons; when it returns none, the row gets a warning. Adds one request per term.

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

Google Trends rate-limits busy IP addresses (HTTP 429). Residential proxies give each retry a fresh IP and are strongly recommended; proxy traffic is included in the price.

## `maxRetries` (type: `integer`):

How many times a rate-limited or failed request is retried (exponential backoff with jitter, new proxy IP each time) before the term is returned with status "error".

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

How many terms are scraped at the same time. Higher is faster for long lists but triggers more rate limiting.

## Actor input object example

```json
{
  "searchTerms": [
    "coffee"
  ],
  "compareTogether": false,
  "geo": "US",
  "timeRange": "today 12-m",
  "category": 0,
  "property": "",
  "includeInterestOverTime": true,
  "includeInterestByRegion": true,
  "regionResolution": "AUTO",
  "includeRelatedQueries": true,
  "includeRelatedTopics": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxRetries": 8,
  "maxConcurrency": 3
}
```

# Actor output Schema

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

One row per search term or comparison group: interest over time, interest by region, related queries and topics, status and a Google Trends link. Rows with status "error" are not charged.

## `interestOverTime` (type: `string`):

Flattened time series: one row per term and period, with relative interest (0-100).

## `interestByRegion` (type: `string`):

One row per term and region, with relative interest (0-100).

# 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"
    ],
    "geo": "US",
    "timeRange": "today 12-m",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

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

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

```

## MCP server setup

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