~ / guides / How to Scrape Google Trends (Python Guide)

How to Scrape Google Trends (Python Guide)

MR
Marcus Reed
Founder & lead tester · about the author
the short version
  • There is no official Google Trends API. The public explore page (trends.google.com/trends/explore) blocked a plain cookieless request with HTTP 429 on my June 12, 2026 run.
  • The daily trending RSS feed (trends.google.com/trending/rss) is the easy DIY win: I hit it and got HTTP 200 and 10 trending searches with traffic estimates and linked news, no key.
  • Interest-over-time numbers come from an undocumented widget API. With a session cookie it returned 200 and 53 weekly datapoints (0-100 scale), but it is unofficial and rate-limited.
  • Trends values are a relative 0-100 index, not raw search counts, and the data is sampled, so two pulls of the same query can differ slightly.
  • For absolute search volume, related-query tables at scale, or no rate-limit babysitting, use a scraper API like ChocoData or a keyword tool with its own volume data.

Google Trends is the fastest free read on what the world is searching for, which makes it a staple for SEO research, content planning, market timing, and trend-spotting. This guide builds a real Python scraper, tests it against the live service, and is honest about which doors are open. I ran every request below on June 12, 2026 and pasted the real output. The short version: the explore page slams plain requests with a 429, but Google’s own trending RSS feed and its undocumented widget API both handed me data. For the fundamentals, see my web scraping guide and the Python walkthrough.

You can pull public Google Trends data with a script, and the legal risk depends on the method and what you store. The technical reality first: the visible explore page at trends.google.com/trends/explore rate-limits no-cookie clients, so a naive request gets a 429. Two other paths work, both tested below. The legal side has two layers worth separating.

On US computer-access law, reading pages anyone can load without logging in sits on the safer side. In hiQ Labs v. LinkedIn, the Ninth Circuit held that scraping public data does not violate the Computer Fraud and Abuse Act, and the Supreme Court’s Van Buren decision narrowed the CFAA toward traditional hacking. Requesting a public RSS feed is not, by itself, a federal computer crime.

Two further constraints apply. Contract law is the live risk: Google’s Terms of Service restrict automated access, and hiQ won the CFAA question yet still paid a $500,000 judgment for breaching a user agreement. Database and copyright protection is the second: the trend index values are factual measurements with thin protection, but the surrounding page content and the news articles linked from the feed are owned by others. Stay on public endpoints, never script a logged-in Google account, throttle your requests, and keep what you collect to the trend figures and metadata. I am a tester, not a lawyer, so get advice before a commercial scrape. My is web scraping legal pillar covers the full picture.

Google Trends exposes four distinct datasets, and they live behind different endpoints with different reliability. Knowing which source holds what tells you when DIY is enough and when you need an API.

DatasetSource endpointWhat you getReliability
Daily trending searchestrending/rss (RSS/XML)Hot queries, traffic estimate, linked newsHigh, returned 200 for me
Interest over timeapi/widgetdata/multiline (JSON)0-100 index per time bucketMedium, undocumented + rate-limited
Interest by regionapi/widgetdata/comparedgeo (JSON)0-100 index per state/countryMedium, undocumented
Related topics / queriesapi/widgetdata/relatedsearches (JSON)Rising and top related termsMedium, undocumented

The takeaway: if you want today’s hot searches, the RSS feed is enough and it is stable. If you want the interest-over-time curve or related queries, you go through the widget API, which works but is unofficial and throttled. None of these returns absolute search volume; every index value is relative.

Google Trends stops naive scrapers with aggressive IP rate limiting and a token-gated API, not a login wall. When I requested the explore page with a normal browser User-Agent but no session cookie, the response was an immediate HTTP 429. Here is the proof from my run on June 12, 2026:

import requests

UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
     "(KHTML, like Gecko) Chrome/126.0 Safari/537.36"

r = requests.get(
    "https://trends.google.com/trends/explore?q=python&geo=US",
    headers={"User-Agent": UA}, timeout=25,
)
print("HTTP", r.status_code)
print("is 429 error page:", "Error 429" in r.text)
print("body length:", len(r.text))
HTTP 429
is 429 error page: True
body length: 1697

The 1697-byte body is Google’s “Too Many Requests” page, not trend data. Two walls cause this. First, the explore endpoint throttles clients that arrive without a valid session cookie, so the first request from a script often fails. Second, even past that, the actual numbers never sit in the page HTML: they load through the api/widgetdata/* endpoints, and each of those requires a one-time token minted by a prior api/explore call. Miss the token step and you get nothing. The good news is that priming a session with one homepage GET clears the cookie wall, which is exactly what Method 2 below does.

The simplest DIY method that works is the daily trending RSS feed, parsed with requests and BeautifulSoup’s XML mode. It skips the cookie wall, needs no token, and returns structured XML, so it is the right starting point for a free scraper. The endpoint takes a geo country code:

GoalURL pattern
US trending searcheshttps://trends.google.com/trending/rss?geo=US
UK trending searcheshttps://trends.google.com/trending/rss?geo=GB
Any countryhttps://trends.google.com/trending/rss?geo=COUNTRY_CODE

Each <item> carries the query, an approx_traffic estimate, a pubDate, a picture, and up to three linked news stories under Google’s ht: namespace. Here is the scraper:

import requests
from bs4 import BeautifulSoup

UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
     "(KHTML, like Gecko) Chrome/126.0 Safari/537.36"
URL = "https://trends.google.com/trending/rss?geo=US"

resp = requests.get(URL, headers={"User-Agent": UA}, timeout=25)
print("HTTP", resp.status_code, resp.headers["content-type"])

# Parse as XML so the ht: namespaced tags survive
soup = BeautifulSoup(resp.content, "xml")
items = soup.find_all("item")
print(f"Parsed {len(items)} trending searches\n")

for it in items[:8]:
    title = it.title.get_text()
    traffic = it.find("ht:approx_traffic")
    traffic = traffic.get_text() if traffic else "n/a"
    news = it.find_all("ht:news_item")
    src = ""
    if news:
        s = news[0].find("ht:news_item_source")
        src = s.get_text() if s else ""
    print(f"{title}  (traffic {traffic}, {len(news)} stories) - {src}")

I ran this on June 12, 2026. Real output:

HTTP 200 text/xml; charset=utf-8
Parsed 10 trending searches

novorossiya  (traffic 1000+, 3 stories) - Institute for the Study of War
john davis  (traffic 1000+, 3 stories) - People.com
thayne jasperson  (traffic 500+, 3 stories) - People.com
antonio gracias  (traffic 1000+, 3 stories) - The New York Times
amanda anisimova  (traffic 1000+, 3 stories) - BBC
dark souls  (traffic 1000+, 3 stories) - OpenCritic
draftkings  (traffic 2000+, 3 stories) - Yahoo Sports
grow a garden 2  (traffic 1000+, 3 stories) - games.gg

That is a working free scraper: one GET, 10 real trending searches, each with a traffic band and its top news source. Two limits to plan around. First, the feed only covers today’s trending searches for one country; it carries no historical interest-over-time and no arbitrary keyword lookup. Second, approx_traffic is a coarse band (“1000+”, “2000+”), not a precise number. Install the dependencies with pip install requests beautifulsoup4 lxml (the xml parser needs lxml). For more on this stack, see my BeautifulSoup guide and the requests walkthrough.

Method 2: Scrape interest-over-time from the widget API (DIY)

To get the interest-over-time curve for a specific keyword, you call Google’s undocumented widget API in two steps: mint a token, then fetch the datapoints. This is the same flow the pytrends library wraps. It works, but it is unofficial and throttled, so handle it with care. The two steps:

  1. GET api/explore with your query JSON, which returns four widgets each carrying a token and a request payload.
  2. GET api/widgetdata/multiline with the TIMESERIES widget’s token and request, which returns the 0-100 index per time bucket.

Both responses start with the XSSI guard )]}', so strip the first five characters before parsing JSON. Priming the session with one homepage request supplies the cookie that gets you past the 429.

import requests, urllib.parse, json

UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
     "(KHTML, like Gecko) Chrome/126.0 Safari/537.36"

s = requests.Session()
s.headers.update({"User-Agent": UA})
s.get("https://trends.google.com/trends/", timeout=25)   # prime the cookie

# Step 1: explore -> tokens
req = ('{"comparisonItem":[{"keyword":"python","geo":"US",'
       '"time":"today 12-m"}],"category":0,"property":""}')
url = "https://trends.google.com/trends/api/explore?hl=en-US&tz=0&req=" + urllib.parse.quote(req)
r1 = s.get(url, timeout=25)
print("explore:", r1.status_code)
widgets = {w["id"]: w for w in json.loads(r1.text[5:])["widgets"]}
ts = widgets["TIMESERIES"]

# Step 2: multiline -> interest over time
url2 = ("https://trends.google.com/trends/api/widgetdata/multiline?hl=en-US&tz=0&req="
        + urllib.parse.quote(json.dumps(ts["request"])) + "&token=" + ts["token"])
r2 = s.get(url2, timeout=25)
print("multiline:", r2.status_code)

points = json.loads(r2.text[5:])["default"]["timelineData"]
print("datapoints:", len(points))
for p in points[:3] + points[-3:]:
    print(" ", p["formattedAxisTime"], "->", p["value"][0])

I ran this on June 12, 2026. Real output (first and last three weeks of the year):

explore: 200
multiline: 200
datapoints: 53
  Jun 8, 2025 -> 69
  Jun 15, 2025 -> 67
  Jun 22, 2025 -> 69
  May 24, 2026 -> 85
  May 31, 2026 -> 55
  Jun 7, 2026 -> 60

53 weekly datapoints on the 0-100 scale, exactly what the explore page would chart. The explore call also returns GEO_MAP, RELATED_TOPICS, and RELATED_QUERIES widgets, so the same token pattern pulls interest-by-region and related queries by swapping the second endpoint to comparedgeo or relatedsearches.

Now the honesty part. This API is undocumented and rate-limited. A cookieless burst earned the 429 you saw above. With a fresh session cookie I ran eight keyword lookups back to back and all eight returned 200, so a polite one-session-per-query pattern survives short runs. Push harder and Google throttles the IP. Plan for retries with backoff, rotate IPs for volume, and expect the token flow to change without notice. For the wider anti-block toolkit, see scraping without getting blocked, and for a structured crawler, Scrapy.

A scraper API is the right tool when you need volume past one IP, or want to skip the token dance and rate-limit babysitting entirely. It carries the session cookie, rotates IPs, and returns the rendered result, so you avoid the 429 and the moving token flow. ChocoData exposes a universal endpoint plus dedicated endpoints, and you point it at the Trends URL:

import requests

resp = requests.get(
    "https://api.chocodata.com/v1",
    params={
        "api_key": "YOUR_API_KEY",
        "url": "https://trends.google.com/trends/explore?q=python&geo=US",
        "render": "true",       # execute the page JavaScript
        "country": "us",        # exit from a US IP
    },
    timeout=90,
)
print(resp.status_code)
data = resp.json()   # rendered interest-over-time, related queries, geo map

I did not run this call (it needs a paid key), so I am not pasting fake output. What an API buys you over the DIY routes, by trade-off:

FactorRSS feedWidget API (DIY)Scraper API
CostFreeFreePer-request, paid
DataToday’s trends onlyAny keyword, 0-100 indexAny keyword, rendered
Rate limitsGenerousYour IP gets throttledRotated pool
Token flowNoneYou maintain itHandled for you
StabilityHighBreaks on Google changesVendor maintains it
Best forDaily hot-search monitoringLight keyword researchVolume, hands-off pipelines

Decision rule: if you only need daily trending searches, the free RSS scraper is enough and I would ship it. For occasional keyword curves, the widget API works as long as you stay polite. For steady volume, a team that cannot afford the token flow breaking, or related-query tables across many keywords, the API earns its cost by absorbing the rate limits and maintenance. Trends still gives you no absolute volume, so for that, pair it with a keyword tool.

The honest limits: no official API, a relative 0-100 index instead of real volume, sampled data, and an explore page that 429s plain requests. The trending RSS feed is stable but covers only today’s hot searches for one country. The widget API reaches any keyword’s interest-over-time and related queries, but it is undocumented, throttled, and free to change whenever Google ships a release. None of the endpoints returns absolute search counts: every value is normalized to the peak in your chosen range and region, and because it is drawn from a sample, two pulls of the same query can differ a few points. Build on RSS for free trend monitoring, layer the widget API for light keyword research with retries and backoff, reach for a scraper API when you need volume or a hands-off pipeline, and anchor the index against a known keyword volume when you need real numbers.

FAQ

Does the pytrends library still work in 2026?

Partly. pytrends wraps the same undocumented widget API I test below, so when Google does not rate-limit you it returns interest-over-time and related-query data. It breaks whenever Google changes the token flow or throttles your IP, and it has no official support. Treat it as a convenience wrapper over an endpoint that can change without notice, not a stable API.

Are Google Trends numbers actual search volume?

No. Each series is normalized to a 0-100 index where 100 is the peak interest in the selected range and region, then divided by total searches to remove popularity bias. A value of 50 means half the peak, not a search count. To turn the index into estimated absolute volume you anchor it against a keyword whose real volume you know from a keyword tool.

Why did I get different numbers for the same query twice?

Google Trends is built from a sample of searches, and the sample is drawn fresh per request, so the curve wobbles a few points between pulls. For stable figures, average several pulls or use a longer time range, and treat single-digit differences as noise rather than signal.

MR
Marcus Reed
I've built and run web scrapers for the better part of a decade. On this site I put scraper APIs and scraping tools through real jobs against real targets, then write up what actually holds up.