# Google Trends Scraper - Interest, Regions & Related Queries (`dami_studio/google-trends-scraper`) Actor

Get Google Trends data with no API key. You get interest over time, interest by region, and top and rising related queries. Every number is relative to that keyword's own peak in your window. It is not a search volume. Up to 25 keywords. $0.06 per 1,000 rows, flat on every plan.

- **URL**: https://apify.com/dami\_studio/google-trends-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** SEO tools, News
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.06 / 1,000 google trends rows

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?

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 Trends Scraper

Type keywords, get Google Trends back as flat rows: interest over time, interest by region, and the
top and rising related queries. One row per datapoint, ready to drop into a spreadsheet, a database
or a chart. It also pulls the Trending Now list for any country.

No account, no API key, no cookies, no browser.

- Up to 25 keywords per run, each scaled 0-100 against its own peak, the same numbers Google Trends
  shows you.
- Three data sets per keyword. Tick only the ones you want and pay for nothing else.
- Every region Google supports: a country code like US, GB, DE or IN, a sub-region like US-CA, or
  worldwide.
- Time ranges from the past hour to 2004-to-now, plus any custom date range you type.
- Comparison mode puts up to five keywords on one shared scale, like typing several terms into
  Google Trends at once.
- Trending Now for any country, with the top news story behind each spike.

### Price

**$0.06 per 1,000 rows**.

That $0.06 doesn't move with your plan or your volume. No tiers to climb, no minimum spend, nothing
charged monthly.

#### What is actually charged

- **One `trend-scraped` event per row written to the dataset.** Nothing else is metered per row.
- **Free:** the sample row an empty run returns, and every diagnostic row, whether that's a keyword
  with no data, a rate-limited request or a region Google doesn't publish. They carry
  `"charged": false`.
- Datapoints Google marks as having no data are dropped before they're charged, so a keyword with
  thin volume costs you nothing for the empty part of the series.
- Ask for city-level detail in a region where Google has none and the run falls back to the level
  Google does publish. You're charged for the rows you get, never for the attempt.
- One row, one charge, and both are countable. The billing event has its own name, so nothing is
  priced onto Apify's generic per-item event where you wouldn't think to look for it.

### Input

```json
{
  "searchTerms": ["bitcoin", "ethereum"],
  "geo": "US",
  "timeframe": "today 12-m",
  "dataTypes": ["interestOverTime", "interestByRegion", "relatedQueries"],
  "maxItems": 500
}
```

| Field | What it does |
|---|---|
| `searchTerms` | The keywords or phrases to look up. Up to 25 per run. Each is looked up on its own and scaled 0-100 against its own peak. |
| `geo` | Two-letter country code (`US`, `GB`, `DE`, `IN`, `BR`), a sub-region (`US-CA`, `GB-ENG`), or empty for worldwide. |
| `timeframe` | One of `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 a custom range typed as `2024-01-01 2024-12-31`. |
| `dataTypes` | Any of `interestOverTime`, `interestByRegion`, `relatedQueries`. Drop the ones you don't need and the bill drops with them. |
| `maxItems` | Total rows across every keyword and data set. Budget roughly 150 rows per keyword when all three data sets are on. Default 1,000, hard ceiling 20,000. |
| `compareKeywords` | Off by default. Turn it on to put the first five keywords on one shared scale so their interest-over-time values can be compared directly. |
| `searchType` | `web`, `images`, `news`, `youtube` or `shopping`. Which Google surface the interest is measured on. |
| `regionLevel` | `default`, `country`, `region`, `city` or `metro`. `city` and `metro` produce far more rows; when Google has no data at that level the run falls back to the level it does publish. |
| `category` | Optional Google Trends category id, for example 71 for Food & Drink or 7 for Finance. 0 means all categories. |
| `language` | Language code for region names and labels, like `en-US`, `de` or `ja`. It changes spelling, not data. |
| `trendingNowGeos` | Optional country codes for the Trending Now list. Independent of the keywords, and usable on its own. |
| `proxyUrls` | Leave empty. Fill it in only if you want the traffic to leave through proxy servers you already pay for. |

Leave the input **empty** and you get one labelled sample row for nothing, so you can see the
shape before you spend.

### Output

One row per datapoint. Real row from a real run:

```json
{
  "ok": true,
  "charged": true,
  "keyword": "bitcoin",
  "geo": "US",
  "timeframe": "today 12-m",
  "searchType": "web",
  "category": 0,
  "trendsUrl": "https://trends.google.com/trends/explore?q=bitcoin&date=today+12-m&geo=US",
  "recordType": "interest_over_time",
  "date": "2025-08-17T00:00:00.000Z",
  "dateLabel": "Aug 17, 2025",
  "value": 29,
  "formattedValue": "29",
  "isPartial": false,
  "scrapedAt": "2026-08-20T19:53:13.168Z"
}
```

#### Field notes

- `recordType` — which of the four kinds of row this is: `interest_over_time`, `interest_by_region`,
  `related_query` or `trending_now`. Filter on it to split the dataset into its parts.
- `value` — Google's own 0-100 relative interest number. 100 is the peak of the series you asked
  for, not an absolute search volume. Google never publishes absolute volumes.
- `formattedValue` — the label Google shows next to the value. For rising related queries this is
  where `Breakout` and `+450%` appear, which the numeric `value` can't express.
- `date` — ISO 8601 in UTC, on `interest_over_time` rows and on the publication time of a
  `trending_now` row. Null on regional and related-query rows.
- `dateLabel` — the same instant as Google prints it on the chart axis, kept because a weekly point
  covers a range the timestamp alone hides.
- `isPartial` — true on the final point of a series, where the period hasn't finished and the value
  will still move. Drop those rows before charting a trend line.
- `geoName` — the region name in your chosen language, on `interest_by_region` rows. `geoCode`
  carries the machine-readable code where Google publishes one.
- `latitude` / `longitude` — only on city-level regional rows, where Google returns coordinates
  instead of a region code.
- `relatedQuery` — the related search, on `related_query` rows. `queryType` is `top` (consistently
  searched alongside your keyword) or `rising` (growing fastest right now), and `rank` is its
  position, 1 being the strongest.
- `trendsUrl` — the Google Trends page the row came from, so any number can be checked by eye in one
  click.
- `trafficEstimate` — on `trending_now` rows only. The approximate search count Google publishes for
  the spike, such as `20K+`.

### How it works

It talks to the same JSON endpoints the Google Trends website calls, and asks for only the data sets
you ticked. No page rendering, no headless browser, no login, no API key.

The endpoint hands any first-time visitor a short-lived guest session before it will answer. The run
mints its own, keeps it for the run, and throws it away at the end. Nothing is stored between runs
and no account is involved.

Requests leave through a large pool of rotating addresses. Google throttles per address, so when one
is throttled the run continues from the next rather than waiting, and because the data request isn't
tied to the address that opened it, nothing has to be redone.

Every response is parsed straight into flat rows. A datapoint Google marks as empty is dropped
before it becomes a row, so you're never billed for a gap in the series.

The Trending Now list comes from Google's own public feed for that country, which needs no session
at all.

### The four kinds of row

Everything is one flat table, and `recordType` says which part of Google Trends a row came from.

- **`interest_over_time`** — one row per point on the chart, with `date`, `dateLabel`, `value` and
  `isPartial`. A 12-month range gives about 53 weekly points; the past day gives minute-level
  points; `all` gives monthly points back to 2004.
- **`interest_by_region`** — one row per region, with `geoName`, `geoCode` and `value`. Worldwide
  runs break down by country; a single country breaks down by state or province, or by city or
  metro area if you ask.
- **`related_query`** — one row per related search, with `relatedQuery`, `queryType` (`top` or
  `rising`), `rank`, `value` and `formattedValue`. Up to 25 of each per keyword.
- **`trending_now`** — one row per search spiking right now in the country you asked for, with
  `rank`, `trafficEstimate` and the top news story behind it.

Because it's one table, `keyword`, `geo`, `timeframe`, `searchType` and `category` repeat on every
row. That's deliberate. You can filter or group the dataset without joining anything back together.

### Diagnostic rows

Nothing here fails silently, and a failed run never bills you for the failure. A problem becomes a
row carrying `"_diagnostic": true`, `"charged": false` and an `errorCode`: `NO_RESULTS` when Google
has no data for a keyword in that region and range, `RATE_LIMITED` when Google throttled a request
the run couldn't recover, `BLOCKED` when a request was refused, `TIME_BUDGET` when the run ran out
of time before reaching a keyword, `NETWORK` when the target couldn't be reached. Each carries a
plain-English `error` and the `keyword` or `geo` it belongs to.

The free sample row is tagged `"_sample": true` and only turns up when the run was given no keywords
and no trending-now regions. Filter on `charged == true` for the data alone; the count of those rows
is the number of events you were billed for.

### What the 0-100 number actually means

Google Trends never publishes absolute search volumes. Every `value` is relative: 100 is the highest
point of the exact series you asked for, and everything else is scaled against it. Change the time
range, the region or the category and the whole series is rescaled.

Two consequences worth knowing before you build on it.

1. **Two separate runs are not directly comparable.** "bitcoin" at 100 and "ethereum" at 100 in two
   runs does not mean they're equally searched. Each was scaled against its own peak. When you need
   them on one scale, turn on `compareKeywords` and run them together.
2. **A keyword with low volume returns little or nothing.** Google suppresses series it considers
   too sparse, and you get an uncharged `NO_RESULTS` row rather than a flat line of zeroes. Broaden
   the region, lengthen the time range, or use a more common phrasing.

### Limitations

- Related TOPICS aren't offered. Google returns an empty list for that widget to callers who aren't
  signed in, so shipping the field would mean shipping a column that's always empty. Related
  QUERIES, top and rising, are unaffected and come back in full.
- Values are Google's relative 0-100 index, never absolute search volumes. Nobody can scrape
  absolute volumes from Google Trends, because Google doesn't publish them.
- A series is rescaled whenever the region, time range or category changes, so numbers from runs
  with different settings can't be compared point-for-point.
- Keywords with low search volume return no data at all rather than low numbers. You get an
  uncharged diagnostic row saying so.
- The final point of a series is usually partial, flagged with `isPartial: true`. It will move if
  you re-run later in the day.
- City-level and metro-level breakdowns are best-effort. Google publishes dense city data for
  high-volume terms in large countries and nothing at all in many other combinations. When there's
  nothing, the run falls back to the level Google does publish. Metro areas are United States only.
- Comparison mode takes at most five keywords, which is Google's own limit. Extra keywords are
  dropped with a warning.
- The Trending Now list is whatever Google currently publishes for that country, typically around
  ten entries, and it's a live snapshot. Two runs an hour apart will differ.
- Google throttles heavy use per address. The run answers a throttle by continuing from a different
  address, but a very large run can still end with some keywords carrying an uncharged
  `RATE_LIMITED` row instead of data.
- Hard ceilings: 25 keywords per run, 20 trending-now regions per run, 20,000 rows per run.
- Category ids are Google's own numeric ids. There's no lookup built in. Pick the number off a
  Google Trends URL when you narrow a search by category there.

### Questions

**Do I need a Google account or an API key?**

No. The run mints its own short-lived guest session against Google's public endpoints and discards
it when it finishes. No account is created, no credentials are stored, nothing carries over between
runs.

**Why does one keyword produce so many rows?**

Because the output is flat. A single keyword with all three data sets over twelve months is about 53
interest-over-time points, about 50 regions and up to 50 related queries, so roughly 150 rows.
That's the same data Google shows you on one page, unpacked so it can be sorted, filtered and
charted without parsing. For fewer rows, untick data sets in `dataTypes` or lower `maxItems`.

**Can I compare two keywords directly?**

Set `compareKeywords` to true and put both in `searchTerms`. Up to five keywords are then put on one
shared 0-100 scale so their `value` numbers read against each other. Leave it off and each keyword
is scaled against its own peak instead.

**What happens if a keyword has no data?**

One uncharged diagnostic row for it with `errorCode: "NO_RESULTS"`, and the run carries on to your
other keywords.

**Will the run fail if Google throttles it?**

No. A throttled request is retried from a different address automatically. If a data set still can't
be fetched you get an uncharged `RATE_LIMITED` diagnostic row for it and the run finishes as
succeeded.

**Can I run this on a schedule?**

Yes. Nothing is held between runs. Schedule the same input daily or weekly and use `keyword` plus
`recordType` plus `date` as a key to work out what changed. Remember the last point of each series
is partial and will move.

**Do I need a proxy?**

No, and there's nothing to configure. Egress is included in the rate above. `proxyUrls` is there
only for people who already pay for their own servers and want the traffic to leave through those.

# Actor input Schema

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

The keywords or phrases to look up on Google Trends. Up to 25 per run. Each one is looked up on its own and scaled 0-100 against its own peak, exactly as it is when you type a single term into Google Trends.

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

Two-letter country code such as US, GB, DE, IN or BR, or a sub-region such as US-CA or GB-ENG. Leave empty for worldwide.

## `timeframe` (type: `string`):

How far back to look. Pick one of the standard ranges, or type a custom range as two ISO dates, for example "2024-01-01 2024-12-31". Shorter ranges return finer-grained points: the last day is minute-by-minute, the last week is hourly, a year is weekly, five years is weekly and "all" is monthly.

## `dataTypes` (type: `array`):

Which of the three data sets to pull for each keyword. Fewer data sets means fewer rows and a smaller bill. Leave all three ticked to get everything.

## `maxItems` (type: `integer`):

Total rows to return across every keyword and data set. A single keyword with all three data sets and a 12-month range produces roughly 150 rows, so budget about 150 rows per keyword. Keep this low while you are testing - you pay per row.

## `compareKeywords` (type: `boolean`):

Off by default. When off, each keyword is scaled 0-100 against its own peak. When on, the first five keywords are put on one shared scale so their interest-over-time values can be compared directly, exactly like typing several terms into Google Trends at once.

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

Which Google surface the interest is measured on.

## `regionLevel` (type: `string`):

How finely to break down the interest-by-region data. "Automatic" uses whatever level Google itself shows for the region you picked - countries for a worldwide run, states or provinces for a single country. "City" and "Metro area" produce far more rows (roughly 200 each) and are only available inside a single country; Metro area is United States only. If a level is not available for your region, the run falls back to Automatic instead of failing.

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

Optional Google Trends category id to narrow the search, for example 71 for Food & Drink, 7 for Finance or 174 for Sports. Leave at 0 for all categories.

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

Language for region names and formatted labels, as a code like en-US, de, fr or ja. It does not change which data is returned, only how names are spelled.

## `trendingNowGeos` (type: `array`):

Optional. Add country codes such as US, GB or DE to also pull that country's current Trending Now list - the searches spiking right now, with the top news story behind each one. This is independent of the keywords above and can be used on its own.

## `proxyUrls` (type: `array`):

Leave this empty. By default the run rotates a large pool of addresses that cost you nothing per gigabyte. Fill it in only if you specifically want the traffic to leave through proxy servers you already pay for, in the form http://user:pass@host:port.

## Actor input object example

```json
{
  "searchTerms": [
    "bitcoin",
    "ethereum"
  ],
  "geo": "US",
  "timeframe": "today 12-m",
  "dataTypes": [
    "interestOverTime",
    "interestByRegion",
    "relatedQueries"
  ],
  "maxItems": 1000,
  "searchType": "web",
  "regionLevel": "default",
  "language": "en-US"
}
```

# Actor output Schema

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

Every row in the default dataset: recordType, keyword, geo, geoName, timeframe, date, value, formattedValue, relatedQuery, queryType, rank, isPartial, trendsUrl, geoCode, dateLabel, searchType, category, latitude, longitude, trafficEstimate, newsTitle, newsUrl, newsSource, newsCount, scrapedAt. An empty, blocked or unmatched run returns a single uncharged row explaining what happened instead.

# 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"
    ],
    "geo": "US",
    "timeframe": "today 12-m",
    "dataTypes": [
        "interestOverTime",
        "interestByRegion",
        "relatedQueries"
    ],
    "maxItems": 1000,
    "compareKeywords": false,
    "searchType": "web",
    "regionLevel": "default",
    "category": 0,
    "language": "en-US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/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",
    ],
    "geo": "US",
    "timeframe": "today 12-m",
    "dataTypes": [
        "interestOverTime",
        "interestByRegion",
        "relatedQueries",
    ],
    "maxItems": 1000,
    "compareKeywords": False,
    "searchType": "web",
    "regionLevel": "default",
    "category": 0,
    "language": "en-US",
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/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"
  ],
  "geo": "US",
  "timeframe": "today 12-m",
  "dataTypes": [
    "interestOverTime",
    "interestByRegion",
    "relatedQueries"
  ],
  "maxItems": 1000,
  "compareKeywords": false,
  "searchType": "web",
  "regionLevel": "default",
  "category": 0,
  "language": "en-US"
}' |
apify call dami_studio/google-trends-scraper --silent --output-dataset

```

## MCP server setup

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