Google Trends Scraper — Interest Over Time & By Region
Pricing
$0.05 / 1,000 per interest row returneds
Google Trends Scraper — Interest Over Time & By Region
Scrape Google Trends search interest as flat, spreadsheet-ready rows: one per keyword and date, one per keyword and region. Any country or subregion, any timeframe from the last hour back to 2004, and web / news / YouTube / image / shopping search. No browser, no API key, no login.
Pricing
$0.05 / 1,000 per interest row returneds
Rating
0.0
(0)
Developer
Scrapers Delight
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Share
Search interest from Google Trends, delivered flat: one row per search term and date, and one row per search term and region. Load it straight into a spreadsheet, a BI tool or a database — no unnesting, no JSON surgery.
Why this one
Every other Google Trends actor hands you one item per keyword with the raw Google JSON nested inside it. Export that to CSV and the whole 12-month timeline arrives as an unusable blob in a single cell.
This one flattens it. One keyword over the past 12 months = 53 tidy rows, each with an ISO date, the 0-100 interest value, and the series average alongside it so you can see at a glance whether a week was above or below par.
It also runs on plain HTTP, no browser. Measured 2026-09-02 on a live Apify run: 3 keywords × 2 countries, both slices, 483 rows in 18.8 seconds and 24 HTTP requests. For comparison, Apify's own google-trends-scraper runs a headless browser at 4 GB, and its public 30-day run scoreboard on the same day read 5,348 TIMED-OUT and 442 FAILED out of 20,571 runs — 28% of runs did not succeed.
Quick start
{"searchTerms": ["bitcoin", "ethereum", "solana"],"geos": ["US", "GB"],"timeframe": "today 12-m","includeInterestOverTime": true,"includeInterestByRegion": true}
Every term is scraped once per location, so 3 terms × 2 countries = 6 queries.
Output
Two row shapes in one dataset, told apart by dataType. 27 fields, same columns on every row so a CSV export stays rectangular.
interest_over_time — the line chart, flattened
{"searchTerm": "ethereum","searchTermType": "Search term","dataType": "interest_over_time","geo": "US","timeframe": "today 12-m","timeframeLabel": "Past 12 months","timeRange": "2025-09-02 2026-09-02","granularity": "WEEK","category": 0,"searchProperty": "web","date": "2025-08-31T00:00:00.000Z","timestamp": 1756598400,"formattedTime": "Aug 31 – Sep 6, 2025","value": 74,"formattedValue": "74","hasData": true,"isPartial": false,"seriesAverage": 58.8,"comparisonGroup": null,"trendsUrl": "https://trends.google.com/trends/explore?date=today+12-m&q=ethereum&geo=US&hl=en-US","scrapedAt": "2026-09-02T04:41:50.757Z"}
interest_by_region — the map, flattened
{"searchTerm": "ethereum","dataType": "interest_by_region","geo": "US","rank": 1,"regionCode": "US-WY","regionName": "Wyoming","regionLevel": "subregion","value": 100,"formattedValue": "100","hasData": true,"latitude": null,"longitude": null,"trendsUrl": "https://trends.google.com/trends/explore?date=today+12-m&q=ethereum&geo=US&hl=en-US"}
Measured field fill
From the live Apify run above (483 rows, 3 terms × 2 countries):
| Row type | n | Fields at 100% fill |
|---|---|---|
interest_over_time | 318 | all 21 applicable fields — date, timestamp, formattedTime, value, formattedValue, hasData, isPartial, seriesAverage, granularity, timeRange, timeframeLabel, geo, searchTerm, searchTermType, dataType, timeframe, category, searchProperty, trendsUrl, scrapedAt |
interest_by_region | 165 | all 19 applicable fields — regionCode, regionName, regionLevel, rank, value, formattedValue, hasData, plus the same shared columns |
The offline regression suite replays 33 captured Google Trends responses across 11 query shapes (worldwide, country, US state, custom date range, hourly, per-minute, YouTube, News, a finance category, forced City detail, and a 3-way comparison) through this exact parser: 1,456 rows, 0 duplicates, 0 dropped records, every integrity assertion passing.
Fields that are null by design, not by failure:
| Field | Where it fills | Why |
|---|---|---|
date / timestamp / formattedTime / granularity / seriesAverage | over-time rows only | a region has no date |
regionCode / regionName / regionLevel / rank | region rows only | a date has no region |
latitude / longitude | City detail only (2% of region rows) | Google returns coordinates instead of a region code for cities |
regionCode | 98% of region rows | city rows have coordinates, not a code |
geo | "" means Worldwide | an empty string is the real Google Trends value for worldwide |
comparisonGroup | comparison mode only | lists the terms a row was normalised against |
The one thing you must understand about Google Trends numbers
The value is 0-100, relative, and scaled to the peak of whatever you asked for. It is not a search volume.
- By default each term is scraped on its own, so its 100 is its own busiest period. Term A's 80 and term B's 80 are not comparable.
- Set
compareKeywords: trueand up to 5 terms go into ONE Google query, scaled to a single shared peak — then they are directly comparable ("brand A ran at 3× brand B all year"). Rows from a comparison carry the group incomparisonGroup.
Two more things the raw API will bite you with, both surfaced as fields here:
isPartial: truemarks the final period, which is still being collected. It almost always looks like a crash. Filter it out before you charting a trend line.hasData: falsemeans Google has no data for that point — not an interest of zero.
What you can control
| Input | What it does |
|---|---|
searchTerms | The keywords. Each is one query per location. |
geos | "" = Worldwide, US / GB / DE = country, US-CA / GB-ENG = state or region. Pass several. |
timeframe | Past hour → 2004-present. Google picks the granularity: per minute, per 8 minutes, hourly, daily, weekly or monthly. |
customTimeRange | 2024-01-01 2024-12-31 — any window back to 2004. |
category | A Google Trends topic ID (7 Finance, 45 Health, 174 Autos…) to strip out same-spelling searches from other subjects. |
searchProperty | Web, Images, News, YouTube or Google Shopping — the same term measured on a different Google surface. |
regionResolution | Automatic, or force Metro / City detail. |
compareKeywords | Scale up to 5 terms against a single shared peak. |
includeRegionsWithNoData | Keep the blank regions Google pads its map with (see below). |
maxItems | Hard row cap; the run stops cleanly at it. |
maxConcurrency | Queries in flight. Default 2 — see rate limits. |
Blank regions are dropped, and not charged
Google pads the region list with entries it explicitly flags as having no data. Measured 2026-09-02: a worldwide query returned 250 countries of which 54 carried data, and forced City detail returned 200 cities of which 10 did. Shipping — and billing you for — 190 blank city rows is not a result set, so they are dropped by default and the count is reported in the run status. includeRegionsWithNoData puts them back if you are drawing a map that needs every shape.
Pricing
Pay per row. Nothing else — no run fee, no platform-usage surcharge on top.
| Event | Price | When it fires |
|---|---|---|
trend-row | $0.00005 | One per interest row delivered to your dataset |
That is $0.05 per 1,000 rows. A full 12-month weekly series for one keyword is 53 rows = $0.0027; add interest by region for a US query and it is 104 rows = $0.0052. Tracking 500 keywords weekly, time series only, costs about $1.33 a month.
Rows are billed through Apify's gated pushData, so at a spend cap the run stops cleanly and you are never charged for rows you did not receive.
Rate limits — the honest version
Google Trends rate-limits hard, and this is the only real difficulty in the lane. Measured 2026-09-02 over 20 keywords (US, past 12 months):
| Setup | First-attempt success |
|---|---|
| Fresh session per query, 1 at a time | 18 / 20 |
| One shared session, 1 at a time | 15 / 20 |
| Fresh session per query, 4 at a time | 8 / 20 |
So the actor takes a fresh cookie jar and a fresh proxy session for every query, and again for every retry, backs off exponentially, and retries a rate-limited query up to four times. On the live 6-query Apify run above it absorbed zero 429s across 24 requests.
If a query is still rate-limited after all four attempts it is counted and named in the run status message as RATE-LIMITED, never silently reported as "this keyword has no data". Re-run those terms, or lower maxConcurrency.
Apify Proxy is on by default and should stay on — the session rotation is the entire defence. Any proxy group works; no residential requirement was measured (the same call returned 200 through Apify's automatic, datacenter, static-US and residential pools, and from an unproxied home IP).
Notes / honest limits
- No login, no API key, no browser. Two HTTP hops per query against Google's own public Trends endpoints. A cookie-minting warm request comes first — without it Google answers 429 from every IP, which is what makes this lane look harder than it is.
- Related queries and related topics are not in this actor. They are a different job (keyword ideation) with a different row shape, and mixing them into this dataset would wreck the flat CSV. They belong in a sibling actor.
- This is relative interest, not search volume. Google does not publish absolute query counts anywhere, and any tool claiming to give you them from Trends is modelling, not measuring.
- Google re-samples. Trends is built from a sample of searches, so the same query re-run tomorrow can differ by a point or two. That is Google, not the scraper.
now 1-H/now 4-Hdata has a short shelf life — Google keeps the per-minute series for only the recent window, so schedule those runs rather than backfilling them.- Sub-country locations only go so deep. A country gives states/provinces, a US state gives metro areas, and City detail returns Google's top 200 cities — most of which are usually blank (see above).