# Google Trends Scraper — interest over time, regions, related (`crearcode/google-trends-scraper`) Actor

Google Trends data that always finishes. Interest over time, interest by region and related queries, with Top and Rising labelled separately. Hard run limit, session-aware retries and partial-result flushing — no infinite loops, no empty runs. Any keyword, country and timeframe.

- **URL**: https://apify.com/crearcode/google-trends-scraper.md
- **Developed by:** [Juan Carlos Gutiérrez Huérfano](https://apify.com/crearcode) (community)
- **Categories:** SEO tools, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 96.2% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.10 / 1,000 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?

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 — never hangs, always returns

Interest over time, interest by region, and related queries from Google Trends.

The point of this Actor is not that it fetches Trends data — plenty do. It is that
**it finishes.** Every run has a hard limit, every retry is budgeted against it, and
everything already collected is written to the dataset before the run ends. You never
watch a run burn five minutes and hand back nothing.

### Why runs hang elsewhere, and what this does instead

Google Trends rarely blocks scrapers by IP. It rejects requests that arrive **without a
session**. Verified directly: the identical request with no cookies returns `HTTP 429`
and an HTML error page, while the same request with a warmed cookie jar returns `200` —
and six rapid calls in a row all returned `200` with no pacing at all.

So a `429` here means *"you skipped the handshake"*, not *"you are banned"*. That single
distinction drives the whole retry policy:

| Response | What most clients do | What this does |
|---|---|---|
| `429` | Back off and retry the same dead session, often forever | **Re-warm the session**, then retry |
| `5xx` | Retry | Retry with capped backoff |
| Other `4xx` | Retry | **Fail immediately** — it will not fix itself |
| Timeout | Hang, or retry without a ceiling | Abort the request, count it, move on |

On top of that:

- **`maxRunSeconds` is absolute.** No request, wait or retry can extend past it. When
  the clock runs out the run stops cleanly and reports what it got.
- **Results are flushed per keyword**, not at the end. A run that dies at keyword 40
  still gives you keywords 1–39.
- **One dead widget never sinks a keyword.** If `GEO_MAP` fails but `TIMESERIES`
  succeeds, you get the timeseries and a note about the map.
- **`RUN_SUMMARY`** in the key-value store lists every keyword, which widgets returned
  what, elapsed time, and the network counters (requests, retries, re-warms, 429s,
  timeouts). A slow or thin run explains itself.

### Two things it gets right that are easy to get wrong

**Separate vs compared scales.** Google's numbers are relative, always 0–100. Ask for
three terms separately and each gets its own scale — good for seeing each term's own
shape. Ask for them *compared* and they share one scale — good for relative size. The
values are not interchangeable, and conflating them is the most common way Trends data
gets misread. Every row carries a `compared` flag so you always know which you have.

**Top vs Rising.** Google returns two related-terms lists: `top` (large and steady) and
`rising` (small but accelerating, including "Breakout" for >5000% growth). Most scrapers
flatten them into one array and throw away the distinction that makes the widget useful.
Here every related row is labelled `list: "top" | "rising"` and carries `isBreakout`.

And when a term is too low-volume for Google to return related terms, the summary says
`no data for this term` rather than emitting whatever generic trending Google falls
back to. A missing answer beats a wrong one.

### Input

```json
{
  "keywords": ["typescript", "rust", "golang"],
  "compareKeywords": false,
  "geo": "US",
  "timeframe": "today 12-m",
  "property": "",
  "includeInterestOverTime": true,
  "includeInterestByRegion": true,
  "includeRelatedQueries": true,
  "includeRelatedTopics": false,
  "maxRunSeconds": 300
}
```

`timeframe` accepts Google's own strings — `now 1-H`, `now 7-d`, `today 1-m`,
`today 3-m`, `today 12-m`, `today 5-y`, `all` — or a custom range like
`2025-01-01 2026-01-01`. `geo` takes a country code (`US`, `CO`, `MX`, `ES`) or a
sub-region (`US-CA`); leave it empty for worldwide. `property` selects the surface:
web, `images`, `news`, `youtube` or `froogle` (Shopping).

### Output

Flat rows with a `dataType` discriminator, so one dataset covers every widget and you
filter instead of unpacking.

```json
{
  "dataType": "interest_over_time",
  "keyword": "typescript",
  "geo": "US",
  "timeframe": "today 12-m",
  "property": "",
  "compared": false,
  "date": "2026-08-09",
  "dateLabel": "Aug 9 – 15, 2026",
  "value": 21,
  "isPartial": true,
  "scrapedAt": "2026-08-01T12:00:00.000Z"
}
```

```json
{
  "dataType": "related_query",
  "keyword": "typescript",
  "list": "rising",
  "query": "typescript 6",
  "value": 4300,
  "isBreakout": false
}
```

`interest_by_region` rows add `regionName`, `regionCode` and `hasData` — the last one
distinguishes "genuinely zero interest" from "Google had too little data to say".
`isPartial` marks the trailing bucket that is still filling, which is the usual cause
of a phantom drop at the right edge of a chart.

### What it does not do

- **No personal data.** Trends is aggregate search interest; nothing here identifies anyone.
- **No absolute search volumes.** Google does not publish them. Anything claiming
  otherwise is modelling, not measuring.
- **No login, ever.** Everything comes from public endpoints an anonymous browser can reach.

### Local development

```bash
npm install
npm run build
npm run test:local
```

`test:local` runs offline assertions — including the two parser bugs found during
development — then performs a live session probe and prints whether a cold, anonymous
handshake succeeds on your network.

### Issues

If a term returns nothing you expected, open an issue with the exact input and the
`RUN_SUMMARY` from that run. The summary usually contains the answer.

# Actor input Schema

## `keywords` (type: `array`):

One or more search terms. By default each term is fetched independently, so every term gets its own 0-100 scale.

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

Off (default): each term is scored on its own scale — use this to see each term's own trend. On: terms are scored against each other on one shared scale, in groups of five, exactly like the Compare view on the Trends website. These answer different questions and the numbers are not interchangeable.

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

ISO-3166 country code such as US, CO, MX, ES. Sub-regions work too, e.g. US-CA. Leave empty for worldwide.

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

Google timeframe string. Common values: "now 1-H", "now 7-d", "today 1-m", "today 3-m", "today 12-m", "today 5-y", "all". A custom range works as "2025-01-01 2026-01-01".

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

Which Google surface to measure.

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

Google Trends category id. 0 means all categories.

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

The 0-100 popularity series over the selected time range. This is the core Google Trends chart.

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

Relative popularity broken down by country, or by sub-region when a country is set in Location.

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

Returns both Google's Top and Rising lists, labelled — Rising is where breakouts show up.

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

Off by default: Google returns nothing here for lower-volume terms.

## `maxRunSeconds` (type: `integer`):

The run stops cleanly at this point and keeps everything already collected. This is what stops a run from hanging: no retry, wait or request can ever push past it.

## `requestTimeoutSeconds` (type: `integer`):

How long a single request to Google may take before it is abandoned and retried. Keep it well below the hard run limit.

## `paceMs` (type: `integer`):

Politeness pacing. 800ms is comfortable; lower it if you are in a hurry and accept more re-handshakes.

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

Affects Google's labels and the related-terms language.

## Actor input object example

```json
{
  "keywords": [
    "typescript",
    "rust",
    "golang"
  ],
  "compareKeywords": false,
  "geo": "US",
  "timeframe": "today 12-m",
  "property": "",
  "category": 0,
  "includeInterestOverTime": true,
  "includeInterestByRegion": true,
  "includeRelatedQueries": true,
  "includeRelatedTopics": false,
  "maxRunSeconds": 300,
  "requestTimeoutSeconds": 20,
  "paceMs": 800,
  "language": "en-US"
}
```

# Actor output Schema

## `allRowsJson` (type: `string`):

Every row collected in this run. Filter by dataType: interest\_over\_time, interest\_by\_region, related\_queries, related\_topics.

## `allRowsCsv` (type: `string`):

The same rows as CSV, ready to open in a spreadsheet.

## `runInConsole` (type: `string`):

Run detail page with the labelled table view, the log and the input used.

# 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 = {
    "keywords": [
        "typescript",
        "rust",
        "golang"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("crearcode/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 = { "keywords": [
        "typescript",
        "rust",
        "golang",
    ] }

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

```

## MCP server setup

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