The Mine Works
Browse on Apify
Google Trends API Python 2025: Why pytrends Keeps Breaking (and What to Use Instead)
← All posts
tutorial May 26, 2025 · 6 min read

Google Trends API Python 2025: Why pytrends Keeps Breaking (and What to Use Instead)

pytrends has been unreliable for years. We explain why Google Trends blocks HTTP clients, and show you three approaches that actually work in 2025.

Try the scraper

The actor referenced in this article is live on Apify. Pay only for results delivered.

Open on Apify →

If you have tried to pull Google Trends data programmatically in the last two years, you have probably watched pytrends fail in one of three ways: a 429 Too Many Requests error that does not go away even after sleeping, a ResponseError with no useful message, or — the most maddening — a response that looks successful but contains empty data arrays.

TL;DR: pytrends fails on cloud servers because Google blocks datacenter IPs from the widgetdata endpoints. The fix: call /trends/api/explore from any IP to get tokens, then route only the widgetdata calls through a residential proxy. For production, a managed scraper handles this automatically with no proxy subscription needed.

This post explains exactly why this happens and walks through every approach that works reliably in 2025.

Why pytrends Breaks

pytrends is a Python library that reverse-engineers Google Trends’ internal HTTP API. The library constructs requests to trends.google.com/trends/api/explore and the downstream widgetdata endpoints.

The fundamental problem is that Google Trends uses two different blocking mechanisms depending on which endpoint you are calling:

/trends/api/explore — This endpoint accepts requests from most IP addresses, including datacenters, as long as you have a valid session cookie (NID). pytrends seeds this cookie from a lightweight HTTP request. This part generally works.

/trends/api/widgetdata/* — This is where the actual trend data lives: interest over time, interest by region, related queries. Google blocks datacenters from hitting this endpoint. Any IP address in an AWS, GCP, or Azure range gets a 429. Residential and mobile IPs work. Datacenter IPs do not.

pytrends runs from your local machine (residential IP) and usually works for light use. When you run it from a server, a Docker container, or a cloud function — which is almost every production use case — it fails.

Approach 1: pytrends from a Residential Proxy

You can route pytrends through a residential proxy to bypass the datacenter block. This works but requires a proxy provider ($30–100/month) and adds latency.

import requests
from pytrends.request import TrendReq

session = requests.Session()
session.proxies = {
    'http': 'http://user:pass@residential-proxy.example.com:port',
    'https': 'http://user:pass@residential-proxy.example.com:port',
}

pytrends = TrendReq(requests_args={'proxies': session.proxies})
pytrends.build_payload(['machine learning'], timeframe='today 12-m', geo='US')
df = pytrends.interest_over_time()

Reliability: Medium. Residential proxies rotate IPs, and some get flagged. Expect occasional failures that require retry logic.

Approach 2: Direct HTTP with the Explore/Widgetdata Pattern

This is the approach used in our Google Trends Pro scraper. The key insight: you do not need a browser. You need the right IP for the right endpoint.

  1. Fetch NID cookie from trends.google.com — any IP works here
  2. Call /trends/api/explore to get widget tokens — any IP works here
  3. Call /trends/api/widgetdata/* endpoints using the widget tokens — requires residential IP
import requests
import json
import re

def get_trends_data(keyword, timeframe='today 12-m', geo='US', proxy=None):
    s = requests.Session()
    if proxy:
        s.proxies = {'https': proxy}
    
    # Step 1: Seed NID cookie (no proxy needed)
    s.get('https://trends.google.com/trends/', timeout=10)
    
    # Step 2: Get widget tokens (no proxy needed)
    params = {
        'hl': 'en-US', 'tz': '-330', 'geo': geo,
        'req': json.dumps({
            'comparisonItem': [{'keyword': keyword, 'geo': geo, 'time': timeframe}],
            'category': 0, 'property': ''
        }),
    }
    explore_res = s.get(
        'https://trends.google.com/trends/api/explore',
        params=params, timeout=15
    )
    # Strip XSSI prefix
    widgets = json.loads(explore_res.text.lstrip(")]}',\n"))['widgets']
    
    # Step 3: Get interest over time (residential proxy needed here)
    iot_widget = next(w for w in widgets if w['id'] == 'TIMESERIES')
    iot_res = s.get(
        'https://trends.google.com/trends/api/widgetdata/multiline',
        params={
            'hl': 'en-US', 'tz': '-330',
            'req': json.dumps(iot_widget['request']),
            'token': iot_widget['token'],
        },
        timeout=20
    )
    data = json.loads(iot_res.text.lstrip(")]}',\n"))
    return data['default']['timelineData']

Note the XSSI prefix )]}',\n — this is intentional anti-JSON-hijacking protection that Google uses. Strip it before parsing.

For scheduled runs, high keyword volumes, or any production pipeline, a managed solution is the practical choice. You define the keywords and get back structured data — no proxy management, no XSSI parsing, no token handling.

from apify_client import ApifyClient

client = ApifyClient('YOUR_API_TOKEN')
run = client.actor('themineworks/google-trends-pro').call(run_input={
    'keywords': ['python', 'javascript', 'rust'],
    'timeframe': 'today 12-m',
    'geo': 'US',
    'includeRelatedQueries': True,
})

for item in client.dataset(run['defaultDatasetId']).iterate_items():
    print(item['keyword'])
    print(item['interest_over_time'])  # [{date, value}, ...]
    print(item['related_queries']['top'])

Common Errors and What They Mean

429 Too Many Requests — You are on a datacenter IP hitting the widgetdata endpoint. Add a residential proxy or use a managed scraper.

ResponseError: The request failed — Usually a stale NID cookie. Re-fetch from trends.google.com before your widgetdata calls.

Empty timelineData array — The keyword has no meaningful trend data for the requested geo/timeframe. This is valid data — it means Google does not have enough search volume to report a trend.

XSSI parse error — You forgot to strip the )]}',\n prefix before calling json.loads().

Rate Limits

Google Trends does not publish official rate limits. In practice:

  • Per IP: ~1,400 explore requests per day, much lower for widgetdata
  • Per keyword session: Rotating to a new token/cookie every 20 keywords prevents session flagging
  • Residential proxy: Rotating IPs per request eliminates most rate limit issues

What Data You Get

A successful Trends response includes:

  • Interest over time — Weekly or daily search interest indexed 0–100 for up to 5 years
  • Interest by region — Country-level or state-level breakdown
  • Related queries — Top and rising related search terms
  • Related topics — Entity-level related searches (broader than queries)

What you cannot get: absolute search volumes (only relative indexed values), data older than 5 years, and data from Google properties other than web search (unless you use the property parameter).

Frequently Asked Questions

Why does pytrends return 429 errors from a cloud server?

Google Trends blocks datacenter IP ranges (AWS, GCP, Azure) from accessing the /widgetdata endpoints where actual trend data lives — interest over time, regional breakdowns, related queries. pytrends works on your local residential IP but fails on any cloud server. The fix is to route only the widgetdata calls through a residential proxy.

What is the XSSI prefix in Google Trends API responses?

Every Google Trends response starts with )]}',\n before the JSON. This is intentional protection against cross-site JSON hijacking. You must strip it with resp.text.lstrip(")]}',\\n") before calling json.loads(). Forgetting this prefix strip is one of the most common causes of parse errors in DIY Trends implementations.

How reliable is pytrends in 2025?

pytrends has not had a meaningful update since 2022 and is effectively unmaintained. GitHub issues consistently document 429 errors, empty data arrays, broken XSSI handling, and API format changes the library does not handle. For cloud-based or production use, pytrends is not a reliable choice.

What data does Google Trends provide?

Google Trends returns interest over time (weekly or daily values indexed 0–100 for up to 5 years), interest by region (country or state level), related queries (top and rising), and related topics. It does not provide absolute search volumes — only relative indexed values. For absolute volumes, use Google Keyword Planner.

How many Google Trends requests can you make per day?

Google does not publish official rate limits. In practice, the explore endpoint handles roughly 1,400 requests per day per IP. The widgetdata endpoints are more restrictive. Rotating to a fresh session cookie every 20 keywords and using rotating residential IPs eliminates most rate limit issues.

Related Actor

Try the scraper referenced in this article — live on Apify, pay only for results.

Open google-trends-pro on Apify →