trendspyg API Reference¶
API documentation for trendspyg 1.9.0.
Everything documented here is covered by the project's API stability policy — semantic versioning with a written definition of the public surface and a deprecation policy.
Table of Contents¶
- RSS Functions
- download_google_trends_rss
- download_google_trends_rss_async
- download_google_trends_rss_batch
- download_google_trends_rss_batch_async
- CSV Functions
- download_google_trends_csv
- Explore Functions
- get_keyword_suggestions
- download_google_trends_interest_over_time
- download_google_trends_explore
- download_google_trends_comparison
- pytrends compatibility (trendspyg.compat)
- Normalized Output
- Cache Functions
- Archive Functions
- clear_rss_cache
- clear_explore_cookies
- get_rss_cache_stats
- set_rss_cache_ttl
- Exceptions
- Configuration
- Monitoring
- MCP Server
- Type Aliases
RSS Functions¶
download_google_trends_rss¶
Fast RSS feed download with rich media content.
def download_google_trends_rss(
geo: str = 'US',
output_format: Literal['dict', 'dataframe', 'json', 'csv'] = 'dict',
include_images: bool = True,
include_articles: bool = True,
max_articles_per_trend: int = 5,
cache: Union[bool, str] = True,
normalize: bool = False,
archive: bool = False,
db_path: Optional[str] = None,
*,
envelope: bool = False
) -> Union[List[Dict], str, pd.DataFrame, Dict]
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
geo |
str |
'US' |
Country code (e.g., 'US', 'GB') or US state (e.g., 'US-CA') |
output_format |
str |
'dict' |
Output format: 'dict', 'dataframe', 'json', 'csv' |
include_images |
bool |
True |
Include image URLs and sources |
include_articles |
bool |
True |
Include news articles data |
max_articles_per_trend |
int |
5 |
Maximum news articles per trend |
cache |
bool \| str |
True |
Memory cache; False for fresh data; "disk" for cross-process caching |
normalize |
bool |
False |
Return a unified NormalizedEnvelope (see Normalized Output); output_format is ignored |
Metadata/archive: envelope=True returns a raw TrendEnvelope dict (fetched_at, geo, count, trends) and ignores output_format; normalize=True takes precedence. archive=True records fresh observations in db_path (or TRENDSPYG_DB). cache="disk" persists across processes. Memory and disk cache hits preserve original observation times and do not add history.
Returns:
List[Dict]whenoutput_format='dict'pd.DataFramewhenoutput_format='dataframe'str(JSON) whenoutput_format='json'str(CSV) whenoutput_format='csv'
Raises:
InvalidParameterError- Invalid geo code or output formatDownloadError- Network or parsing errorRateLimitError- Rate limit exceeded (HTTP 429/403)
Example:
from trendspyg import download_google_trends_rss
# Basic usage
trends = download_google_trends_rss(geo='US')
# Get as DataFrame
df = download_google_trends_rss(geo='GB', output_format='dataframe')
# Minimal data (faster)
trends = download_google_trends_rss(
geo='US',
include_images=False,
include_articles=False
)
# Bypass cache for fresh data
trends = download_google_trends_rss(geo='US', cache=False)
Return Data Structure (dict format):
{
'trend': 'bitcoin', # Trend keyword
'traffic': '500K+', # Search volume tier (human-readable)
'traffic_min': 500000, # Parsed lower bound as int (always present)
'published': datetime(...), # Publication timestamp
'explore_link': 'https://...', # Google Trends explore URL
'image': { # Only if include_images=True
'url': 'https://...',
'source': 'CNN'
},
'news_articles': [ # Only if include_articles=True
{
'headline': 'Bitcoin surges...',
'url': 'https://...',
'source': 'Reuters',
'image': 'https://...'
}
]
}
download_google_trends_rss_async¶
Async version for parallel fetching. 50-100x faster for batch operations.
async def download_google_trends_rss_async(
geo: str = 'US',
output_format: Literal['dict', 'dataframe', 'json', 'csv'] = 'dict',
include_images: bool = True,
include_articles: bool = True,
max_articles_per_trend: int = 5,
session: Optional[aiohttp.ClientSession] = None,
cache: Union[bool, str] = True,
normalize: bool = False,
archive: bool = False,
db_path: Optional[str] = None,
*,
envelope: bool = False
) -> Union[List[Dict], str, pd.DataFrame, Dict]
Additional Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
session |
aiohttp.ClientSession |
None |
Shared session for connection pooling |
Requires: pip install trendspyg[async]
Example:
import asyncio
from trendspyg import download_google_trends_rss_async
# Single country
async def main():
trends = await download_google_trends_rss_async(geo='US')
print(f"Got {len(trends)} trends")
asyncio.run(main())
# Multiple countries in parallel
async def fetch_all():
countries = ['US', 'GB', 'CA', 'AU', 'DE']
tasks = [download_google_trends_rss_async(geo=c) for c in countries]
results = await asyncio.gather(*tasks)
return dict(zip(countries, results))
all_trends = asyncio.run(fetch_all())
download_google_trends_rss_batch¶
Synchronous batch fetching with progress bar.
def download_google_trends_rss_batch(
geos: List[str],
include_images: bool = True,
include_articles: bool = True,
max_articles_per_trend: int = 5,
show_progress: bool = True,
delay: float = 0.0,
normalize: bool = False,
archive: bool = False,
db_path: Optional[str] = None,
*,
cache: Union[bool, str] = True
) -> Dict[str, Union[List[Dict], Dict]]
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
geos |
List[str] |
required | List of geo codes to fetch |
show_progress |
bool |
True |
Show tqdm progress bar |
delay |
float |
0.0 |
Delay between requests (seconds) |
normalize |
bool |
False |
Each geo maps to a NormalizedEnvelope instead of a trend list |
Caching/archive: cache=True uses memory; False fetches fresh; "disk" persists across processes. archive=True records fresh observations in db_path (or TRENDSPYG_DB). Cached reads retain their original timestamps and do not add history.
Returns: Dict[str, List[Dict]] - Dictionary mapping geo codes to trends (or geo to NormalizedEnvelope when normalize=True)
Example:
from trendspyg import download_google_trends_rss_batch
# Fetch multiple countries with progress bar
results = download_google_trends_rss_batch(
['US', 'GB', 'CA', 'AU'],
delay=0.5 # Be nice to Google
)
# Output: Fetching trends: 100%|██████████| 4/4
for country, trends in results.items():
print(f"{country}: {len(trends)} trends")
download_google_trends_rss_batch_async¶
Async batch fetching - fastest option for multiple countries.
async def download_google_trends_rss_batch_async(
geos: List[str],
include_images: bool = True,
include_articles: bool = True,
max_articles_per_trend: int = 5,
show_progress: bool = True,
max_concurrent: int = 10,
normalize: bool = False,
archive: bool = False,
db_path: Optional[str] = None,
*,
cache: Union[bool, str] = True
) -> Dict[str, Union[List[Dict], Dict]]
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
geos |
List[str] |
required | List of geo codes to fetch |
show_progress |
bool |
True |
Show tqdm progress bar |
max_concurrent |
int |
10 |
Maximum concurrent requests |
normalize |
bool |
False |
Each geo maps to a NormalizedEnvelope instead of a trend list |
Example:
import asyncio
from trendspyg import download_google_trends_rss_batch_async
async def main():
results = await download_google_trends_rss_batch_async(
['US', 'GB', 'CA', 'AU', 'DE', 'FR', 'JP'],
max_concurrent=5 # Limit to avoid rate limits
)
return results
all_trends = asyncio.run(main())
CSV Functions¶
download_google_trends_csv¶
Full-featured CSV download with filtering (requires Chrome).
def download_google_trends_csv(
geo: str = 'US',
hours: int = 24,
category: str = 'all',
active_only: bool = False,
sort_by: str = 'relevance',
headless: bool = True,
download_dir: Optional[str] = None,
output_format: Literal['csv', 'json', 'parquet', 'dataframe', 'dict'] = 'csv',
normalize: bool = False,
timeout: int = 10,
max_retries: int = 3,
archive: bool = False,
db_path: Optional[str] = None
) -> Union[str, pd.DataFrame, List[Dict], Dict, None]
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
geo |
str |
'US' |
Country or US state code |
hours |
int |
24 |
Time period: 4, 24, 48, or 168 (7 days) |
category |
str |
'all' |
Category filter (see Configuration) |
active_only |
bool |
False |
Only show active/rising trends |
sort_by |
str |
'relevance' |
Sort: 'relevance', 'title', 'volume', 'recency' |
headless |
bool |
True |
Run Chrome in headless mode |
download_dir |
str |
None |
Download directory (default: ./downloads/) |
output_format |
str |
'csv' |
Output: 'csv', 'json', 'parquet', 'dataframe', 'dict' |
normalize |
bool |
False |
Return a unified NormalizedEnvelope (see Normalized Output); output_format is ignored |
Returns:
str- File path whenoutput_formatis 'csv', 'json', or 'parquet'pd.DataFramewhenoutput_format='dataframe'List[Dict]whenoutput_format='dict'Dict(NormalizedEnvelope) whennormalize=True
Requires: Chrome browser installed. Category and active-only selections are
verified before export; a failed selection raises BrowserError instead of
returning an unfiltered dataset. timeout bounds each page/control/download
wait; max_retries caps scrape attempts. archive=True records the normalized
observation with requested filters in db_path (or TRENDSPYG_DB).
Example:
from trendspyg import download_google_trends_csv
# Basic usage
csv_path = download_google_trends_csv(geo='US')
# With filters
df = download_google_trends_csv(
geo='US-CA',
hours=168, # 7 days
category='sports',
active_only=True,
output_format='dataframe'
)
Explore Functions¶
get_keyword_suggestions¶
from trendspyg import get_keyword_suggestions, KeywordSuggestion
candidates = get_keyword_suggestions("apple", language="en-US", cache=True, timeout=10.0)
# list[KeywordSuggestion]: {"mid": str, "title": str, "type": str}
New in 1.8.0. Resolves non-empty text to Google's topic candidates using HTTP,
without Chrome. Every field is present; upstream order is preserved. Choose the
intended title/type explicitly and pass its mid to an Explore function. Keep
the selected labels with the result: comparison keys remain the IDs supplied.
Suggestions are neither a popularity ranking nor the Related Topics widget;
an empty list means no candidates were offered, not zero interest.
| Parameter | Default | Meaning |
|---|---|---|
keyword |
required | Search text; outer whitespace is removed. |
language |
"en-US" |
Language tag for labels, e.g. fr, es-419, zh-Hant-TW; independent of Explore geography. |
cache |
True |
Keyword-only boolean. One-hour in-memory cache, up to 128 keyword/language pairs; False bypasses reads and writes. No disk cache or archive for lookup. |
timeout |
10.0 |
Keyword-only positive finite seconds for connection/read waits; not a whole-call deadline. |
One lookup attempt, no retry loop. Invalid inputs raise InvalidParameterError;
HTTP 403/429 raises RateLimitError (stop and back off); other HTTP/network
failures raise DownloadError; malformed responses raise ParseError.
Cache hits return independent copies. Lookup stores no cookies or files and
its cache is separate from the RSS cache controls.
Browser analysis¶
Keyword analysis over time — interest over time, related queries, and interest by region.
This is the data the archived pytrends was most used for. New in 0.6.0.
The three download functions below drive a real (headless) Chrome browser against Google's Explore page. Google defends the Explore endpoints aggressively, so expect ~10–90s per call with retries, and a
RateLimitErrorwhen Google persistently throttles. Use them for analysis, not high-frequency polling — use the RSS path for fast real-time checks. Requires Chrome (installed via the same setup as the CSV path).
download_google_trends_interest_over_time¶
download_google_trends_interest_over_time(
keyword: str,
geo: str = 'US',
timeframe: str = 'today 12-m',
category: int = 0,
headless: bool = True,
output_format: str = 'dict',
max_retries: int = 10, # new in 0.9.0
retry_wait: float = 8.0, # new in 0.9.0
cache: bool | str = False, # new in 1.4.0 — False or 'disk'
cache_ttl: float = None, # new in 1.4.0
archive: bool = False, # new in 1.4.0
db_path: str = None, # new in 1.4.0
gprop: str = '', # new in 1.5.0 — '', 'images', 'news', 'youtube', 'froogle'
cookies: bool | str = False, # new in 1.6.0 — 'disk' = returning-visitor cookie jar
engine: str = 'browser', # new in 1.9.0 — 'browser', 'auto' or 'http'
) -> Union[List[Dict], str, pd.DataFrame]
Google's 0-100 relative-interest time series for a single search term.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
keyword |
str |
(required) | Search term to analyze (e.g. "bitcoin"). |
geo |
str |
'US' |
Country/region code ('US', 'GB', 'US-CA'). |
timeframe |
str |
'today 12-m' |
Date range. Common: 'today 12-m' (weekly), 'today 5-y', 'today 3-m', 'now 7-d' (hourly), 'now 1-H', 'all', or custom '2024-01-01 2024-12-31'. |
category |
int |
0 |
Google Trends category id (0 = all). |
headless |
bool |
True |
Run Chrome headless. |
output_format |
str |
'dict' |
'dict', 'dataframe', 'json', or 'csv'. |
max_retries |
int |
10 |
Chart-load attempts (page reloads) past Google's soft-throttle before RateLimitError. Must be ≥ 1. (new in 0.9.0) |
retry_wait |
float |
8.0 |
Seconds to watch the chart per attempt before reloading. Must be > 0. Lower this and max_retries for fewer chart retries. They do not set a whole-call deadline: Explore checks a 40–90s browser-work budget between phases, and browser setup/download and cleanup add time. (new in 0.9.0; finite browser-work budget in 1.7.0) |
cache |
bool \| str |
False |
'disk' serves an identical recent request from the local archive DB — no browser launch, no rate-limit exposure. True is rejected (the Explore path has no in-memory cache). (new in 1.4.0) Since 1.6.0 a cached bigger answer (a full explore) also serves a smaller question (interest over time only) for the same keyword/geo/timeframe/category/gprop, trimmed to what was asked. |
cache_ttl |
float |
None |
Max age in seconds a cached result may be served. Default: 1 hour for 'now *' timeframes (hourly points), 24 hours otherwise. (new in 1.4.0) |
archive |
bool |
False |
Also record this fetch as a full ExploreEnvelope snapshot (source='explore') in the local archive. Fresh fetches only — cache hits are not re-recorded; failed writes warn instead of raising. (new in 1.4.0) |
db_path |
str |
None |
Archive/disk-cache file (default: TRENDSPYG_DB env var, else the platform data dir). (new in 1.4.0) |
gprop |
str |
'' |
Google property to analyze: ''/'web' (web search), 'images', 'news', 'youtube' (YouTube search interest), 'froogle' (Google Shopping). Validated up-front; part of the cache key. (new in 1.5.0) |
cookies |
bool \| str |
False |
'disk' reuses Google's session cookies across Explore calls (a small JSON file beside the archive DB, or TRENDSPYG_COOKIES), so each browser session is a returning visitor. Measured 2026-08-19: after a burst Google refuses new visitors with its hard 429 page while a session carrying the saved jar is served. A refused jar is dropped automatically; True is rejected. Opt-in — it keeps a Google cookie on disk. (new in 1.6.0) |
engine |
str |
'browser' |
How to reach Google. 'browser' drives Chrome, as before. 'auto' asks Google directly first (the requests pytrends made; about a second each while Google allows it) and starts Chrome only if that fails; after Google refuses, direct requests pause for five minutes. 'http' never starts Chrome and raises RateLimitError when Google refuses. Same result shape either way. Measured 2026-10-02 from one machine: direct answers repeated each other (50 of 53 values identical 40 minutes apart) and carried the browser's dates and regions, but differed from a browser answer by up to 7 points, for reasons not established; Google refused direct requests after roughly a hundred in half an hour. Direct requests follow the HTTPS_PROXY/HTTP_PROXY environment variables; Chrome is not given the proxy, so 'auto' can fall back without it; use 'http' to keep every request on a proxy. (new in 1.9.0) |
Returns (dict format): a list of points, oldest first:
[
{"date": "2025-06-01T00:00:00+00:00", "value": 27, "is_partial": False},
...
{"date": "2026-05-31T00:00:00+00:00", "value": 35, "is_partial": True},
]
value is Google's 0-100 relative-interest index; is_partial flags the still-in-progress
final period. Every value is JSON-safe (no datetime objects).
Example
from trendspyg import download_google_trends_interest_over_time
series = download_google_trends_interest_over_time("bitcoin", geo="US", timeframe="today 5-y")
peak = max(series, key=lambda p: p["value"])
print(peak["date"], peak["value"])
download_google_trends_explore¶
download_google_trends_explore(
keyword: str,
geo: str = 'US',
timeframe: str = 'today 12-m',
category: int = 0,
headless: bool = True,
include_related: bool = True,
include_geo: bool = True,
max_retries: int = 10, # new in 0.9.0
retry_wait: float = 8.0, # new in 0.9.0
cache: bool | str = False, # new in 1.4.0 — False or 'disk'
cache_ttl: float = None, # new in 1.4.0
archive: bool = False, # new in 1.4.0
db_path: str = None, # new in 1.4.0
gprop: str = '', # new in 1.5.0
cookies: bool | str = False, # new in 1.6.0
engine: str = 'browser', # new in 1.9.0 — see download_google_trends_interest_over_time
) -> Dict[str, Any] # ExploreEnvelope
The full Explore picture for a keyword in a single browser load. max_retries /
retry_wait tune the soft-throttle retry loop, and cache / cache_ttl /
archive / db_path behave exactly as in
download_google_trends_interest_over_time above — with one addition: a cache
hit returns the envelope with its original fetched_at, so the data's real
age is never hidden. include_related / include_geo are part of the cache
key (exact-match; a full fetch is not sliced to serve a slimmer request).
Returns an ExploreEnvelope:
{
"schema_version": "1.2", # 1.1 since 1.5.0 (gprop); 1.2 since 1.6.0 (is_empty)
"source": "explore",
"keyword": "bitcoin",
"geo": "US",
"timeframe": "today 12-m",
"gprop": "", # Google property ("" = web) — new in 1.5.0
"fetched_at": "2026-06-06T...+00:00",
"count": 53, # number of interest_over_time points
"is_empty": False, # True when the series has no non-zero point (Google's no-data answer) — new in 1.6.0
"interest_over_time": [ {"date", "value", "is_partial"}, ... ],
"related_queries": {
"top": [ {"query", "value", "formatted_value", "link"}, ... ],
"rising": [ {"query", "value", "formatted_value", "link"}, ... ], # formatted_value e.g. "+3,650%", "Breakout"
},
"interest_by_region": [ {"geo_code", "geo_name", "value"}, ... ], # sorted strongest first
}
related_queries / interest_by_region are empty lists when not requested
(include_related=False / include_geo=False) or when Google did not return them — the
interest_over_time series is the guaranteed payload. The envelope is JSON-safe throughout,
so no normalize pass is needed.
Example
from trendspyg import download_google_trends_explore
env = download_google_trends_explore("taylor swift", geo="US")
for q in env["related_queries"]["rising"][:5]:
print(q["query"], q["formatted_value"])
download_google_trends_comparison¶
New in 1.1.0.
def download_google_trends_comparison(
keywords: Sequence[str], # 2-5 distinct terms, no commas
geo: str = 'US',
timeframe: str = 'today 12-m',
category: int = 0,
headless: bool = True,
output_format: str = 'dict', # 'dict' | 'json' | 'dataframe' | 'csv'
include_geo: bool = True,
max_retries: int = 10,
retry_wait: float = 8.0,
cache: bool | str = False, # new in 1.4.0 — False or 'disk'
cache_ttl: float = None, # new in 1.4.0
archive: bool = False, # new in 1.4.0 — source 'explore_comparison'
db_path: str = None, # new in 1.4.0
gprop: str = '', # new in 1.5.0
cookies: bool | str = False, # new in 1.6.0
engine: str = 'browser', # new in 1.9.0 — see download_google_trends_interest_over_time
) -> Union[Dict[str, Any], str, pd.DataFrame] # ComparisonEnvelope for dict/json
Compare 2-5 keywords on one shared 0-100 scale — the pytrends kw_list
use case. Google scales each single-keyword series independently, so fetching
terms one at a time does not produce comparable numbers; this function
loads Google's own comparison view (one browser load, not N) and returns its
data.
Returns (for dict/json) a ComparisonEnvelope:
{
"schema_version": "1.1", # 1.1 since 1.5.0 (added gprop)
"source": "explore_comparison",
"keywords": ["bitcoin", "ethereum", "solana"],
"geo": "US",
"timeframe": "today 12-m",
"gprop": "", # Google property ("" = web) — new in 1.5.0
"fetched_at": "2026-07-10T...+00:00",
"count": 53,
"averages": {"bitcoin": 39, "ethereum": 7, "solana": 5}, # Google's per-keyword averages
"interest_over_time": [
{"date": "2025-07-06T00:00:00+00:00",
"values": {"bitcoin": 38, "ethereum": 6, "solana": 4}, # keyed by keyword
"is_partial": False},
...
],
"interest_by_region": [
{"geo_code": "US-WY", "geo_name": "Wyoming",
"values": {"bitcoin": 68, "ethereum": 16, "solana": 16},
"top_keyword": "bitcoin"}, # the winner in this region
...
],
}
output_format="dataframe" / "csv" render the interest-over-time series as a
pytrends-style table instead: date, <kw1>, ..., <kwN>, is_partial.
include_geo=False skips the region fetch (faster) — interest_by_region is
then [], the field is always present.
Limits (Google's, stated honestly): at most 5 terms; terms containing a comma cannot be compared (the URL uses commas as separators); duplicates (case-insensitive) are rejected. Same rate-limit-sensitive path as the other Explore functions (~10-90s — not for polling).
Example
from trendspyg import download_google_trends_comparison
env = download_google_trends_comparison(["bitcoin", "ethereum"], geo="US")
print(env["averages"]) # {'bitcoin': 39, 'ethereum': 7}
best = max(env["averages"], key=env["averages"].get)
print(f"{best} dominates {env['geo']} search interest")
pytrends compatibility (trendspyg.compat)¶
New in 1.9.0. pytrends 4.9.2's TrendReq, so pytrends code runs after its
imports change from pytrends to trendspyg.compat. Requires pandas
(pip install trendspyg[analysis]). Not imported by import trendspyg.
from trendspyg.compat.request import TrendReq
from trendspyg.compat.exceptions import ResponseError, TooManyRequestsError
TrendReq(hl="en-US", tz=360, geo="", timeout=(2, 5), proxies="", retries=0, backoff_factor=0, requests_args=None, *, cache=False, cookies=False, headless=True, engine="auto")
pytrends' arguments, plus four keyword-only trendspyg options:
cache:Falseor"disk". Serve an identical recent payload from the local disk cache with no browser session.cookies:Falseor"disk". Reuse Google's session cookies between sessions (seeclear_explore_cookies).headless: run Chrome without a window.engine:"auto"(default) asks Google directly and opens a browser session only if Google refuses;"browser"always uses the browser;"http"never starts Chrome. Seeengineondownload_google_trends_interest_over_time.
hl sets the language of suggestions() and categories(). tz, timeout,
retries, backoff_factor and requests_args are accepted and not applied.
proxies (or requests_args["proxies"]) raises InvalidParameterError,
because requests come from this machine's browser.
| Method | Returns | Served by |
|---|---|---|
build_payload(kw_list, cat=0, timeframe="today 5-y", geo="", gprop="") |
None; validates 1-5 keywords, geo, category and property before any browser starts |
- |
interest_over_time() |
DataFrame: date index, one int64 column per keyword, isPartial |
one fetch per payload: direct, or a browser session if Google refuses |
interest_by_region(resolution="COUNTRY", inc_low_vol=False, inc_geo_code=False) |
DataFrame: sorted geoName index, optional geoCode, one int64 column per keyword |
the same fetch |
related_queries() |
{kw: {"top": DataFrame or None, "rising": DataFrame or None}} with query, value |
the same fetch (after a browser fallback, one more session per keyword for 2-5 keywords) |
related_topics() |
{kw: {"rising": None, "top": None}} and a CompatWarning |
- |
suggestions(keyword) |
[{"mid", "title", "type"}, ...] |
HTTP |
categories() |
Google's category tree | HTTP |
trending_searches(pn="united_states") |
one-column DataFrame of searches | Trending Now RSS |
today_searches(pn="US") |
Series query |
Trending Now RSS |
realtime_trending_searches(pn="US", cat="all", count=300) |
DataFrame title, entityNames |
Trending Now RSS |
top_charts(...), multirange_interest_over_time(), get_historical_interest(...) |
raise NotImplementedError |
- |
interest_by_region follows pytrends' rules for resolution: worldwide
payloads serve "COUNTRY"; for geo="US", "REGION" (states) and "DMA"
(metro areas) work; other geos keep Google's own level. "CITY" raises
NotImplementedError. inc_low_vol=True includes low-volume regions. Regions
Google reports without data are omitted.
Exceptions: a Google rate limit raises TooManyRequestsError (also a
RateLimitError); unusable data raises ResponseError. Missing Chrome and bad
arguments keep their trendspyg types (BrowserError, InvalidParameterError).
See Migrating from pytrends.
Normalized Output¶
Pass normalize=True to download_google_trends_rss, download_google_trends_rss_async,
or download_google_trends_csv to receive a NormalizedEnvelope — one JSON-native
schema identical across both data paths, so a consumer (or AI agent) learns one shape.
output_format is ignored when normalize=True.
from trendspyg import download_google_trends_rss
env = download_google_trends_rss(geo='US', normalize=True)
Envelope structure:
{
'schema_version': '1.1',
'source': 'rss', # or 'csv'
'geo': 'US',
'fetched_at': '2026-05-22T01:00:00+00:00',
'count': 10,
'request': {}, # CSV: hours/category/active_only/sort_by
'trends': [ ... ] # list of NormalizedTrend
}
Schema 1.1 adds request: CSV request settings, {} for RSS. sort_by does not control CSV export order. Historical schema-1.0 archives remain readable without this field. See interpretation and workflows.
NormalizedTrend — every field is always present and JSON-safe:
| Field | Type | Description |
|---|---|---|
keyword |
str |
Search term, verbatim from the source |
rank |
int |
1-based position in source ordering |
volume_text |
str |
Raw human-readable volume, e.g. '5M+' |
volume_min |
int |
Parsed lower bound of volume_text |
started_at |
str \| None |
RSS publication time or CSV start time, ISO 8601 |
ended_at |
str \| None |
ISO 8601 end time (None if still active) |
is_active |
bool |
True when ended_at is None |
related_queries |
list[str] |
Related searches (CSV path); [] for RSS |
news |
list |
News articles (RSS path); [] for CSV |
image |
obj \| None |
Trend image |
explore_url |
str |
Google Trends Explore URL |
TypedDicts are importable: from trendspyg import NormalizedEnvelope, NormalizedTrend.
Cache Functions¶
clear_rss_cache¶
Clear all cached RSS data.
def clear_rss_cache() -> None
Example:
from trendspyg import clear_rss_cache
clear_rss_cache() # Clear all cached data
clear_explore_cookies¶
Delete the Explore session cookie jar written by cookies="disk" (new in 1.6.0).
def clear_explore_cookies(path: Optional[str] = None) -> bool
path defaults to the TRENDSPYG_COOKIES env var, else explore_cookies.json
beside the archive DB. Returns True if a file was removed.
from trendspyg import clear_explore_cookies
clear_explore_cookies() # start the next Explore session as a new visitor
get_rss_cache_stats¶
Get cache statistics.
def get_rss_cache_stats() -> Dict[str, Any]
Returns:
{
'hits': 10, # Cache hits
'misses': 5, # Cache misses
'size': 8, # Current entries
'max_size': 256, # Maximum entries
'ttl': 300.0, # TTL in seconds
'hit_rate': '66.7%' # Hit rate percentage
}
Example:
from trendspyg import get_rss_cache_stats
stats = get_rss_cache_stats()
print(f"Cache hit rate: {stats['hit_rate']}")
set_rss_cache_ttl¶
Set cache TTL (Time-To-Live).
def set_rss_cache_ttl(ttl: float) -> None
Parameters:
| Parameter | Type | Description |
|---|---|---|
ttl |
float |
TTL in seconds (0 to disable caching) |
Example:
from trendspyg import set_rss_cache_ttl
set_rss_cache_ttl(600) # 10 minutes
set_rss_cache_ttl(0) # Disable caching
set_rss_cache_ttl(300) # Reset to default (5 min)
The TTL also governs the disk cache (cache="disk", below).
Archive Functions¶
New in 1.3.0; Explore support in 1.4.0. Google's trending feed is ephemeral —
nothing anywhere tells you "what was trending on date X". Opt in to archiving
and every fetch records a snapshot to one local SQLite file (Python stdlib —
no server, no keys, no new dependencies). The same file also holds the
persistent caches: the RSS cache (cache="disk", RSS TTL knob) and, since
1.4.0, the Explore cache (hours-scale freshness, own table).
Recording (opt-in kwargs on the download functions):
from trendspyg import download_google_trends_rss, download_google_trends_csv
from trendspyg import download_google_trends_interest_over_time
download_google_trends_rss(geo="US", archive=True) # archive this fetch
download_google_trends_rss(geo="US", cache="disk") # persistent cache
download_google_trends_csv(geo="US", archive=True) # CSV path archives too
# Explore path (1.4.0): all three functions — interest_over_time, explore,
# comparison. The disk cache matters most here: a hit skips a 10-40s
# rate-limited browser run entirely.
download_google_trends_interest_over_time("bitcoin", cache="disk", archive=True)
Only fresh fetches are archived (cache hits are not re-recorded), and an
archive/cache write failure never breaks the download — it emits a
RuntimeWarning and the fetch returns normally. Explore snapshots store the
full envelope with source "explore" / "explore_comparison"; each keyword
is indexed for the query functions below (with rank/volume_min None —
research queries have no trending rank).
Default location: %LOCALAPPDATA%\trendspyg\trendspyg.db (Windows),
~/Library/Application Support/trendspyg/ (macOS), $XDG_DATA_HOME or
~/.local/share/trendspyg/ (Linux). Override per call with db_path= or
globally with the TRENDSPYG_DB env var. Safe for concurrent processes
(WAL mode; e.g. trendspyg watch and CLI calls writing at once).
read_archive()¶
read_archive(
geo=None, source=None, # source: "rss"/"csv"/"explore"/"explore_comparison",
start=None, end=None, # or a sequence like ("rss", "csv") (1.4.0)
keyword=None, # only snapshots containing it (case-insensitive)
limit=None, # newest N
output_format="dict", # "dict" | "json" | "dataframe"
db_path=None,
)
Returns archived envelopes newest first (dict = list of
NormalizedEnvelope; dataframe = one row per trend, needs [analysis]).
A fresh/nonexistent archive reads as empty. Raises ArchiveError if the file
is unreadable, InvalidParameterError on bad arguments.
get_keyword_history()¶
get_keyword_history("bitcoin", geo=None, start=None, end=None,
source=None, # single value or sequence (new in 1.4.0)
db_path=None)
# -> [{"fetched_at", "geo", "source", "rank", "volume_min"}, ...] oldest first
Every archived appearance of a keyword, answered from an indexed table (no
envelope loading) — "when did X first trend, and how did it move?" The return
shape is the KeywordHistoryPoint TypedDict. Explore-path appearances (1.4.0)
show up with rank/volume_min None; pass source=("rss", "csv") to keep
the timeline strictly "it trended", or source="explore" for "I researched it".
get_archive_stats()¶
get_archive_stats(db_path=None)
# -> {"db_path", "db_size_bytes", "snapshot_count", "trend_row_count",
# "geos", "sources", "first_fetched_at", "last_fetched_at",
# "cache_entries", "explore_cache_entries"} # explore_cache_entries: 1.4.0
prune_archive()¶
prune_archive("2026-01-01", geo=None, source=None, db_path=None) # -> deleted count
Deletes snapshots fetched strictly before the cutoff (datetime or ISO
string; geo/source narrow it). Nothing in the archive expires on its own —
deletion is always explicit. Sizing: ~15 KB per RSS snapshot (roughly
130-260 MB/year at hourly cadence); ~4-26 KB per Explore snapshot depending on
timeframe and widgets. (Explore cache entries are separate and do expire: an
opportunistic 30-day garbage collection reclaims abandoned keys.)
CLI equivalent: trendspyg history (see CLI.md) — snapshots,
--timeline -k <kw>, --stats, --prune-before.
Exceptions¶
All exceptions inherit from TrendspygException. Since v1.0.0 they are
importable straight from the package root (trendspyg.exceptions also remains
valid — both paths are the same classes):
from trendspyg import (
TrendspygException, # Base exception
InvalidParameterError, # Invalid input parameters
DownloadError, # Network/download failures
RateLimitError, # Rate limit exceeded (429/403)
BrowserError, # Browser automation failures
ParseError, # Data parsing failures
ArchiveError, # Local archive unreadable (1.3.0; writes warn, never raise)
)
Example:
from trendspyg import download_google_trends_rss
from trendspyg import InvalidParameterError, RateLimitError
try:
trends = download_google_trends_rss(geo='INVALID')
except InvalidParameterError as e:
print(f"Invalid parameter: {e}")
except RateLimitError as e:
print(f"Rate limited: {e}")
Configuration¶
Countries (125 total)¶
from trendspyg.config import COUNTRIES
# Example: {'US': 'United States', 'GB': 'United Kingdom', ...}
print(list(COUNTRIES.keys())[:10])
# ['US', 'GB', 'CA', 'AU', 'IN', 'DE', 'FR', 'BR', 'MX', 'JP']
US States (51 total)¶
from trendspyg.config import US_STATES
# Example: {'US-CA': 'California', 'US-NY': 'New York', ...}
print(list(US_STATES.keys())[:5])
# ['US-AL', 'US-AK', 'US-AZ', 'US-AR', 'US-CA']
Categories (20 total)¶
from trendspyg.config import CATEGORIES
# Available categories:
# 'all', 'sports', 'entertainment', 'business', 'politics',
# 'technology', 'health', 'science', 'games', 'shopping',
# 'food', 'travel', 'beauty', 'hobbies', 'climate',
# 'jobs', 'law', 'pets', 'autos', 'other'
Time Periods¶
| Hours | Description |
|---|---|
4 |
Past 4 hours |
24 |
Past 24 hours (default) |
48 |
Past 48 hours |
168 |
Past 7 days |
Monitoring¶
Real-time monitoring built on the RSS path (new in 0.7.0). The diff core is pure and JSON-safe (no network, no browser).
watch_google_trends_rss¶
watch_google_trends_rss(
geo="US", interval=60, iterations=None, *,
on_change=None, min_volume=None, events=None, keywords=None,
webhook=None, sleep=time.sleep, include_context=False, **rss_kwargs
) -> Iterator[TrendChange]
Polls download_google_trends_rss(geo, cache=False) every interval seconds and
yields each change between consecutive snapshots. The first poll is the baseline
(yields nothing). iterations=None runs until the caller stops iterating; otherwise
it stops after N polls. Filters: min_volume, events, keywords (see
filter_changes). webhook POSTs each change as JSON (fire-and-forget). RSS-only —
safe for continuous polling. Use archive=True, db_path="trends.sqlite3" to retain polls, including the baseline. include_context=True adds geo and observed_at to each event (monitor schema 1.1); defaults keep the six-field event shape.
diff_trends / filter_changes / post_webhook¶
diff_trends(old, new) -> list[TrendChange] # pure, no network
filter_changes(changes, *, min_volume=None, events=None, keywords=None) -> list[TrendChange]
post_webhook(url, change, timeout=10.0) -> bool # 2xx -> True; never raises
TrendChange = {event, keyword, rank, prev_rank, volume_min, prev_volume_min},
event ∈ {new, dropped, volume_up, volume_down, rank_change}. rank/volume_min
are None for a dropped trend; prev_* are None for a new one.
CLI¶
trendspyg watch --geo US --interval 60 --events new,volume_up --min-volume 50000
Streams one NDJSON change per line (stdout stays pipe-clean).
MCP Server¶
New in 0.8.0. Expose trendspyg to Claude and any MCP-compatible client as native
tools — no Python needed on the agent side. Requires Python 3.10+ (the core library
still supports 3.8+); runs on the MCP SDK v2 stable line or v1 (mcp>=1.27,<3 —
the server detects which is installed).
pip install trendspyg[mcp]
trendspyg-mcp # stdio transport
claude mcp add trendspyg -- trendspyg-mcp # register in Claude Code
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"trendspyg": { "command": "trendspyg-mcp" }
}
}
| Tool | Speed | Browser | Returns |
|---|---|---|---|
suggest_keywords(keyword, language="en-US") |
network-dependent; cached repeats instant | No | {query, language, count, suggestions: [{mid, title, type}]} |
get_trending_now(geo, archive=False) |
~0.2–2s | No | NormalizedEnvelope (~10–20 trends + news) |
compare_trending(geos, compact=False, archive=False) |
~0.2–2s/geo | No | {geo: NormalizedEnvelope}, 1–20 geos; compact=true keeps keyword/rank/volume_min/is_active per trend only (~16× smaller) (1.6.0) |
get_trend_changes(geo, archive=False) |
~0.2–2s | No | new/dropped/volume/rank changes since last call |
list_supported_options() |
instant | No | geo codes, categories, hours, timeframes |
get_interest_over_time(keyword, geo, timeframe, gprop) |
~10–40s fresh; instant on cached repeats (1.4.0) | Yes | [{date, value, is_partial}] — gprop selects web/YouTube/News/Images/Shopping (1.5.0) |
compare_interest_over_time(keywords, geo, timeframe, gprop) |
~10–40s fresh; instant on cached repeats (1.4.0) | Yes | ComparisonEnvelope — 2–5 keywords, one shared scale (new in 1.1.0) |
get_trending_full(geo, hours, category, archive=False) |
~10–15s | Yes | NormalizedEnvelope (480+ trends) |
get_trending_history(geo, keyword, start, end, limit) |
instant | No | compact archived snapshots + keyword timeline (new in 1.3.0; local archive only) |
Tools read Google data; optional archiving writes only to the local database. Enable archive=true on the Trending Now fetch tools from the first call to build history. Cached reads do not add observations; get_trend_changes(archive=true) performs a fresh poll. The browser-backed tools carry explicit latency and
rate-limit warnings in their descriptions so agents prefer the fast RSS tools.
Since 1.4.0 the two interest tools use the Explore disk cache: an identical
repeat question within the freshness window (1h for "now *" timeframes, 24h
otherwise) answers instantly, and the cache survives server restarts.
get_trend_changes keeps its baseline per geo in server memory — restarting the
server resets it. get_trending_history reads only what was archived on this
machine (archive=True / --archive), and only Trending-Now sources (rss/csv) —
archived Explore research queries never appear as "was trending". An empty
result means nothing was recorded for those filters, not that nothing trended.
Type Aliases¶
from typing import Literal
# CSV path (download_google_trends_csv)
OutputFormat = Literal['csv', 'json', 'parquet', 'dataframe', 'dict']
# RSS path (download_google_trends_rss / _async / _batch) accepts the same
# names minus 'parquet' — i.e. 'dict', 'dataframe', 'json', 'csv'.
SortOption = Literal['relevance', 'title', 'volume', 'recency'] # CSV sort_by
Performance Tips¶
1. Use Caching¶
# Results cached for 5 minutes by default
trends = download_google_trends_rss(geo='US') # Network call
trends = download_google_trends_rss(geo='US') # Instant (cached)
2. Use Async for Multiple Countries¶
# Sequential: ~5 seconds for 10 countries
# Parallel: ~0.5 seconds for 10 countries
results = await download_google_trends_rss_batch_async(countries)
3. Minimize Data When Possible¶
# Faster if you don't need images/articles
trends = download_google_trends_rss(
geo='US',
include_images=False,
include_articles=False
)
4. Use Shared Sessions¶
import aiohttp
async with aiohttp.ClientSession() as session:
tasks = [
download_google_trends_rss_async(geo=c, session=session)
for c in countries
]
results = await asyncio.gather(*tasks)
Version¶
from trendspyg import __version__
print(__version__) # '1.9.0'