Google Trends Scraper: Interest Over Time, Regions & Queries
Pricing
Pay per event
Google Trends Scraper: Interest Over Time, Regions & Queries
Google Trends interest over time, interest by region, related queries and daily trending searches for any term, region and time range. Handles Google's 429s by rotating residential sessions with a fresh cookie; a blocked or failed query is never charged.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Changefeeds Tools
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Share
Google Trends Scraper: Interest Over Time, Regions & Related Queries
A Google Trends scraper and Google Trends API alternative in one actor. Give it search terms and get back, per term and region, the same data the trends.google.com Explore page shows: interest over time, interest by region (countries, states or metros), and top and rising related queries. It can also export today's trending searches for a country. It reads the JSON endpoints the Trends page itself calls. No Google account, cookie or API key needed.
Google Trends is quick to answer automated clients with HTTP 429 ("too many requests" or "unusual traffic"). Reliability is what this actor is built around:
- Requests go through Apify residential proxy by default.
- Google's first-contact 429 (the one that hands out an
NIDcookie) is recognised as a cookie handshake and retried at once with that cookie. - A real block (a 429 "unusual traffic" page, or a redirect to Google's consent or sorry page) makes the actor wait, move to a new proxy session (a new IP) with a new cookie of its own, and try again. It does this up to 8 times, with backoff that grows between attempts.
- A query that is still blocked after that becomes a free
blockedstatus row and the run moves on. You are never charged for it, and one bad query never fails the whole run.
It never tries to solve Google's CAPTCHA.
Who it's for
SEO and content teams checking whether a topic is growing, market researchers comparing brands or products across countries, analysts who need Trends series in a spreadsheet or pipeline, and anyone whose pytrends script keeps dying on 429s.
Input
| Field | Type | Default | What it does |
|---|---|---|---|
searchTerms | list of strings | — | Terms to look up. You can also paste trends.google.com/trends/explore?q=... URLs: a URL is fetched with its own terms, geo, date, cat and gprop, and the terms in one URL are compared together. Topic ids like /m/0dl567 work too. |
compareTogether | boolean | false | Off: each term is queried alone (scaled 0-100 against itself). On: terms are compared in groups of up to 5 (Google's limit), one row per group, scaled against each other. |
geo | string | "" | Empty = worldwide. A country (US), subregion (US-CA, GB-ENG) or metro (US-CA-807). Comma-separated codes give one row per term per region. |
timeRange | select | today 12-m | 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 (2004 to present). A custom YYYY-MM-DD YYYY-MM-DD range is also accepted. |
category | integer | 0 | A Trends category id (the cat= value in a Trends URL). 0 = all categories. |
property | select | "" | "" web search, images, news, froogle (Google Shopping), youtube. |
includeRegions | boolean | true | Add interest by region. One extra request per row. |
includeRelated | boolean | true | Add top and rising related queries (and related topics for single terms). |
trendingNow | boolean | false | Also output today's trending searches for each region in geo (US when geo is empty). Works with no search terms. |
proxy | proxy | residential, US | Leave on. Turn off only when running from your own network. |
Example: three AI assistants compared in the US over 90 days.
{ "searchTerms": ["chatgpt", "gemini", "claude"], "compareTogether": true, "geo": "US", "timeRange": "today 3-m" }
Output
trend rows
One row per term (or per compared group) per region:
Built from Google's live responses for the prefill input (chatgpt, US,
today 3-m) on 2026-09-30, trimmed:
{"type": "trend","input": "chatgpt","terms": ["chatgpt"],"term": "chatgpt","compared": false,"geo": "US","timeRange": "today 3-m","resolvedTime": "2026-06-30 2026-09-30","resolution": "DAY","category": 0,"property": "","timeline": [ // 93 daily points; last 3 shown{"time": 1790553600,"date": "2026-09-28T00:00:00.000Z","formattedTime": "Sep 28, 2026","value": 72,"values": [72],"isPartial": false},{"time": 1790640000,"date": "2026-09-29T00:00:00.000Z","formattedTime": "Sep 29, 2026","value": 71,"values": [71],"isPartial": false},{"time": 1790726400,"date": "2026-09-30T00:00:00.000Z","formattedTime": "Sep 30, 2026","value": 71,"values": [71],"isPartial": true}],"averages": null,"regions": [ // 51 (50 states + DC); top 3 shown{"geoCode": "US-CA","geoName": "California","value": 100,"values": [100]},{"geoCode": "US-GA","geoName": "Georgia","value": 88,"values": [88]},{"geoCode": "US-DC","geoName": "District of Columbia","value": 87,"values": [87]}],"regionResolution": "REGION","relatedQueries": {"top": [ // 25; first 2 shown{"term": "chatgpt","query": "what is chatgpt","value": 100,"formattedValue": "100","link": "https://trends.google.com/trends/explore?q=what+is+chatgpt&date=today+3-m&geo=US"},{"term": "chatgpt","query": "chatgpt ai","value": 98,"formattedValue": "98","link": "https://trends.google.com/trends/explore?q=chatgpt+ai&date=today+3-m&geo=US"}],"rising": [ // 4; first 2 shown{"term": "chatgpt","query": "chatgpt astra","value": 1800,"formattedValue": "+1,800%","link": "https://trends.google.com/trends/explore?q=chatgpt+astra&date=today+3-m&geo=US"},{"term": "chatgpt","query": "chatgpt student offer","value": 350,"formattedValue": "+350%","link": "https://trends.google.com/trends/explore?q=chatgpt+student+offer&date=today+3-m&geo=US"}]},"relatedTopics": {"top": [],"rising": []},"exploreUrl": "https://trends.google.com/trends/explore?date=today+3-m&geo=US&q=chatgpt&hl=en-US","notes": ["Google returned an empty related-topics list for \"chatgpt\"."],"noData": false,"scraped_at": "2026-09-30T05:06:10.000Z"}
timeline[].valueis the 0-100 interest for a single-term row. In a comparison row it isnull: usevalues, one number per entry interms, in the same order. A bucket Google marks as having no data isnull, not0.isPartial: truemarks the latest, still-incomplete bucket.averagesis Google's per-term average for the range. Google only returns it for comparisons; single-term rows havenull.regionsin a single-term row are 0-100 relative to the top region. In a comparison row they are Google's "compared breakdown": each term's share of that region's searches among the compared terms, in percent. Regions with no data are left out.regionResolutiontells you whether these are countries, regions (states) or DMA metros.relatedQueries.topvalues are 0-100 relative.risingvalues are the percent increase. Google labels increases over 5000 % as "Breakout", and that label is kept informattedValue. Every item carries thetermit belongs to, so comparison rows stay flat.notessays why a field is empty. For example, the term has too little search volume, or Google returned no related-topics widget.exploreUrlopens the same comparison on trends.google.com.
When Google has no data at all for a query (a very rare term, say), you
still get the row, with empty fields and a note. It has noData: true and
is not charged.
trending rows
From a live run (trendingNow: true, geo: "US,GB", 2026-09-30: 10 rows per
region), trimmed:
{"type": "trending","geo": "US","title": "evan bouchard","approxTraffic": "500+","approxTrafficMin": 500,"pubDate": "2026-09-30T05:00:00.000Z","picture": "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcRY6KarQldgLOIhenYpGzGBRIYWIYlvzsuSV99HI_Y-MKGguer3T3D_orYEPAo","pictureSource": "NHL.com","newsItems": [ // 3; first shown{"title": "Bouchard earns second hat trick","snippet": null,"url": "https://www.nhl.com/video/van-edm-bouchard-has-a-hat-trick-against-the-canucks-6405939435112","picture": "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcRY6KarQldgLOIhenYpGzGBRIYWIYlvzsuSV99HI_Y-MKGguer3T3D_orYEPAo","source": "NHL.com"}],"exploreUrl": "https://trends.google.com/trends/explore?date=now+1-d&geo=US&q=evan+bouchard&hl=en-US","scraped_at": "2026-09-30T05:25:34.633Z"}
status rows (free)
Problem inputs get a free status row instead of data:
{ "type": "status", "input": "chatgpt", "mode": "trend", "geo": "US", "status": "blocked", "error": "HTTP 429: Google reported unusual traffic from this IP; still blocked after 8 proxy rotations", "checked_at": "2026-09-30T05:25:18.678Z" }
status is one of:
invalid: the entry is not a usable term, Trends URL or geo code, or Google rejected the query with HTTP 400 (bad category or time range).blocked: Google kept answering 429 after every retry and rotation.not_found: the trending feed for a region had no items.error: any other HTTP or network failure.
The key-value store record OUTPUT holds a run summary: rows by type,
charged counts per event, stopped_reason, truncated_inputs (what was
skipped because the budget ran out), HTTP requests, retries and proxy
rotations. Every run writes at least one dataset row.
Pricing
Pay per event. Proxy traffic is included in the price.
- $0.004 per trend row (
trend-returned), which is $4 per 1,000 rows. One row covers a term (or a group of up to 5 compared terms) in one region, with its timeline, regions and related queries. - $0.001 per trending row (
trending-returned).
You are never charged for status rows (blocked, invalid, error), for
no-data trend rows, or for the run_info row. If you set a maximum total
charge, the actor checks the remaining budget before every query. It never
fetches a row it could not bill, stops cleanly when the budget is used up,
and records "max_total_charge_reached" in OUTPUT.stopped_reason.
Limits, stated plainly
- Google's numbers are relative, not search counts. Every value is
scaled 0-100 within its own query. The same term queried alone and inside
a comparison gives different numbers, and so do two runs with different
time ranges. Compare terms in one row (
compareTogether) when you need them on one scale. - Google's own sampling. Trends data is a sample. Re-running the same query can shift values by a point or two, and very small terms flicker between some data and none.
- Related topics usually come back empty. In every live test so far
(September 2026), Google returned an empty related-topics list to this
client, for big terms like "chatgpt" and "tesla" too. The actor passes on
whatever Google returns and says so in
notes, but do not count on topics. Google's comparison view has no related topics at all. - Trending searches are Google's RSS feed. That is about 10 current
trending searches per region, with approximate traffic buckets like
"500+"and a few news links each. It is not the full trending list on the trends.google.com website and not historical. - Blocking can still win. Rotation and backoff are the whole point of
this actor, but they cannot guarantee that Google answers. A query that is still blocked after
8 new sessions is reported as
blocked(free), not retried forever. Runs without a proxy (your own IP) only back off and retry 3 times. - It stays polite. It runs one query at a time with at least 0.5 s between requests. 5xx errors are retried with backoff, and Retry-After is honoured up to 60 s.
FAQ
Do I need a Google account or API key? No.
Why are my numbers different from the website? Check that you use the
same region, time range, category and search type (the exploreUrl on each
row opens exactly what was queried), and that you compared the same terms
together. Google also re-samples, so small differences are normal.
Can I get more than 5 terms on one scale? Google compares at most 5 terms at a time. A common workaround is to include one anchor term in every group and rescale the groups against it. The actor does not do that for you.
Is this the official Google Trends API? No. It reads the same public endpoints the trends.google.com page uses.
Troubleshooting: pytrends 429 TooManyRequestsError
If you're using pytrends and hitting ResponseError: The request failed: Google returned a response with code 429 (TooManyRequestsError), you're experiencing one of the library's longest-running issues. It happens whether you're making one request or a thousand, and it has shown up for years.
Why it happens
pytrends is an unofficial wrapper: there is no public Google Trends API. The library reverse-engineers the requests the trends.google.com website itself makes, which means every call is an anonymous, cookie-less request against an interface Google didn't design for programmatic use. Google rate-limits that pattern aggressively — by IP and seemingly by request velocity and shape.
Free fixes
- Add retries with backoff at construction time:
from pytrends.request import TrendReqpytrends = TrendReq(hl='en-US',tz=360,retries=2,backoff_factor=0.5,)
- Space out requests and batch fewer keywords per call (Google Trends compares up to 5 terms at a time).
- Route through your own proxy list.
TrendReqaccepts aproxiesparameter directly; use rotating residential proxies to avoid quick blocks on static IPs. - Add jitter and cache aggressively. If pulling the same terms repeatedly, cache results and only re-fetch what's stale. Every avoided request is one less chance to get rate-limited.
None of this guarantees success — Google's limiter looks at more than raw request count. Re-test whenever Google's backend changes (which it does without notice, as the README itself warns).
This actor handles it
This actor uses residential proxy with per-session cookie handling and rotation, retrying through fresh IPs up to 8 times with backoff. Blocked or no-data queries are not charged.
Local development
pnpm --filter @mmnm/gtrends test # unit tests, no networkpnpm --filter @mmnm/gtrends build
node src/main.ts runs the actor locally with Apify's local storage
(./storage). ACTOR_TEST_PAY_PER_EVENT=true ACTOR_MAX_TOTAL_CHARGE_USD=1
exercises the charging path.