Trendflow
A type-safe Python library for querying, streaming, and exporting Google Trends data
- GitHub: https://github.com/dariomory/trendflow/
- PyPI package: https://pypi.org/project/trendflow-py/ (install name
trendflow-py; import astrendflow) - Created by: Dario Mory | GitHub https://github.com/dariomory | PyPI https://pypi.org/user/dariomory/
- Free software: MIT License
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%"
growthis the percentage rise over the window,volumea relative search-volume index.- Any country code works, not a fixed list, and worldwide is now allowed (and the default).
articlesis always empty — this endpoint carries no article links.- No cookie is needed, and the RPC answers on IPs that get a
429from the widgetdata endpoints, sotrending_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:
- User-Agent. Google returns
429to 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. - IP reputation. Once an IP is flagged, every request gets
429regardless 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:
| 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.
- Live site: https://dariomory.github.io/trendflow/
- Preview locally:
just docs-serve(serves at http://localhost:8000) - Build:
just docs-build
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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file trendflow_py-0.3.0.tar.gz.
File metadata
- Download URL: trendflow_py-0.3.0.tar.gz
- Upload date:
- Size: 214.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
809215a7cd84995b0d0da97409befcd5154a9c68c3a9c5a5c81c8202e15f0fea
|
|
| MD5 |
766b1edd15411cb5a6c165473dccce34
|
|
| BLAKE2b-256 |
4ba5d6571a8ad611632a04c61ed65aea9dfcd89b07e27fdeba106b1de7d46b17
|
Provenance
The following attestation bundles were made for trendflow_py-0.3.0.tar.gz:
Publisher:
publish.yml on dariomory/trendflow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
trendflow_py-0.3.0.tar.gz -
Subject digest:
809215a7cd84995b0d0da97409befcd5154a9c68c3a9c5a5c81c8202e15f0fea - Sigstore transparency entry: 2497192027
- Sigstore integration time:
-
Permalink:
dariomory/trendflow@b0b659b7aef1d7ebb70045f8f4884307991c0d4d -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/dariomory
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b0b659b7aef1d7ebb70045f8f4884307991c0d4d -
Trigger Event:
push
-
Statement type:
File details
Details for the file trendflow_py-0.3.0-py3-none-any.whl.
File metadata
- Download URL: trendflow_py-0.3.0-py3-none-any.whl
- Upload date:
- Size: 33.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3aa50c4ad9e9c876e3ddaadb0484f6a783c47366a69778e39cf01f965ec424e6
|
|
| MD5 |
7edc292de08ab824c2606f458d953917
|
|
| BLAKE2b-256 |
94c5d30ac51fd2a8534241bdfc61e6be0cd9573857e3a26a806d3d5629b1055c
|
Provenance
The following attestation bundles were made for trendflow_py-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on dariomory/trendflow
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
trendflow_py-0.3.0-py3-none-any.whl -
Subject digest:
3aa50c4ad9e9c876e3ddaadb0484f6a783c47366a69778e39cf01f965ec424e6 - Sigstore transparency entry: 2497192057
- Sigstore integration time:
-
Permalink:
dariomory/trendflow@b0b659b7aef1d7ebb70045f8f4884307991c0d4d -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/dariomory
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b0b659b7aef1d7ebb70045f8f4884307991c0d4d -
Trigger Event:
push
-
Statement type: