Google Trends Scraper — interest over time, regions, related avatar

Google Trends Scraper — interest over time, regions, related

Pricing

from $0.10 / 1,000 results

Go to Apify Store
Google Trends Scraper — interest over time, regions, related

Google Trends Scraper — interest over time, regions, related

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.

Pricing

from $0.10 / 1,000 results

Rating

0.0

(0)

Developer

Juan Carlos Gutiérrez Huérfano

Juan Carlos Gutiérrez Huérfano

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

24 days ago

Last modified

Share

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:

ResponseWhat most clients doWhat this does
429Back off and retry the same dead session, often foreverRe-warm the session, then retry
5xxRetryRetry with capped backoff
Other 4xxRetryFail immediately — it will not fix itself
TimeoutHang, or retry without a ceilingAbort 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

{
"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.

{
"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"
}
{
"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

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.