TL;DR: Google announced an official Trends API in July 2025 "only to a very limited number of testers". Fifteen months later the page is still titled "Alpha" and still ends in an application form. pytrends, the library most tutorials use, was archived in April 2025, and its last maintainer left a note saying Google gives bots "slightly altered data". I tested that claim on 13 October 2026: five keywords, one hour, three ways to ask Google. Interest over time and interest by region came back the same from all three, give or take 3 points. Related queries are unstable for everyone, including the website. Related topics are the one thing Google withholds from anything it tags as a scraper, and it tags sessions openly, in its own JSON.
What you can't get from any route below: absolute search volumes. Google Trends only ever gives relative interest on a 0 to 100 scale, API or not.
Status table (checked 13 October 2026)
| Source | State today |
|---|---|
| Official Google Trends API | Alpha. "As we can't open the API for everyone yet, we're prioritizing developers that know what they want to do", with an application form (API page). Announced 24 July 2025 (blog). An April 2026 Ask HN asking who got in has one reply, from someone who didn't |
| pytrends (Python) | Archived on GitHub 17 April 2025. Last release 4.9.2, April 2023. Still 908,810 PyPI downloads in the last 30 days (pypistats). 63 issues and PRs in the repo mention HTTP 429 |
| trendspy (Python) | The fork the pytrends maintainer pointed to. Last commit 25 December 2024, v0.1.6 |
| google-trends-api (npm) | Last push November 2022. 569,469 downloads in the last 30 days, 54 open issues |
| trends.google.com | Works. Compare a handful of terms, download a CSV by hand |
| Scrapers (self-built or hosted) | Work, with the caveats measured below |
The pytrends note is worth reading in full (issue #636, 8 February 2025). The maintainer's claim is that when Google detects a bot it returns "slightly altered data" compared with the website. That's a strong claim, and nobody I could find had checked it. So I did.
The test
Same five keywords (python, javascript, rust, typescript, golang), United States, past 12 months, web search. All pulled between 07:19 and 07:31 UTC on 13 October 2026, three ways:
| Who asks Google | Where from | |
|---|---|---|
| A. Website | trends.google.com in a normal browser. I read the JSON the page itself loads | My IP (Rwanda) |
| B. Plain fetch |
fetch() typed into the console of that same tab. Same cookies, same IP, same minute |
My IP (Rwanda) |
| C. Scraper | My Apify Actor (residential proxy, no cookies) | Apify's servers |
The raw responses, the Actor's output and the overlap script are in the public repo.
1. Google labels every session, and tells you
The first request on a Trends page is POST /trends/api/explore. Its response contains the request object for every widget, and inside each one is this:
"userConfig": {"userType": "USER_TYPE_LEGIT_USER"}
That is what the page got (A). The fetch() from the console of the same tab (B) got, every time:
"userConfig": {"userType": "USER_TYPE_SCRAPER"}
Same browser, same cookies, same IP, seconds apart. The widget tokens are signed against this label, so you can't edit it. Whatever the website's JavaScript sends that my fetch() didn't, Google uses it to sort requests into two bins before any data is returned. The question is what differs between the bins.
2. Interest over time: the same, within 3 points
A and B returned identical numbers. I compared python on all 53 weeks and the other four terms on 48 weeks, and found no difference. The scraper label alone does not change the time series.
The Actor (C) got a slightly different sample. Google's help pages say Trends data is a sample, and the Actor asked from a different IP ten minutes earlier. Per keyword, 53 weekly points on the shared 0 to 100 scale:
| Keyword | Points identical | Mean abs. difference | Max difference | Correlation |
|---|---|---|---|---|
| python | 16 of 53 | 1.08 | 3 | 0.997 |
| javascript | 42 of 53 | 0.21 | 1 | 0.994 |
| rust | 25 of 53 | 0.66 | 3 | 0.985 |
| typescript | 48 of 53 | 0.09 | 1 | 0.987 |
| golang | 52 of 53 | 0.02 | 1 | 0.995 |
The Actor's share-of-search ranking (python 57%, rust 28%, javascript 9%, typescript 3%, golang 2%) is the same ranking the website chart shows. For a trend line, a seasonality check or a "did interest double" question, 3 points on a 0 to 100 scale is noise.
3. Interest by region: the same, within 1 point
Website (A) versus Actor (C) for python, US states, top of the list:
| State | Website | Actor |
|---|---|---|
| Wyoming | 100 | 100 |
| Kansas | 35 | 35 |
| District of Columbia | 28 | 27 |
| Massachusetts | 24 | 24 |
| Maryland | 23 | 22 |
| Washington | 23 | 22 |
| California | 23 | 24 |
| Virginia | 22 | 22 |
All 51 regions are within 1 point. (Yes, Wyoming. Google's regional score is a share of all searches in that state, not a count.)
4. Related queries: unstable for everyone
This is where "altered data" would hide, so I pulled the top 25 related queries for python five times: the website twice (two page loads, ten minutes apart), the plain fetch twice, and the Actor once. Overlap of the 25:
| Pair | Shared queries (of 25) | Shared in the top 10 |
|---|---|---|
| Website load 1 vs website load 2 | 15 | 5 |
| Website load 1 vs plain fetch | 18 | 7 |
| Website load 2 vs plain fetch | 17 | 7 |
| Website load 1 vs Actor | 15 | 5 |
| Website load 2 vs Actor | 15 | 6 |
The website disagrees with itself as much as it disagrees with the scrapers. Two loads of the same page, ten minutes apart, shared 15 of 25 queries. "python list", "python documentation", "python news" and "python class" were in load 1 and gone in load 2. Rising queries were worse: 6 of 25 shared between the two website loads.
The head of the list is stable everywhere: "what is python" at 100, "python code" at 69 to 72, "ball python" at 40 or 41 in every pull but one. Even the wording moves: the website said "install python" on one load and "python install" on the next.
Conclusion: take the top handful of related queries from any source. Don't build anything on the tail, and don't diff two pulls and call the difference a trend.
5. Related topics: withheld from scrapers
Related topics (Google's entity list, with Freebase-style ids like /m/05z1_) are the one hard difference.
- Website (A), python, past 12 months: 20 top topics and 13 rising. Python the programming language at 100, then Data 7, Python the snake 7, Documentation 6, Computer file 5, API 5, and so on.
- Plain fetch (B), same tab, seconds later:
rankedListpresent, both lists empty. - Actor (C):
{"top": [], "rising": []}.
The topics widget appears only on single-keyword pages, by the way. On a compare page the website itself shows related queries only.
So the maintainer was right that scrapers get something different, and the something is this: no related topics. On the numbers people actually chart, I found no alteration.
What this means if you need Trends data in code
| You need | Where to get it |
|---|---|
| Interest over time, interest by region, share of search between terms | Any route. The website, a scraper, or the official API if you're in the alpha |
| Related queries | Any route, head of the list only |
| Related topics | The website, or the official API if it ever ships them. Not a scraper |
| Absolute volumes | Nowhere in Trends |
Hosted version
Google Trends Scraper on Apify is source C above. One record per keyword: the time series with a trend direction and recent change, the region table, related queries with breakouts flagged, and a share-of-search ranking when you compare terms. It also chains batches so you can compare more than five keywords on one scale. It can't return related topics, for the reason in section 5, and it says so in the README.
Disclosure: I built this. It costs $4 per 1,000 keyword reports and $0.50 per 1,000 Trending Now items. Keywords Google has no data for aren't charged. Apify's free plan includes $5 of credit a month.
With apify-client 3.x (pip install apify-client):
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("kuezi/google-trends-scraper").call(logger=None, run_input={
"keywords": ["python"],
"geo": "US",
"timeRange": "today 12-m",
"includeInterestOverTime": True,
"includeInterestByRegion": True,
"includeRelatedQueries": True,
"includeRelatedTopics": True,
})
for item in client.dataset(run.default_dataset_id).iterate_items():
print(item["keyword"], item["status"], "avg", item["average"], "latest", item["latestValue"], item["direction"])
print(" top region:", item["topRegion"])
print(" top queries:", [q["query"] for q in item["relatedQueries"]["top"][:5]])
print(" rising:", [q["query"] for q in item["relatedQueries"]["rising"][:3]])
print(" related topics:", item.get("relatedTopics"))
print(run.status_message)
Output from the real run on 13 October 2026 (25 seconds, $0.004):
python ok avg 62.5 latest 37 falling
top region: Wyoming
top queries: ['what is python', 'python code', 'ball python', 'install python', 'python online']
rising: ['walmart near me', 'python swallowing deer india', 'singapore reticulated python sighting']
related topics: {'top': [], 'rising': []}
Done: 1/1 keywords.
The empty topics are the honest output, not a bug. A TIMELINE_WIDE.csv with one column per keyword lands in the run's key-value store for Sheets or pandas.
What this doesn't give you
- Search volumes. 100 means the peak for that term, place and period. Nothing more.
- Related topics. See section 5.
- A guarantee the sample matches the website to the point. Expect a 1 to 3 point spread on the time series.
If you've compared Trends output across sources in a different way, or you're in the API alpha and can say what it returns, post it in the comments and I'll add it to the tables.
AI disclosure: I wrote this with AI assistance. Every quote, number and output above was checked or run on 13 October 2026.
Top comments (0)