DEV Community

SteadyScrape
SteadyScrape

Posted on

Google Trends in Python Without the 429s: Momentum, Breakouts and a Weekly Trend Alert

Google Trends is one of the most useful free datasets on the internet: it shows what people are searching for, how that changes over time, and where. It's also one of the most annoying to use from code. There's no official public API, and the popular unofficial route (libraries like pytrends that call the Trends website's internal endpoints) regularly hits HTTP 429 "too many requests" errors, especially from cloud servers.

This post covers two things: what to do with Trends data once you have it (momentum, breakouts, alerts), and a way to get it reliably.

First, understand what Trends numbers mean

Trends values aren't search counts. Each series is scaled 0–100, where 100 is the peak within that request. That has consequences:

  • Two separate requests can't be compared. "chatgpt" at 80 and "claude" at 80 from two different queries mean nothing relative to each other. To compare terms, request them together (up to 5), so they share one scale.
  • The last point is partial. The current week or day is incomplete and usually looks like a drop. Leave it out of any calculation.
  • Small terms are noisy. Low-volume terms bounce between 0 and 100. Use longer ranges or weekly data.

Turning a series into a signal

Raw series are hard to act on. A simple momentum score is easier: compare the most recent slice of the range with the slice before it.

def momentum(points, slices=6):
    """points: [{'date': ..., 'value': int, 'partial': bool}, ...] oldest first"""
    values = [p["value"] for p in points if not p.get("partial")]
    n = max(1, len(values) // slices)
    recent, previous = values[-n:], values[-2 * n:-n]
    if not previous or sum(previous) == 0:
        return None
    pct = 100 * (sum(recent) / len(recent) / (sum(previous) / len(previous)) - 1)
    label = "rising" if pct >= 15 else "falling" if pct <= -15 else "stable"
    return round(pct, 1), label
Enter fullscreen mode Exit fullscreen mode

Two other signals are worth extracting:

  • Peak date: when interest topped out. Is the topic past its peak?
  • Breakouts: Trends marks related queries that grew more than 5,000% as "Breakout". These are often the earliest signal of a new sub-topic.

Getting the data reliably

Disclosure: we (SteadyScrape) built the tool below and get paid when it's used.

Google Trends API: Trend Data and Breakout Alerts (steady-scrape/google-trends-api) is an Apify Actor that returns Trends data from a licensed Google Trends API instead of scraping the Trends website, so there are no captchas, 429 errors or timed-out runs. You get one row per term with the full interest-over-time series, interest by country or region, top and rising related queries and topics, and a summary already calculated: average, latest, peak, momentum, a rising / falling / stable label and breakout lists. It uses the same "latest sixth vs the sixth before" momentum as the function above, with the partial period left out.

import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("steady-scrape/google-trends-api").call(run_input={
    "searchTerms": ["web scraping", "chatgpt, gemini, claude"],   # a comma line = compare on one scale
    "timeRange": "past_12_months",
})
for t in client.dataset(run["defaultDatasetId"]).iterate_items():
    if t["status"] != "ok":
        continue
    print(f'{t["searchTerm"]:<14} avg {t["averageInterest"]:>5}  momentum {t["momentumPct"]:>6}%  '
          f'{t["trend"]:<8} peak {t["peakDate"]}  breakouts: {", ".join(t["breakoutQueries"][:3])}')
Enter fullscreen mode Exit fullscreen mode

Here's what a test run like that returned (worldwide, past 12 months):

Term Average Momentum Trend Peak week
web scraping 60.9 −16.5% falling 2026-05-03
chatgpt (compared) 82 +4.5% stable 2025-11-02
gemini (compared) 44.6 +8.8% stable 2026-10-04
claude (compared) 14.8 +10.6% stable 2026-06-07

The three AI assistants were requested on one line, so their numbers share a scale. Read that as "chatgpt gets roughly 5–6× the search interest of claude", not as absolute volumes. Claude was the only one of the three with breakout queries in that run, mostly new product and model names. A gibberish test term came back as no_data and wasn't charged.

The run also included the interestOverTime series, topRegions and related queries for each term, plus a googleTrendsUrl that opens the same chart on Google Trends so you can check it by eye.

Options worth knowing

  • region: a country or sub-region (US, US-CA, Germany).
  • timeRange: from past_hour to all_time (since 2004), or startDate/endDate for a custom range.
  • searchType: web, news, youtube, images or shopping. YouTube and Shopping trends are underrated for product research.
  • dataMode: Standard is cheapest and usually ready in 1–2 minutes (that 5-term run took about 70 seconds). Live returns in seconds for a higher price.

A weekly trend-change alert

Monitor mode remembers the last run for a named watchlist and flags changes: momentum past your threshold, a new breakout, or a flipped trend direction.

run = client.actor("steady-scrape/google-trends-api").call(run_input={
    "searchTerms": ["standing desk", "walking pad"],
    "region": "US",
    "monitor": True,
    "monitorName": "office-products",
    "alertThresholdPct": 25,
})
alerts = [t for t in client.dataset(run["defaultDatasetId"]).iterate_items()
          if (t.get("monitor") or {}).get("alert")]
for t in alerts:
    print(t["searchTerm"], t["monitor"]["alertReasons"])
Enter fullscreen mode Exit fullscreen mode

Schedule it weekly in Apify and send alerts to Slack or email, or wire it up without code in Make, Zapier or n8n.

What it costs

Event Price
Term with data, Standard $0.006
Term with data, Live $0.02
Run start (only if a term has data) $0.005

A term inside a comparison counts as one term. No-data terms, invalid lines and errors are free. Platform usage is included.

Examples:

  • The 4-term test above (plus one free no-data term) cost 4 × $0.006 + $0.005 = $0.029.
  • A weekly monitor of 20 product terms costs about 4.3 × (20 × $0.006 + $0.005) ≈ $0.54 a month.
  • 1,000 terms in Live mode is $20.

You can set a maximum cost per run, and the Actor only fetches what fits.

Whatever you use to fetch the data, the analysis rules above still apply: compare terms only within one request, drop the partial period, and treat momentum and breakouts as the signals, not the raw 0–100 numbers.

The Actor is Google Trends API: Trend Data and Breakout Alerts on Apify. The first run with the prefilled example input (two lines, four terms, Standard mode) cost $0.029 in our test on 2026-10-30 and took about 70 seconds.

Top comments (0)