Google Trends data without pytrends 429 errors
pytrends reads Google Trends from your own IP address. When Google decides that address sends too many requests, it answers HTTP 429 and pytrends raises TooManyRequestsError ("The request failed: Google returned a response with code 429"). Its issue tracker has many reports of this, for example #561, #573, #614 and #625. The repository is archived on GitHub (read-only), and its README asks for new maintainers.
arcadium/google-trends runs in the Apify cloud instead of on your machine. It reads the same public Google Trends data through Apify proxies, retries a refused keyword on a fresh connection, and returns one JSON row per keyword: interest over time, interest by region, related queries and related topics. Your code makes one call to the Apify API and never talks to Google itself. Google can still refuse requests on a bad day; then a keyword takes longer, and if every retry fails it comes back as a free row with code: "blocked" while the other keywords still arrive.
Cost of this example on the Apify Free plan: 2 × $0.0027 = $0.0054 ($0.00054 on the Apify Gold plan and higher), plus $0.00005 per GB of memory when the run starts.
What you need
- An Apify account (the Free plan works) and its API token in
APIFY_TOKEN: Apify Console, Settings, API & Integrations. - Python 3.9 or newer with
apify-client3.x (andpandasfor the DataFrame example), or any HTTP client.
Step 1: Run it in the Apify Store (no code)
- Open arcadium/google-trends on the Apify Store and sign in (a free Apify account works).
- Enter the keywords in Search terms, one per line (the
kw_listyou passed to pytrends). - Turn on Compare keywords on one scale, which is what one pytrends
build_payloadcall gives. - Set Location to a country code such as
US(pytrendsgeo), or leave it empty for worldwide. - Optional: set a maximum cost per run in the run options as a hard cap.
- Click Start. Rows appear in the Output tab; export them as JSON, CSV or Excel.
The same input as JSON (paste it in the JSON tab of the input form):
{
"searchTerms": [
"python",
"javascript"
],
"compareKeywords": true,
"geo": "US",
"includeRelatedQueries": true,
"includeRelatedTopics": false,
"includeGeo": false
}
Step 2: Call it over the Apify API
One HTTP call runs the actor and returns its rows. It suits runs up to about five minutes; for longer runs start the run asynchronously and read the dataset when it finishes.
curl -X POST "https://api.apify.com/v2/acts/arcadium~google-trends/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"searchTerms":["python","javascript"],"compareKeywords":true,"geo":"US","includeRelatedQueries":true,"includeRelatedTopics":false,"includeGeo":false}'
Step 3: Run it from Python
With apify-client 3.x, call() returns a Run object: read the dataset id as run.default_dataset_id (older examples use run["defaultDatasetId"], which no longer works).
# pip install "apify-client>=3" pandas
import os
import pandas as pd
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
# pytrends: build_payload(["python", "javascript"], geo="US"), then interest_over_time()
run = client.actor("arcadium/google-trends").call(run_input={
"searchTerms": ["python", "javascript"],
"compareKeywords": True,
"geo": "US",
"includeRelatedQueries": True,
"includeRelatedTopics": False,
"includeGeo": False,
})
series, rising = {}, {}
# apify-client 3.x returns a Run object: use run.default_dataset_id, not run["defaultDatasetId"]
for row in client.dataset(run.default_dataset_id).iterate_items():
if row.get("rowType") != "trend":
print("not delivered:", row.get("code"), row.get("message")) # free row, e.g. code "blocked"
continue
points = row.get("interestOverTime_timelineData") or []
series[row["searchTerm"]] = pd.Series(
[p["value"][0] for p in points],
index=pd.to_datetime([int(p["time"]) for p in points], unit="s"),
)
rising[row["searchTerm"]] = [q["query"] for q in row.get("relatedQueries_rising") or []]
# Same shape as pytrends interest_over_time(): one column per keyword, 0-100 on one scale
df = pd.DataFrame(series)
print(df.tail())
print(rising)
Step 4: Run it from JavaScript
// npm install apify-client
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('arcadium/google-trends').call({
"searchTerms": [
"python",
"javascript"
],
"compareKeywords": true,
"geo": "US",
"includeRelatedQueries": true,
"includeRelatedTopics": false,
"includeGeo": false
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
Step 5: Ask an AI assistant (MCP)
Apify’s hosted MCP server turns the actor into one tool. Add it to Claude, Cursor, VS Code or any MCP client:
{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com/?tools=arcadium/google-trends",
"headers": {
"Authorization": "Bearer <APIFY_TOKEN>"
}
}
}
}Then ask, for example:
Use arcadium/google-trends to compare US search interest in "python" and "javascript" over the past 12 months on one scale, and list the rising related queries of each.
More clients and the local setup on the MCP page.
Plain HTTP from Python (no client library)
One POST to the run-sync-get-dataset-items endpoint starts the run, waits for it and returns the rows as a JSON array. It suits runs up to about five minutes; for long keyword lists use apify-client as above, which waits as long as the run takes.
# pip install requests
import os
import requests
resp = requests.post(
"https://api.apify.com/v2/acts/arcadium~google-trends/run-sync-get-dataset-items",
headers={"Authorization": "Bearer " + os.environ["APIFY_TOKEN"]},
json={"searchTerms": ["python", "javascript"], "compareKeywords": True, "geo": "US"},
timeout=330,
)
resp.raise_for_status()
for row in resp.json():
if row.get("rowType") == "trend":
print(row["searchTerm"], row["averageInterest"])
else:
print("not delivered:", row.get("code"), row.get("message"))
From pytrends calls to actor input
build_payload(kw_list, cat, timeframe, geo, gprop) becomes searchTerms, category (a category id from the input list), timeRange (presets such as today 5-y or now 7-d; leave it empty for the past 12 months) or customTimeRange (2024-01-01 2024-12-31), geo and property (images, news, youtube, froogle, the same values as gprop). interest_over_time() is interestOverTime_timelineData, interest_by_region() is interestBy, interestBySubregion or interestByCity (keep includeGeo on; Google picks the level), related_queries() is relatedQueries_top and relatedQueries_rising, and related_topics() is relatedTopics_top and relatedTopics_rising. For the trending searches of a country, send trendingGeos such as ["US"]: one row per trending search.
More than five keywords
Google compares at most five keywords per request, so pytrends code often loops over batches and rescales them by hand. With Compare keywords on one scale on, the actor does the batching with a shared anchor keyword and puts every keyword on one 0-100 scale; scale on each row says whether the values are exact (up to five keywords) or approximate, with the estimated error.
Output fields
Every row has a rowType; filter on it first. Failed inputs come back as free rows with a code and message. The fields this tutorial uses, from the actor’s dataset schema:
| Field | What it holds |
|---|---|
rowType | Kind of row. Filter on this first. 'trend': one keyword (charged). 'trending': one Trending now search (charged). 'error': an input could not be read; see code and message (free). |
searchTerm | The keyword this row is about. |
geo | Location measured: country or region code, empty for worldwide. |
timeRange | Period as Google writes it, for example today 12-m. |
averageInterest | Mean of the timeline values on the 0-100 scale. |
interestOverTime_timelineData | Timeline points with time (Unix seconds), formattedTime, value (0-100), hasData and isPartial. |
relatedQueries_rising | Rising related queries; formattedValue is such as +250% or Breakout. |
scale | How values are scaled: method (single, compare, anchor), keywords, anchor and factorErrorPct. |
code | Error rows: machine-readable error code. |
All fields: Google Trends Scraper guide.
Cost per run
Pay per event: you pay for the rows delivered, at the actor’s live Apify price on 2026-11-02. Error, coverage and summary rows are free.
| What | Rows | Price per row (Free plan) | Subtotal |
|---|---|---|---|
| Result row: 2 keywords, one row each | 2 | $0.0027 | $0.0054 |
Cost of this example on the Apify Free plan: 2 × $0.0027 = $0.0054 ($0.00054 on the Apify Gold plan and higher), plus $0.00005 per GB of memory when the run starts. Higher Apify plans pay less per row. The Store page always has the current price.
Limitations
- The actor does not remove Google’s rate limit; it moves the requests off your IP and retries them. On a day when Google limits the shared proxy pool, a long keyword list takes a few minutes longer, and a keyword that still fails is a free
blockedrow: run it again later. - Values are relative interest (0-100), not search counts, the same as in pytrends.
- Public Google Trends data only. Nothing behind a login.
- Google shows relative interest (0-100), not search counts. Trending now gives a volume bracket (for example 100,000+), not an exact number.
- Google sometimes answers "too many requests". The actor retries on the same or a fresh connection, repeats the lookup for new tokens, and after a short pause tries the whole keyword again, so a keyword may take a few seconds (rarely a minute) longer; if all retries fail it returns a free
blockedrow. - If three keywords in a row still end
blocked, Google is limiting the whole shared connection pool. The actor then pauses for about a minute once and starts the next keywords at most every 5 seconds, so it does not prolong the limit for this run or for other users. After three successful keywords in a row it returns to normal speed. Expect a long list to take a few minutes longer on such a day rather than fail. - Related topics are empty for many rare keywords; that is Google's answer, not a failure.
- Values for more than 5 compared keywords are approximate (see above).
Links
- Google Trends Scraper: fields, price table and sample output
- How to use Google Trends Scraper: every input field, n8n, LangChain
- arcadium/google-trends on the Apify Store
- All tutorials