# Google Trends Scraper — Calibrated Keyword Comparison API (`george.the.developer/calibrated-google-trends-api`) Actor

Compare Google Trends keyword portfolios beyond one request on a common calibrated scale, with uncertainty bounds and API-ready time-series data. One dataset record per keyword: raw series, calibrated series, bounds, anchor provenance.

- **URL**: https://apify.com/george.the.developer/calibrated-google-trends-api.md
- **Developed by:** [George Kioko](https://apify.com/george.the.developer) (community)
- **Categories:** SEO tools, Marketing, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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 — Calibrated Keyword Comparison API

Compare Google Trends keyword portfolios **beyond one request** on a single calibrated
scale, with uncertainty bounds and API-ready time-series data.

> **100 inputs are not necessarily 100 comparable values.** This actor makes them comparable.

### The problem with every Google Trends scraper

Google Trends never returns absolute search volume. Every request is normalized to its own
0–100 scale, where 100 = the biggest term *in that request*. Fetch two batches and the
numbers cannot be compared:

```
  Request A: [chatgpt, figma]              Request B: [obsidian, miro]
  ┌──────────────────────────┐             ┌──────────────────────────┐
  │ chatgpt   ████████── 100 │             │ obsidian  ████████── 100 │
  │ figma     █──────────  2 │             │ miro      █████────  57  │
  └──────────────────────────┘             └──────────────────────────┘
              │                                        │
              └───────────────┬────────────────────────┘
                              ▼
              Is obsidian's "100" bigger than figma's "2"?
              ── You cannot tell. Different requests,
                 different scales, no shared reference. ──
```

Send 100 keywords to an ordinary Google Trends API scraper and you get back 20 batches of
5, each on its own private scale. Ranking or charting them together is silently wrong.

### How calibration fixes it

Every keyword is measured **against an anchor** — a term from a pre-built, versioned anchor
bank whose terms were chained together through overlapping requests. Because each comparison
shares a request with its anchor, the ratio between them is meaningful, and every keyword
lands on one common scale:

```
  ANCHOR BANK (built once, versioned, immutable)
  ┌────────────────────────────────────────────────────────┐
  │  chatgpt ──── gmail ──── claude ai ──── cursor ai ──── │
  │   1.00        0.31        0.09           0.02          │
  │     └── ratios chained through shared terms ──┘        │
  └────────────────────────────────────────────────────────┘
                        ▲
        your keyword ───┤  one request: [your keyword, anchor]
                        │
   ratio(keyword/anchor) × anchor's known position
                        │
                        ▼
        ┌───────────────────────────────────┐
        │  keyword on the COMMON scale      │
        │  value: 3.57   bounds: [3.2, 4.0] │
        └───────────────────────────────────┘
```

### What one run does

```
 your keywords (1–20)
        │
        ▼
 ┌─────────────────┐    ┌──────────────────┐    ┌─────────────────────┐
 │ batch into      │    │ Google Trends    │    │ calibration engine  │
 │ groups of 4     │──▶ │ (US residential  │──▶ │ + versioned anchor  │
 │ + 1 anchor each │    │  proxy, retries) │    │   bank (KVS)        │
 └─────────────────┘    └──────────────────┘    └──────────┬──────────┘
                                                           │
                              charged only on delivery     ▼
                        ┌──────────────────────────────────────────┐
                        │ ONE DATASET RECORD PER KEYWORD           │
                        │ raw series · calibrated series · bounds  │
                        │ anchor used · bank revision · timestamp  │
                        └──────────────────────────────────────────┘
```

Keywords that cannot be calibrated (too small to register, transport exhausted) are
returned as diagnostic rows and **never charged**.

### What data you get per keyword

- `keyword` — your term, as sent
- `calibratedMax` — peak interest on the common scale (comparable across ALL your keywords)
- `lo`, `hi` — uncertainty bounds around the calibrated value
- `ratio` — the keyword's position relative to the bank's reference term
- `calibratedSeries` — weekly time series on the common scale (12 months)
- `rawSeries` — the original 0–100 values Google returned
- `anchorUsed`, `bankRevision` — full provenance: which anchor, which bank version
- `snapshotAt` — when this measurement was taken

### Output example (real run)

```json
{
  "keyword": "deepseek",
  "ok": true,
  "calibratedMax": 3.57,
  "lo": 3.21,
  "hi": 3.96,
  "ratio": 0.0357,
  "anchorUsed": "perplexity ai",
  "bankRevision": 3,
  "snapshotAt": "2026-08-03T06:31:12.000Z",
  "calibratedSeries": [
    { "time": 1722643200, "value": 1.964 },
    { "time": 1723248000, "value": 2.107 }
  ]
}
```

### Measured, not promised (audit of 2026-08-03)

We ran a public-methodology quality audit against the live actor before making any of
the claims on this page. The numbers, all dated with run IDs logged:

```
  Cross-anchor agreement     26/26 keywords (100%) — same keyword calibrated
                             through two independent anchors agreed within bounds
  Bounds coverage            52/52 checks (100%)
  Ground truth               10/10 clearly-separated rankings matched Google's
                             own same-request ordering
  100-keyword workload       100/100 inputs returned exactly one row each
  Billing reconciliation     41/41 audited runs: charges == delivered rows, exact
  Bad-run rate               1 of 46 production runs (2.2%)
  5-keyword run              p50 26s · p95 77s · p95 cost $0.032
  Snapshot stability         12 identical requests over 7.5h: median per-point
                             variation 0%, p95 2.9%
```

For context: the most-used Google Trends scraper on this store showed a 28.9% bad-run
share (timed-out + aborted + failed) in its public run statistics when we measured it
on 2026-08-02 across 21,682 runs. Sample sizes differ (46 vs 21,682 runs) — judge
accordingly; our audit set grows with every scheduled soak run.

One defect was found by this audit and fixed the same day (a dataset-schema rule that
crashed diagnostic rows). We publish what we find.

### Use cases

1. **SEO keyword research at portfolio scale** — rank 20 candidate topics on one scale
   instead of eyeballing separate Trends charts, before committing content budget.
2. **Brand share of search** — measure your brand against competitors and category terms
   in a single comparable frame, with bounds that tell you when a gap is real.
3. **Product demand validation** — compare niche product ideas whose volumes are too far
   apart to ever share one Google Trends request.
4. **SEO trend analysis pipelines** — the calibrated series is API-ready JSON; feed it
   straight into dashboards, sheets, or your own models via the Apify API.

### How to use

1. Enter 1–20 keywords (defaults work for everything else).
2. Run. A 5-keyword run typically finishes in 1–5 minutes.
3. Read the dataset — one record per keyword, all on the same scale — or pull it via the
   Apify API / scheduled Tasks.

Keep the default proxy setting (US residential). Google Trends aggressively rate-limits
datacenter IPs; the default exists because it is the configuration that works.

### Use it from Claude, Cursor, or any MCP client

This actor works as a **Google Trends MCP tool** out of the box — no extra setup on the
actor side. Add it to any MCP client through Apify's MCP server:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=george.the.developer/calibrated-google-trends-api",
      "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
    }
  }
}
```

Your AI assistant can then compare keyword portfolios on the calibrated scale as part of
its own research — "compare search demand for these 15 product ideas" becomes one tool
call. MCP clients connected to the general Apify server can also discover this actor
dynamically via the server's `call-actor` tool.

### Input parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `keywords` | array | yes | 1–20 keywords to calibrate onto one common scale |
| `geo` | string | no | `""` = Worldwide (default) or `US` |
| `timeframe` | string | no | `today 12-m` (past 12 months, default) |
| `category` | integer | no | Google Trends category id, default `0` (all) |
| `property` | string | no | `web` (default) |
| `maxCostUsd` | integer | no | Hard cost cap; run is rejected **without charge** if the keyword count would exceed it (default 3) |
| `proxyConfiguration` | object | no | Default: US residential via Apify Proxy — keep it |

(`mode` and `bankSpec` are owner-only maintenance inputs for building anchor banks; runs
from other accounts exit uncharged.)

### Understanding the bounds

Google rounds every value to an integer, so a reported `2` means "somewhere between 1.5
and 2.5". Those rounding errors propagate through each calibration ratio — the bounds make
that honest instead of hiding it:

```
  reported value:        2
  true value lies in:  [1.5 ─────────── 2.5]
  after calibration:   value 3.57, bounds [3.21 ── 3.96]

  Two keywords overlap in bounds?  → treat them as tied.
  Bounds clearly separated?        → the ranking is real.
```

The same keyword calibrated through two *different* anchors agrees within these bounds —
that is the correctness check the engine is built on.

### Pricing

Pay-per-event. You pay for delivered results, not attempts:

- **Actor start:** $0.00005
- **Calibrated keyword:** $0.15 per successfully calibrated keyword persisted to the dataset

Uncalibratable keywords come back as diagnostic rows at **no charge**. The `maxCostUsd`
input rejects over-budget runs before any work happens.

### Limits (read this)

- Google's API path accepts **max 5 terms per request** — that is why keywords are batched
  with an anchor, and why raw multi-keyword scrapers can't give you one comparable scale.
- Launch catalog: **Worldwide + US**, past 12 months, web search, all categories. More
  dimension combinations ship as their anchor banks are built and validated.
- Each run is a **fresh snapshot** of Google Trends at run time. Google's own sampling
  varies between snapshots; compare values *within* a run, and use the bounds when you do.
- Very-low-volume keywords may flatline at zero on every anchor — those are reported as
  diagnostics, uncharged.

### FAQ

**Q: Why don't the numbers match what I see on trends.google.com?**
A: The Trends UI normalizes each chart to its own 0–100. This actor puts all your keywords
on one shared scale instead — that is the entire point. The `rawSeries` field still gives
you Google's original per-request values.

**Q: What happens to keywords that fail?**
A: They come back as `ok: false` rows with a reason, and you are not charged for them.

**Q: Why 20 keywords max?**
A: Each keyword costs real requests against an aggressively rate-limited Google endpoint.
The cap keeps runs fast and predictable. Need more? Split into multiple runs — the shared
anchor bank keeps results on the same scale within each run's snapshot.

**Q: What is an anchor bank?**
A: A pre-measured chain of reference terms spanning several orders of magnitude of search
volume, built through overlapping Trends requests and stored versioned + immutable. Every
result records which bank revision measured it, so output is auditable.

**Q: Can I schedule this?**
A: Yes — standard Apify Tasks + Schedules work. Each scheduled run is an independent
snapshot with its own timestamp.

### More actors by this developer

- [LinkedIn Company Employees Scraper & Email Finder](https://apify.com/george.the.developer/linkedin-company-employees-scraper) — employee lists + verified work emails, no login or cookies
- [Shopify DTC Brand Discovery + Tech Stack Filter](https://apify.com/george.the.developer/shopify-dtc-brand-discovery) — find Shopify stores by niche and see the apps they run
- [Threads by Meta Scraper](https://apify.com/george.the.developer/threads-scraper) — posts, profiles and engagement data from Threads
- [Email Validator API](https://apify.com/george.the.developer/email-validator-api) — verify deliverability, disposable and MX checks in bulk

### Support

Found a bug or need a dimension combination (another country, timeframe, or property)?
Open an issue on the actor's **Issues** tab — supported combinations are prioritized by
request.

If this actor saved you time, a short review on this page helps other developers find it.

# Actor input Schema

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

Keywords to calibrate onto one common scale (1-20). Each successfully calibrated keyword is one charged result.

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

Supported: worldwide (empty) or US. Unsupported combinations are rejected without charge.

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

Analysis window. Launch catalog: past 12 months.

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

Google Trends category id. Launch catalog: 0 (all categories).

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

Google search surface. Launch catalog: web search.

## `maxCostUsd` (type: `integer`):

Hard pre-run charge cap in whole cents is not supported by Apify schema; this is USD. Run is rejected without charge if the keyword count would exceed this.

## `mode` (type: `string`):

calibrate = normal customer run. build-bank = owner-only anchor bank maintenance.

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

Apify proxy settings. Default: US residential (required — Google Trends rate-limits datacenter IPs).

## `bankSpec` (type: `object`):

Owner-only: anchor candidate terms and dimensions for bank construction.

## Actor input object example

```json
{
  "keywords": [
    "chatgpt",
    "claude ai",
    "perplexity ai",
    "gemini ai",
    "copilot"
  ],
  "geo": "",
  "timeframe": "today 12-m",
  "category": 0,
  "property": "web",
  "maxCostUsd": 3,
  "mode": "calibrate",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# 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": [
        "chatgpt",
        "claude ai",
        "perplexity ai",
        "gemini ai",
        "copilot"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("george.the.developer/calibrated-google-trends-api").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": [
        "chatgpt",
        "claude ai",
        "perplexity ai",
        "gemini ai",
        "copilot",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("george.the.developer/calibrated-google-trends-api").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": [
    "chatgpt",
    "claude ai",
    "perplexity ai",
    "gemini ai",
    "copilot"
  ]
}' |
apify call george.the.developer/calibrated-google-trends-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,george.the.developer/calibrated-google-trends-api"
        }
    }
}

```

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/N4hAsabiND1JYaZH5/builds/tQ5SJJuX9G8y5B49V/openapi.json
