Skip to main content

Trendflow logo

Trendflow

PyPI version

A type-safe Python library for querying, streaming, and exporting Google Trends data

Features

  • Type-safe API: regions, timeframes, resolutions, and export formats use enums instead of raw strings.
  • Rich queries: interest over time, regional breakdown, live trending searches, and related queries, with dataclass results.
  • Exports: JSON, CSV, or load results into a pandas DataFrame.

Usage

import trendflow
from trendflow import Region, Timeframe, Resolution, ExportFormat

# Initialize client (optional API config)
tf = trendflow.Client(language="en", timeout=10)

# --- Enums for type safety ---
# Region.US, Region.GB, Region.DE ...
# Timeframe.PAST_DAY, Timeframe.PAST_WEEK, Timeframe.PAST_YEAR, Timeframe.PAST_5_YEARS
# Resolution.COUNTRY, Resolution.REGION, Resolution.CITY

# Fetch interest over time
data = tf.interest_over_time(
    keywords=["Python", "JavaScript", "Rust"],
    timeframe=Timeframe.PAST_YEAR,
    region=Region.US,
)

# Dataclass-backed results
print(data.keywords)        # ["Python", "JavaScript", "Rust"]
print(data.granularity)     # "weekly"
print(data.points)          # list of TrendPoint(date, scores: dict)

# Get regional breakdown (region defaults to Region.US)
regional = tf.interest_by_region(
    keyword="Python",
    resolution=Resolution.COUNTRY,
)

# Trending searches right now
trending = tf.trending_now(region=Region.US)
for item in trending.results:
    print(item.title, item.traffic, item.articles)  # TrendingItem dataclass

# Related queries — returns RelatedResult dataclass
related = tf.related_queries("machine learning")
for query in related.top:
    print(query.term, query.value)    # RelatedQuery(term, value)
for query in related.rising:
    print(query.term, query.breakout) # RelatedQuery(term, breakout%)

# --- Exports ---
data.export(ExportFormat.CSV,  path="trends.csv")
data.export(ExportFormat.JSON, path="trends.json")
data.to_dataframe()  # pandas DataFrame

Feature Parity

Trendflow also ships as a JavaScript/TypeScript library: trendflow-js (npm: trendflow).

Current: trendflow-py 0.2.0 · trendflow 0.1.0. Versions are independent; each changelog cross-references the sibling release.

Feature Python — trendflow-py JS — trendflow
Interest over time ✅ ✅
Interest by region ✅ ✅
Trending now ✅ ✅
Trending growth % and volume ✅ ✅
Trending for any country code ✅ ✅
Trending news articles (RSS) ✅ ✅
Selectable trending backend ✅ ✅
Related queries ✅ ✅
Search suggestions ✅ suggestions() ✅ suggestions()
Query by topic (entity mid) ✅ ✅
CSV / JSON export ✅ ✅
Rotating proxy pool ✅ ✅
Browser User-Agent by default ✅ ✅
Full geo hierarchy ✅ geo_list() ✅ geoList()
Overridable RPC ids ✅ ✅
pandas DataFrame ✅ to_dataframe() ❌ N/A
Plain-object rows ❌ N/A ✅ toArray()
ESM + CommonJS + types ❌ N/A ✅
CLI ✅ 🔜 planned

Trending now

Google retired the hottrends/visualize/internal/data endpoint, along with api/dailytrends and api/realtimetrends; all three now return HTTP 404. trending_now() therefore runs on the batchexecute RPC that trends.google.com itself uses, which returns more than the old endpoint did:

trending = tf.trending_now(Region.US)
for item in trending.results:
    print(item.title, item.growth, item.volume, item.traffic)
    # "fifa world cup 2026"  3650  6  "+3,650%"
  • growth is the percentage rise over the window, volume a relative search-volume index.
  • Any country code works, not a fixed list, and worldwide is now allowed (and the default).
  • articles is always empty — this endpoint carries no article links.
  • No cookie is needed, and the RPC answers on IPs that get a 429 from the widgetdata endpoints, so trending_now() often works where the other queries do not.

Pass window=TRENDING_WINDOW_TOP for the highest-volume searches instead of the fastest-growing ones. window is an undocumented Google parameter; other integers between 4 and 12 also return data over varying recency windows.

Trending backends: RPC and RSS

Google exposes trending searches two ways. They are not interchangeable, so backend lets you pick:

"rpc" (batchexecute) "rss" (feed)
items 50 10
payload ~2 KB JSON ~21 KB XML
growth % and volume ✅ ❌ — buckets like "2000+"
news articles ❌ ✅
window selection ✅ ignored by Google
worldwide ✅ ❌ country only
rss = tf.trending_now(Region.US, backend="rss")
rss.source  # "rss"
rss.results[0].articles
# [TrendingArticle(title='...', url='https://...', source='Buffalo News', picture='https://...')]

"auto" (the default) tries the RPC and falls back to the feed. The RPC comes first deliberately: it returns five times the items with real growth figures, so defaulting to RSS would quietly degrade results. Reach for "rss" when you want the articles — that is the one thing the RPC cannot give you — or as a second opinion if the RPC id ever goes stale.

Note that the feed is not a lighter path despite being a feed, and Google ignores hours, sort and count on it: it always returns the same 10 entries.

Topics and search suggestions

Google distinguishes a search term (the literal string) from a topic (the entity, in every spelling and language). suggestions() finds the topic; every query method already accepts one — pass the mid where you would pass a keyword.

topics = tf.suggestions("artificial intelligence")
# [TopicSuggestion(mid='/m/0mkz', title='Artificial intelligence', type='Professional field')]

data = tf.interest_over_time(
    keywords=[topics[0].mid, "artificial intelligence"],
    timeframe=Timeframe.PAST_YEAR,
    region=Region.US,
)
# {'/m/0mkz': 62, 'artificial intelligence': 1}

That gap is the point: the topic scores 62 where the literal phrase scores 1, because it aggregates every phrasing and translation people actually search.

suggestions() needs no cookie and no proxy — it answers on IPs the widgetdata endpoints reject with 429, same as trending_now(). type disambiguates same-name entities ("Nike" returns both the company and the goddess) and is None when Google omits it.

Rate limits

Google Trends aggressively rate-limits datacenter and shared IPs, so 429 is common even on your first request of the day. Two things matter:

  1. User-Agent. Google returns 429 to the default agent strings Python HTTP clients send, no matter how few requests you have made. This library sends a browser User-Agent by default for exactly that reason.
  2. IP reputation. Once an IP is flagged, every request gets 429 regardless of headers. Route through a residential proxy to recover.

Using a proxy pool

Pass a list of proxy URLs and the client rotates through them automatically, moving to the next one whenever a query is refused:

import trendflow
from trendflow import Region

tf = trendflow.Client(
    proxies=[
        "http://user:pass@gate.decodo.com:7000",
        "http://user:pass@gate.decodo.com:7000",
    ],
    max_proxy_attempts=3,  # defaults to the pool size, capped at 5
    on_proxy_rotate=lambda attempt, error: print(f"rotated after {attempt}: {error!r}"),
)

trending = tf.trending_now(Region.US)
print(tf.current_proxy)  # the proxy that answered

Entries are just URLs, so a pool can mix providers. Repeating one rotating gateway also works: each entry gets its own connection, so it lands on a fresh exit IP.

Rotation happens per query, not per request — this matters. Google binds the NID cookie and the widget token to the IP that requested them, so a single query must complete on one exit IP; sending the follow-up widgetdata call from a different IP earns an instant 429. The pool pins one proxy for the whole query and advances only on failure, re-seeding the cookie jar each time. For the same reason, point the pool at sticky sessions rather than per-request rotating endpoints if your provider offers the choice.

Rotation is skipped for errors a different IP cannot fix, such as a 404 or a renamed RPC.

Where to get proxies

Residential proxies are what actually clears Google's 429. Verified against this library:

Decodo

Provider Notes Endpoint format
Decodo (formerly Smartproxy) Cheapest entry tier; pay-as-you-go available. Used to verify this library's live tests. http://user:pass@gate.decodo.com:7000

Ask for sticky sessions when you sign up — per-request rotating endpoints break the cookie/token binding described above. Note that a shared residential pool can be exhausted for Google Trends specifically, in which case even a valid proxy returns 429; that is what max_proxy_attempts is for.

If Google renames an RPC

The batchexecute RPC identifiers are pinned constants; they are not discoverable at runtime. If Google renames one, calls raise UnknownRpcError naming the identifier, and you can patch it without waiting for a release by passing rpc_ids to trendflow._trends_http.batchexecute.BatchExecuteClient.

Documentation

Documentation is built with Zensical and deployed to GitHub Pages.

API documentation is auto-generated from docstrings using mkdocstrings.

Docs deploy automatically on push to master or main via GitHub Actions.

Development

To set up for local development:

# Clone your fork
git clone git@github.com:dariomory/trendflow.git
cd trendflow

# Install in editable mode with live updates
uv tool install --editable .

This installs the CLI globally but with live updates - any changes you make to the source code are immediately available when you run trendflow.

Run tests:

uv run pytest

Run quality checks (format, lint, type check, test):

just qa

Author

Trendflow was created in 2026 by Dario Mory

Metadata

Release files for trendflow-py 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for trendflow-py 0.3.0
File Size Uploaded
trendflow_py-0.3.0.tar.gz 214.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for trendflow-py 0.3.0
File Interpreter ABI Platform
trendflow_py-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 247.7 kB

Release files / trendflow_py-0.3.0.tar.gz

Download URL trendflow_py-0.3.0.tar.gz
Size 214.4 kB
Tags Source
SHA-256 checksum
How to use checksums
809215a7cd84995b0d0da97409befcd5154a9c68c3a9c5a5c81c8202e15f0fea
BLAKE2b-256 checksum
How to use checksums
4ba5d6571a8ad611632a04c61ed65aea9dfcd89b07e27fdeba106b1de7d46b17
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 17, 2026.

Transparency log

Release files / trendflow_py-0.3.0-py3-none-any.whl

Download URL trendflow_py-0.3.0-py3-none-any.whl
Size 33.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3aa50c4ad9e9c876e3ddaadb0484f6a783c47366a69778e39cf01f965ec424e6
BLAKE2b-256 checksum
How to use checksums
94c5d30ac51fd2a8534241bdfc61e6be0cd9573857e3a26a806d3d5629b1055c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 17, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page