Disclosure: I built the Google Trends Actor used in this post and I earn money when people run it. I've tried to keep this useful even if you never use it: the first half explains the 429 problem and the free workarounds.
If you've used pytrends, you've probably seen this:
pytrends.exceptions.TooManyRequestsError: The request failed: Google returned a response with code 429
It usually works on your laptop for a few requests, then starts failing. It fails faster on Colab, AWS, GitHub Actions or any other cloud IP. This post explains why, what you can do for free, and how I now get Google Trends data from Python without managing proxies.
Why pytrends gets 429s
Google Trends has no public API. pytrends calls the same internal endpoints the trends.google.com website uses (/explore, then /widgetdata/multiline, /comparedgeo and /relatedsearches). Google rate-limits those endpoints per IP and per session:
-
Every keyword is several requests. One
build_payload()plusinterest_over_time(),interest_by_region()andrelated_queries()is four or more calls. A loop over 50 keywords is 200+ requests from one IP. - Datacenter IPs are treated as bots. Cloud and shared IPs hit the limit far sooner than a home connection.
- The cookie/session matters. Requests without a warm Google session get rejected more often.
- pytrends is no longer maintained. The GitHub repo is archived and the last PyPI release (4.9.2) is from April 2023, so it won't adapt when Google changes things.
Free workarounds (try these first)
For a handful of keywords now and then, these are often enough:
-
time.sleep(60)between keywords. Slow, but it works more often than not. -
TrendReq(retries=3, backoff_factor=0.5)so transient 429s are retried. - Run from your home connection instead of a cloud VM.
- Pass rotating proxies with
TrendReq(proxies=[...]). This works, but now you're buying and managing proxies.
If you need dozens or hundreds of keywords, on a schedule, from a server, these stop being fun.
The alternative: call a hosted scraper from Python
I wrote a Google Trends scraper that runs on Apify and handles the retries for you: warm sessions, backoff, a fresh IP per retry, and a switch to residential proxies when datacenter IPs get blocked. You call it with Apify's official Python client and get one JSON item per keyword.
Every item contains interest over time, interest by region, top and rising related queries and topics, and a summary object (average, peak, latest value, rising / stable / falling and % change).
Setup
- Create a free Apify account and copy your API token (Console > Settings > API & Integrations).
- Install the client and pandas, and put the token in an environment variable:
pip install apify-client pandas
export APIFY_TOKEN=your_token_here
The code
This compares three AI assistants in the US over the past 12 months on one shared 0-100 scale, just like the compare view on trends.google.com:
import os
import pandas as pd
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run_input = {
"searchTerms": ["ChatGPT", "Gemini", "Claude"],
"geo": "US",
"timeRange": "today 12-m",
"compareTerms": True, # all three on the same 0-100 scale
}
run = client.actor("zahidthani/google-trends-scraper").call(
run_input=run_input,
max_total_charge_usd=0.10, # hard budget cap for this run
)
# apify-client 3.x returns a Run object; 1.x and 2.x return a dict
dataset_id = getattr(run, "default_dataset_id", None) or run["defaultDatasetId"]
items = list(client.dataset(dataset_id).iterate_items())
for item in items:
if item["status"] != "ok":
print("FAILED:", item["searchTerm"], item["errors"]) # failed terms are not charged
continue
s = item["summary"]
print(
f'{item["searchTerm"]:<8} avg {s["average"]:>5} latest {s["latest"]:>3} '
f'{s["trend"]:<8} {s["changePercent"]:+6.1f}% peak {s["peak"]["date"][:10]}'
)
# One column per keyword, one row per week -> CSV
frames = []
for item in items:
if item["status"] == "ok":
df = pd.DataFrame(item["interestOverTime"])[["date", "value"]]
frames.append(df.set_index("date").rename(columns={"value": item["searchTerm"]}))
timeline = pd.concat(frames, axis=1)
timeline.index = pd.to_datetime(timeline.index).date
timeline.index.name = "week"
timeline.to_csv("trends_timeline.csv")
print(timeline.tail(5))
# Rising related searches for each keyword
for item in items:
if item["status"] == "ok":
rising = [q["query"] + " (" + q["formattedValue"] + ")" for q in item["relatedQueries"]["rising"][:3]]
print(item["searchTerm"], "rising:", ", ".join(rising))
Tested with Python 3.12 and apify-client 3.2.1. The dataset_id line also covers the older 1.x/2.x clients (I checked 1.12 on Python 3.9).
Real output
From a run on 6 October 2026. The whole call took about 40 seconds. Google answered the scraper with HTTP 429 twice during the run; it retried with a fresh session and carried on, which is the part you'd otherwise write yourself.
| Keyword | Average | Latest full week | Trend | Change | Peak week |
|---|---|---|---|---|---|
| ChatGPT | 77.4 | 76 | stable | -14.2% | 2025-10-19 |
| Gemini | 27.4 | 33 | rising | +31.9% | 2026-04-19 |
| Claude | 18.7 | 21 | rising | +235.0% | 2026-04-12 |
changePercent compares the average of the last quarter of the period with the first quarter. Within ±15% counts as stable. The last five weeks of the timeline:
| week | ChatGPT | Gemini | Claude |
|---|---|---|---|
| 2026-09-06 | 77 | 32 | 18 |
| 2026-09-13 | 69 | 32 | 19 |
| 2026-09-20 | 72 | 33 | 20 |
| 2026-09-27 | 76 | 33 | 21 |
| 2026-10-04 | 79 | 34 | 21 |
The last row is the current, unfinished week (isPartial: true in the raw data), so don't read too much into it. The rising related searches came back as, for example, how to use chatgpt effectively (+1,900%), gemini 3 pro (+500%) and claude cowork (Breakout).
Exporting to CSV, Excel or Google Sheets
-
CSV: the script above writes
trends_timeline.csv(53 weekly rows, one column per keyword).timeline.to_excel("trends.xlsx")works too if youpip install openpyxl. - Google Sheets: the simplest route is File > Import > Upload in Sheets with that CSV. If you want it to update by itself, save the input as a task in Apify Console, add a schedule, and send each run's results to a sheet with Make, Zapier or n8n (Apify has integrations for all three).
- Raw JSON: every run's dataset can also be downloaded as JSON, CSV, Excel or HTML from the Apify Console.
What it costs
The Actor is pay-per-result: $0.004 per keyword (all sections included) plus $0.01 per run. Keywords that fail are not charged.
| Job | Cost |
|---|---|
| This example (3 keywords) | about $0.02 |
| 100 keywords, all sections | about $0.41 |
| 20 keywords every week | about $0.09 a run, under $0.40 a month |
Apify's free plan includes $5 of monthly credit, which is plenty to try it. max_total_charge_usd in the code is a hard cap: the run stops cleanly if it would cost more.
Limitations (read these before you rely on it)
- Values are relative, not search counts. 0-100 is scaled within your request. A keyword run alone and the same keyword in a comparison give different numbers.
- Compare is capped at 5 terms, the same as on the website. For more, put one "anchor" term in every group of five and rescale against it.
- Google samples its data. Values move by a few points between requests, and the rising lists move more: I ran the same script twice a few minutes apart and the ChatGPT rising queries were partly different. Use long time ranges and look at trends, not single points.
- Very niche keywords return no data, and related topics are sometimes empty. That mirrors the website.
-
It's a dependency on a third party (me). If Google changes its endpoints, the Actor breaks until I fix it. I monitor it daily, but if you only need a few keywords a month,
pytrendswith long sleeps costs nothing.
Try it
Google Trends Scraper on the Apify Store: https://apify.com/zahidthani/google-trends-scraper
Runnable Python, Node.js and n8n examples: https://github.com/zahidthani/apify-data-tools-examples
Top comments (0)