Not affiliated with, endorsed by, or sponsored by Google LLC. "Google" and "Google Trends" are trademarks of Google LLC, used here only to describe the public data discussed.
If you have ever built anything on top of "Trending Now" (a newsletter, an alerting bot, a content calendar), you know the uncomfortable part: a ranked list tells you what is trending, not for how long. A two-hour celebrity spike and a two-day story that is still growing look identical at rank 3.
Google's own Trending Now page actually knows the difference. Each trend has a start time and, once it stops trending, an end time. The interface shows a small "Active" / "Lasted N hours" label and moves on. Most tools that re-publish the feed drop that information on the floor.
This post is about keeping it, what the numbers look like once you do, and the limits of the approach.
The four fields that matter
For each trending term, keep four fields:
| field | meaning |
|---|---|
started_at |
Unix timestamp when the term entered the trending feed |
ended_at |
Unix timestamp when it left, or null while it is still trending |
is_active |
true while ended_at is null
|
duration_minutes |
ended_at - started_at, in minutes, once the trend has ended |
Everything else (volume, category, rank) is the same data every list gives you. These four turn a list into a lifecycle.
What the numbers look like
Measured on one live pull of the US feed, 24-hour window:
- 421 trending terms
- 123 still active, 298 already ended
- median lifetime of the ended ones: about 2 hours
GB, 48-hour window: 530 of 615 terms had already ended by the time of the pull.
So: most of what is "trending right now" has already finished trending. If your pipeline reacts to the list once a day, roughly 70% of what it reacts to is over. Filtering on is_active == true is the single cheapest improvement you can make to a trend-driven workflow. Sorting the ended ones by duration_minutes is the second: it separates the stories with legs from the spikes.
Getting the data
We maintain a small Apify Actor that returns the Trending Now feed with these four fields on every row: Google Trends Scraper. Disclosure: it is our paid tool (pay-per-event: $0.01 per run plus $0.0005 per row on the free Apify tier; a run that returns zero rows costs nothing), so weigh the recommendation accordingly. The idea itself works with any source that exposes start and end times for a trend.
One synchronous request, results back as JSON:
curl -X POST \
"https://api.apify.com/v2/acts/orbots~google-trends-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mode": "trending", "trendingGeos": ["US"], "trendingHours": 24}'
A row looks like this:
{
"term": "japan earthquake",
"rank": 3,
"geo": "US",
"started_at": 1785224400,
"ended_at": 1785238800,
"is_active": false,
"duration_minutes": 240,
"search_volume": 200000,
"formatted_volume": "200K+",
"category": "Law and Government",
"trends_url": "https://trends.google.com/trends/explore?q=japan+earthquake&date=now+1-d&geo=US&hl=en-US"
}
And the ten longest-lived ended trends, in Python:
import os
import requests
r = requests.post(
"https://api.apify.com/v2/acts/orbots~google-trends-scraper/run-sync-get-dataset-items",
params={"token": os.environ["APIFY_TOKEN"]},
json={"mode": "trending", "trendingGeos": ["US"], "trendingHours": 24},
timeout=300,
)
r.raise_for_status()
rows = r.json()
live = [x for x in rows if x["is_active"]]
ended = sorted((x for x in rows if not x["is_active"]),
key=lambda x: x["duration_minutes"], reverse=True)
print(f"{len(live)} active, {len(ended)} ended")
for x in ended[:10]:
print(f'{x["term"]:<32} {x["duration_minutes"]:>5} min {x["formatted_volume"]}')
run-sync-get-dataset-items blocks until the run finishes (three countries came back in 26 seconds in our benchmark) and returns the dataset directly, so there is no polling loop to write.
Running it on a schedule without paying for the same list twice
A daily schedule against a trending feed has a built-in waste problem: most of today's list was also on yesterday's. The Actor has an onlyNew flag that remembers, per country and window, which (term, start time) pairs it has already delivered to you, and returns only the additions. A topic that starts trending again later counts as new, because the key includes the start time. A day with nothing new ends as a successful run with zero rows, and zero charge.
If you are building the memory yourself, that key (term plus started_at) is the detail that matters. Keying on the term alone swallows every comeback.
What this does not do (read before you build on it)
- No related queries or related topics. The endpoint behind those rejects automated access, so the Actor does not advertise them.
-
Search volume is Google's estimate bucket (
200K+), not an absolute count. It is comparable across terms in the same feed; it is not a keyword-research number. - Trend duration is measured by the feed. It is how long Google kept the term in Trending Now. It is a good proxy for "how long people cared", not a definition of it.
- Keyword mode (interest over time / by region) exists but is slower and far more rate-limited than the trending feed. If you need breadth, start with Trending Now.
Takeaways
- A trending list without
ended_atis a snapshot pretending to be a stream. - On a live US pull, ~70% of "trending now" had already ended; median life about 2 hours.
- Filter on
is_active, sort onduration_minutes, key your memory on(term, started_at).
Data is public, aggregated search interest; no personal data is involved.
orbots builds small, reliable data Actors on Apify.
Top comments (0)