# Google Trends Scraper - Compare 100 Keywords (`satin_armeria/google-trends-advanced-comparator`) Actor

Google Trends scraper & API that compares up to 100 keywords on ONE 0-100 scale (Google allows 5). Every run is self-checked against a direct Google query. Country, region, metro, custom dates, Web/YouTube/News/Shopping. CSV/JSON. Pay only for keywords delivered.

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

## Pricing

from $4.00 / 1,000 keyword compareds

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 – Compare 100 Keywords on One Scale

**Google Trends lets you compare only 5 keywords at a time, and every comparison is rescaled to its own 100.** This Actor compares **up to 100 keywords on one common 0–100 scale**, so "50" means the same thing for every keyword in your list. Every run is **self-checked against a direct Google Trends query** and the error is reported in the output.

**$4 per 1,000 keywords. You pay only for keywords that were actually delivered.**

### Who uses it

- **SEO & content agencies**: rank 20–100 topic or keyword ideas by real search interest before writing content; compare clients vs. competitors in one chart.
- **Marketers & brand managers**: track brand vs. competitor interest by country, US state or metro, on Web, YouTube, News or Google Shopping.
- **Analysts & researchers**: long, comparable time series (2004–today) for 100 terms, ready for Excel, Google Sheets, Python or BI tools.
- **E-commerce & product teams**: compare product categories or brands on Google Shopping search for a market and season.
- **AI agents (MCP) & developers**: one API call with just `keywords` (+ optional `geo`, `timeframe`) returns normalized data plus a quality report the agent can check.

### Why chaining matters (real example)

Paste several 5-keyword Google Trends exports together and each batch has its own 100, so the numbers can't be compared. In our test with synthetic data of known truth, this naive approach was **off by more than 20 index points**. With anchor chaining every well-measured keyword stayed within **0.3 points** mean absolute error.

Real 100-keyword run (worldwide, last 5 years, 2026-10-18), top of the ranking on the common scale:

| Rank | Keyword | Average | Peak |
|---|---|---|---|
| 1 | chatgpt | 38.2 | 100.0 |
| 2 | c++ | 29.4 | 40.2 |
| 3 | python | 9.4 | 14.0 |
| 4 | swift | 7.6 | 25.7 |
| 5 | github | 6.2 | 27.0 |
| 6 | java | 6.1 | 8.5 |

![6 of the 100 keywords on one common scale (real run)](https://raw.githubusercontent.com/carlos-calatayud/google-trends-compare-100-keywords/main/chart_100_keywords.png)

Self-check of that run: 5 keywords measured in 4 *different* batches (cloudflare, scala, jenkins, typescript, postgresql) were queried directly together. Mean absolute error vs. Google's direct answer: **0.187 points** (max 1.01), average-ratio error ≤ 0.37 %.

### Measured accuracy and speed (real runs)

| Run | Keywords | Requests | Time | Self-check error (mean, 0–100 scale) |
|---|---|---|---|---|
| US, 12 months, Apify cloud, residential proxy | 25 | 29 | 36 s | 0.59 points ✅ |
| US-CA, 2024, YouTube (local test) | 25 | 25 | 41 s | 0.15 points ✅ |
| Worldwide, last 7 days hourly, News + extras (local test) | 6 | 21 | 46 s | 0.61 points ✅ |
| Worldwide, 5 years (local test) | 100 | 109 | 3 min 43 s | 0.19 points ✅ |

### Features

- **1–100 keywords on one 0–100 scale** (100 = the single highest point of any keyword in your list).
- **Self-check** against a direct Google query + per-keyword precision and consistency metrics.
- **Any location**: worldwide, country (`US`, `ES`, `DE`…), region (`US-CA`, `ES-MD`), US metro (`US-NY-501`).
- **Any period**: past hour … 2004–today, or a custom date range.
- **Web, News, YouTube, Images, Google Shopping**, plus category filter.
- Optional **related queries** (top & rising) and **interest by region**.
- **CSV / JSON / Excel** dataset export + ready-made wide CSV (date × keyword) and summary CSV.
- **Fair billing**: failed or no-data keywords are free; your max cost per run is respected.
- **Safe**: hard caps on requests and runtime, bounded retries, clear error messages, no infinite loops.

### How it works (anchor chaining)

1. **Seed**: the first 5 keywords are queried together and a *mid-popularity* keyword is chosen as the anchor.
2. **Star screening**: the remaining keywords are queried in groups of 4 + the anchor.
3. **Refinement ladders**: keywords that are much more or much less popular than the anchor would lose precision (Google rounds to integers, very small values become `0` or `<1`). They are re-measured in overlapping batches with neighbours of similar magnitude, so every keyword is measured where its values are large and precise.
4. **Weighted least-squares solve**: one scale factor is estimated for every batch from all shared keywords at once (weighted by rounding precision), so ratios are chained through multiple anchors instead of a single fragile one.
5. **Global renormalization**: all series are rescaled so the overall maximum is exactly 100.
6. **Self-check**: up to 5 keywords of similar magnitude that were measured in *different* batches are queried directly together; the Actor reports mean/max absolute error, average-ratio error and Pearson correlation.

Every keyword also gets **consistency metrics**: how many batches it appeared in, its best raw peak (precision), the number of `<1` points, the scale residual across batches and the cross-batch deviation of its series.

### Input example

```json
{
  "keywords": ["python", "javascript", "rust", "golang", "kotlin", "swift", "typescript"],
  "geo": "US",
  "timeframe": "today 12-m",
  "gprop": "web",
  "category": 0,
  "selfCheck": true
}
```

Custom date range for a region on YouTube:

```json
{
  "keywords": ["iphone", "samsung", "pixel", "xiaomi", "oneplus", "motorola", "nokia"],
  "geo": "US-CA",
  "timeframe": "custom",
  "startDate": "2024-01-01",
  "endDate": "2024-12-31",
  "gprop": "youtube"
}
```

| Field | Description |
|---|---|
| `keywords` | 1–100 terms (deduplicated case/whitespace-insensitively). |
| `geo` | Empty = worldwide, `US`, `US-CA`, `US-NY-501`… |
| `timeframe` | `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 `custom` with `startDate`/`endDate`. |
| `gprop` | `web`, `news`, `youtube`, `images`, `froogle` (Google Shopping). |
| `category` | Google Trends category ID, `0` = all. |
| `selfCheck` | Validate against a direct query (default `true`, no extra charge). |
| `includeRelatedQueries`, `includeInterestByRegion` | Optional extra data per keyword. |
| `proxyConfiguration` | Residential proxy recommended (default). |
| `maxRequests`, `maxRuntimeSeconds`, `maxRetries`, `requestDelayMs`, `maxConcurrency`, `maxRefinementRounds` | Safety and speed limits. |

### Output

**Dataset** – one row per keyword per date (export as CSV, JSON, Excel…):

```json
{"keyword": "python", "date": "2025-10-12", "timestamp": 1760227200, "value": 57.0,
 "isPartial": false, "status": "ok", "rank": 1, "geo": "US", "timeframe": "today 12-m",
 "gprop": "web", "category": 0}
```

**Key-value store**

| Record | Content |
|---|---|
| `OUTPUT` | Ranking and per-keyword metrics (average, peak, peak date, latest, precision, consistency), validation & self-check, request statistics, warnings. |
| `results_wide.csv` | `date, keyword1, keyword2, …` – ready for Excel / Google Sheets. |
| `summary.csv` | One row per keyword with rank and metrics. |
| `results.json` | Everything above plus the full time series. |
| `RELATED_QUERIES`, `INTEREST_BY_REGION` | Only when requested. |

Keyword `status`: `ok`, `low_precision` (best raw peak < 5 – treat with care), `no_data` (below Google's detection relative to the other terms – not charged), `disconnected`/`failed` (could not be measured – not charged).

### Pricing (real numbers)

| What you run | Keywords charged | Price |
|---|---|---|
| 7 programming languages, US, 12 months | 7 | **$0.028** |
| 25 brands, one US state, 2024 | 25 | **$0.10** |
| 100 keywords, worldwide, 5 years | 100 | **$0.40** |
| + related queries and region breakdown for 6 keywords | 12 extras | +$0.024 |

Plus Apify's standard start fee ($0.00005 per run). No charge per dataset row, no platform-usage add-on.

Pay per event – **$4 per 1,000 keywords compared** (`keyword-compared`, charged only for keywords with status `ok` or `low_precision`, after the data has been saved). Optional related queries / interest by region: `keyword-extra-data`, $2 per 1,000. The Actor respects your *max cost per run*: if the limit allows fewer keywords than you sent, only the first affordable ones are processed and the rest are skipped with a warning.

### Use via API

```bash
curl -X POST "https://api.apify.com/v2/acts/satin_armeria~google-trends-advanced-comparator/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"keywords":["python","javascript","rust","golang","kotlin","swift"],"geo":"US","timeframe":"today 12-m"}'
```

Python:

```python
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("satin_armeria/google-trends-advanced-comparator").call(run_input={
    "keywords": ["python", "javascript", "rust", "golang", "kotlin", "swift"], "geo": "US"})
summary = client.key_value_store(run["defaultKeyValueStoreId"]).get_record("OUTPUT")["value"]
rows = client.dataset(run["defaultDatasetId"]).list_items().items
```

**MCP / AI agents**: add the Actor to the Apify MCP server (`https://mcp.apify.com?tools=satin_armeria/google-trends-advanced-comparator`). The input schema is fully described, so an agent only needs `keywords` (+ optional `geo`, `timeframe`).

### Limitations (please read)

- Data comes from Google Trends' public web endpoints. Google Trends values are **sampled** and can change slightly between requests; the consistency metrics and the self-check show you how much.
- Very small keywords relative to the largest one may be `no_data` or `low_precision` – this is a property of Google Trends, not a bug. Compare them in a separate run without the giants for more detail.
- Live `now …` timeframes shift between requests; timestamps not present in every batch are dropped.
- Interest by region is returned per keyword on its own 0–100 scale (not cross-keyword); small regions can dominate for low-volume properties such as News.
- Google may rate-limit (HTTP 429). The Actor backs off and rotates proxy sessions, but very large runs without a proxy can fail; you are never charged for keywords that were not delivered.
- Not affiliated with Google.

### FAQ

**Why not just use 5-keyword queries?** Each query has its own 100. Chaining with a shared anchor and solving all batch scales together is the only way to compare 20–100 terms on one scale.

**How accurate is it?** On synthetic data with known ground truth (exact Google-style rounding), every well-measured keyword was within 1 index point MAE. In live runs the self-check against a direct Google query had a mean absolute error of 0.15–0.6 points.

**Is this an official Google Trends API?** No. Google's official Trends API is invite-only (alpha). This Actor reads the same public data the Google Trends website shows.

**How is this different from other "compare unlimited keywords" scrapers?** We can't verify how other Actors normalize. What we can show: our method is documented above, every run includes a direct-query self-check you can verify yourself, and you are charged only for keywords that were delivered.

**What is the cheapest way to try it?** Run the prefilled 7-keyword example: it costs about $0.03.

# Changelog

This Actor's version history is a separate document: https://apify.com/satin_armeria/google-trends-advanced-comparator/changelog.md

# Actor input Schema

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

1-100 search terms to compare (duplicates are removed case/whitespace-insensitively). You are charged only for keywords that were successfully placed on the common scale.

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

Empty = worldwide. ISO country code (US, ES, DE...), region (US-CA, ES-MD) or US metro (US-NY-501).

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

Preset Google Trends period, or 'custom' to use Start date / End date. Live 'now' periods return hourly/minute data.

## `startDate` (type: `string`):

Used when Time range = custom. Format YYYY-MM-DD, not earlier than 2004-01-01.

## `endDate` (type: `string`):

Used when Time range = custom. Format YYYY-MM-DD. Defaults to today.

## `gprop` (type: `string`):

Which Google search to measure.

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

Google Trends category ID (0 = all categories; e.g. 5 = Computers & Electronics, 7 = Finance, 71 = Food & Drink).

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

Google Trends interface language, affects related-query labels only.

## `selfCheck` (type: `boolean`):

After chaining, re-query up to 5 of the keywords together in one direct Google Trends request and report the point-by-point error of the chained values. Costs 2 extra requests, no extra charge.

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

Fetch related queries for every keyword (1 extra request each, charged as 'keyword-extra-data'). Saved to the RELATED_QUERIES record.

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

Fetch each keyword's interest by sub-region (own 0-100 scale per keyword; 1 extra request each, charged as 'keyword-extra-data'). Saved to the INTEREST_BY_REGION record.

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

Google rate-limits shared IPs. Residential proxy is recommended for reliable runs.

## `maxRefinementRounds` (type: `integer`):

Extra rounds that re-measure keywords whose magnitude is far from the anchor (ladder batches). 0 = star chaining only (cheaper, less precise for long-tail terms).

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

0 = auto (3 with proxy, 1 without).

## `requestDelayMs` (type: `integer`):

Politeness delay per session. Lower values are faster but raise HTTP 429 risk.

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

Retries for HTTP 429/5xx/timeouts with bounded exponential backoff (4s, 8s, 16s... max 60s).

## `maxRequests` (type: `integer`):

The run stops fetching when this many requests were made (results so far are still normalized and saved).

## `maxRuntimeSeconds` (type: `integer`):

Stops fetching after this time and saves what was measured.

## Actor input object example

```json
{
  "keywords": [
    "python",
    "javascript",
    "rust",
    "golang",
    "kotlin",
    "swift",
    "typescript"
  ],
  "geo": "US",
  "timeframe": "today 12-m",
  "startDate": "2024-01-01",
  "endDate": "2024-12-31",
  "gprop": "web",
  "category": 0,
  "language": "en-US",
  "selfCheck": true,
  "includeRelatedQueries": false,
  "includeInterestByRegion": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxRefinementRounds": 2,
  "maxConcurrency": 0,
  "requestDelayMs": 1500,
  "maxRetries": 4,
  "maxRequests": 400,
  "maxRuntimeSeconds": 900
}
```

# Actor output Schema

## `timeSeries` (type: `string`):

One row per keyword per date.

## `summary` (type: `string`):

Ranking, per-keyword precision/consistency metrics, self-check result, request statistics.

## `wideCsv` (type: `string`):

No description

## `summaryCsv` (type: `string`):

No description

## `fullJson` (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 = {
    "keywords": [
        "python",
        "javascript",
        "rust",
        "golang",
        "kotlin",
        "swift",
        "typescript"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("satin_armeria/google-trends-advanced-comparator").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": [
        "python",
        "javascript",
        "rust",
        "golang",
        "kotlin",
        "swift",
        "typescript",
    ],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("satin_armeria/google-trends-advanced-comparator").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": [
    "python",
    "javascript",
    "rust",
    "golang",
    "kotlin",
    "swift",
    "typescript"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call satin_armeria/google-trends-advanced-comparator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,satin_armeria/google-trends-advanced-comparator"
        }
    }
}
```

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/TuWrGhlXSgeCuuIBh/builds/9ypYGTzcFHeDtRzb6/openapi.json
