Twitter Trends Scraper avatar

Twitter Trends Scraper

Pricing

$19.99/month + usage

Go to Apify Store
Twitter Trends Scraper

Twitter Trends Scraper

Twitter Trends Scraper extracts trending topics from Twitter worldwide or by region. Get trend names, tweet volumes, and links. Ideal for social listening, content strategy, and research. Export data in JSON, CSV, or Excel.

Pricing

$19.99/month + usage

Rating

5.0

(4)

Developer

Scraper Engine

Scraper Engine

Maintained by Community

Actor stats

0

Bookmarked

32

Total users

0

Monthly active users

5 days ago

Last modified

Share

Twitter Trends Scraper returns Twitter/X trending topics as structured JSON: the trend name, its board rank, the page's real UTC snapshot hour, the market it trended in, and a canonical per-trend URL. Pick one of 62 countries or worldwide, tick Live and any of the 23 hourly rewind toggles, and each enabled slice arrives as its own labelled set of rows. No login, cookie, API key or session token is involved. Nothing is required — open it and press Start.

⚠️ Read the section on what this Actor does not return before your first run. There is no tweet-volume field, date-based archive slices are gone, and an unsupported country code falls back to worldwide instead of failing. All three are explained below.

Twitter Trends Scraper is an Apify Actor that reads the public, logged-out HTML of getdaytrends.com — a third-party Twitter/X trends aggregator — and shapes each board row into one flat, typed record. It is not the X API and it does not call x.com: one HTTP GET per selected time slice, no pagination, no browser.

No Twitter/X account, login, cookie, bearer token or API key is required, and no captcha-solver key or other third-party credential either. The only credential in play is your Apify token. Requests go out anonymously and, by default, without a proxy.

It is built for social media managers tracking what is breaking in a specific market, content and news teams that need the hour-by-hour shape of a trend rather than a single snapshot, and developers feeding trending topics into dashboards, alerting jobs or agent tools.

What Twitter trend data is publicly available to scrape?

The trends page this Actor reads publishes, to any logged-out visitor, a 50-row ranked board per market plus two sidebar panels — and it publishes the snapshot hour that board belongs to inline in the page. It does not publish the posts behind a trend, and it no longer serves date-based archives.

Data CategoryOn the public trends pageNot available to this Actor
Trend name — hashtag or plain phrase✅ Public
Board rank, 1-50✅ Public
The board's real UTC snapshot hour✅ Public — inline in the page
Hourly rewind, 1 to 23 hours back✅ Public — one page per hour
"Top by score" and "longest trending" side panels✅ Public — opt-in, off by default
Canonical per-trend page URL✅ Public
Tweet / post volume per trendNot published — X removed the count display, and the field measured empty at every source tested
Date archives: yesterday, last week, last month, last yearThe source answers every archive URL with HTTP 520
The posts, authors and engagement behind a trendNot on this page at all — no handles, no post text, no counts
City-level targets, WOEIDs, multi-country runsOne market per run by design

Twitter Trends Scraper only returns publicly visible data — what any visitor sees on a logged-out trends page. Nothing behind a login wall.

⚠️ No tweet volume, no date archives, and a silent country fallback

Three things about this Actor are worth knowing before you wire it into anything, so they get their own section instead of a footnote.

There is no volume field, and that is deliberate. Competing Twitter trends Actors advertise a tweet-volume column; this one does not have one. The field was measured empty at every known source, and the fallback selector that used to fill it was emitting the trend name into the volume column on a share of live rows — silent corruption. It was deleted outright rather than shipped as a permanently-null column. If your pipeline needs volume, this Actor cannot give it to you.

Date-based archive slices are gone. Yesterday, two days, three days, a week, a month and a year ago all resolve to the source's /{country}/{date}/24/ URL space, and every one of those returns HTTP 520 upstream. The toggles were removed from the input form. The six legacy keys — yesterday, day2, day3, weekAgo, monthAgo, yearAgo — are still accepted so an older input JSON still validates and runs, but each one produces a loud warning and one uncharged accounting row carrying errorReason: "unsupported_time_slice_upstream_520". It never silently returns nothing.

An unsupported country code falls back to worldwide. The country dropdown is a closed list, so this cannot happen from the Console — but a code passed over the API that is not in the map logs Unknown/unsupported country code '<code>' — using worldwide trends instead and then runs the worldwide board. The run succeeds, rows are charged, and country comes back null with countryName: "Worldwide". Check countryName on the first row before trusting a market-specific result. The same rule applies to startUrls: a URL whose slug is not one of the 62 known markets is stamped country: null and countryName: "Worldwide", whatever market the page actually showed.

Every row carries the trend and its position, the provenance of the row — which panel it came from, which time slice, which market — and both URLs involved. Twelve keys on a result row, thirteen on an accounting row. The default dataset view shows twelve of the thirteen, so countrySlug is in the JSON but not in that table view, and errorReason is in the view but only ever populated on accounting rows.

Field NameDescription
trendThe trending topic exactly as published — a hashtag like #Wimbledon or a plain phrase. A row with no trend text is skipped entirely rather than emitted empty
rankInteger position read from the row's own position cell. On a board row this is the board rank; on a panel row it is that panel's position. null when the page publishes no position cell for the row
sourcePanelWhich table the row came from: board, top_by_score, longest_trending, or top_panel when a panel's own text matches neither of the two known kinds
panelDetailThe panel's own figure, as text — for example 564 score or 14 hours. null on every board row
timePeriodThe slice this row belongs to: Live, 1 hour ago, 2 hours ago23 hours ago, or Custom for a row from startUrls
snapshotTimeUtcThe board's real UTC hour, ISO 8601, read from the currentDate JavaScript Date the page publishes inline. Hour granularity — minutes and seconds are always zero. null when the page omits it or the value does not parse
timeWhen this row was scraped, ISO 8601 UTC with microseconds. Identical for every row parsed from the same page
countryISO alpha-2 code for the market, e.g. GB. null on a worldwide run
countrySlugThe source's own URL slug for the market, e.g. united-kingdom. null on a worldwide run. Present in the JSON but not in the default dataset view
countryNameHuman-readable market name, e.g. United Kingdom, or Worldwide when no market applies
trendPageUrlCanonical per-trend page URL, normalised to the worldwide form https://getdaytrends.com/trend/<trend>/. The board's own country-scoped hrefs answer HTTP 520, so emitting them raw would ship a dead link column
sourceUrlThe exact page this row was parsed from — useful for auditing which slice produced what. null on the accounting rows written for the retired date keys
errorReasonPresent only on uncharged accounting rows. Typed reason string. Omitted entirely from result rows rather than set to null, so rec.get("errorReason") is None is the clean-row filter

Field names are camelCase throughout, but the values of sourcePanel are snake_case — top_by_score, longest_trending, top_panel. Compare against those exact strings.

Trend, rank and panel fields

trend and rank are the two you sort and threshold on, and sourcePanel is what keeps them meaningful. The page renders four visually similar ranking tables: two of them are the actual 50-trend board, split as ranks 1-15 and the collapsed remainder, and the other two are sidebar panels whose rows repeat board trends with an entirely different meaning. Sweeping all four into one list produces a 70-row result for a 50-trend board. Here they are separated and labelled, so sourcePanel == "board" is always the ranked board and nothing else. Panels are off by default; turn on includeTopPanels to add them, each row carrying its own panelDetail.

Time, market and provenance fields

timePeriod is the label you asked for; snapshotTimeUtc is the hour the page says the board actually belongs to. They are not the same thing, and the second is the one to join on when you are stitching slices from different runs into a timeline — a run started at 09:58 UTC and a run started at 10:02 UTC produce different time values for the same underlying Live board, but the same snapshotTimeUtc. country, countrySlug and countryName are stamped from the URL that was actually fetched rather than from the dropdown, so a startUrls entry pointing at a different market reports that market instead of inheriting the dropdown's.

🤖 Add-on: Need additional Twitter/X data?

Trends tell you what is moving; they do not tell you what people are saying. X Twitter Posts Search takes a trend name straight out of the trend field and returns the posts behind it, and Twitter X Reply Scraper goes a level deeper into the conversation. For cross-platform coverage of the same question, TikTok Trending Hashtags Scraper and Reddit Trends Scraper cover the two places a trend usually shows up next.

Why not build this yourself?

One page, one GET, one table — it looks like an afternoon's work. What makes it a maintenance job is that the source's URL space keeps moving underneath you, and it fails in ways that return HTTP 200.

The archive endpoints that used to serve yesterday and last week now answer 520. Five country slugs that once worked now 404. The per-trend links the board itself renders answer 520 in their country-scoped form while the worldwide form answers 200, so a naive scrape ships a link column where every link is dead. The volume column that every competing listing advertises measured empty at three separate sources, and the obvious fallback selector quietly wrote the trend name into it. A soft block does not arrive as a 403 — it arrives as a 200 with the wrong page, which is why this Actor treats a response as good only when the expected result container is present, never on the status code alone. The snapshot hour is not in the markup at all; it sits inside an inline JavaScript Date constructor with a zero-indexed month.

None of that is visible until it is in production, and each fix costs you a debugging session and a redeploy. X does publish an official API, whose trends coverage sits on paid tiers and has changed repeatedly — check X's current published API documentation if you want the supported route, since those terms change and are deliberately not reproduced here.

Twitter Trends Scraper runs on Apify. Start it from the Apify Console or call it through the Apify API.

  1. Open Twitter Trends Scraper on Apify and click Try for free
  2. Pick a market in 🌍 Country — or leave it empty for worldwide trends. One market per run
  3. Leave 🔴 Live now on for the board as it stands right now, and tick any of the hourly toggles you also want. They are not mutually exclusive: Live plus three hours gives you four boards in one run
  4. Optionally turn on 🏆 Also include the two side panels to add the "top by score" and "longest trending" rows, labelled in sourcePanel
  5. Click Start, then export as JSON, CSV or Excel, or read the dataset through the Apify API

Nothing is required, so an empty input is a valid run. With no input at all the Actor targets worldwide, finds no time slice selected, logs No time slice selected — defaulting to Live. and scrapes the single live worldwide board with panels off, three retries, a 30-second timeout and no proxy. Two independent paths reach the same place: the schema defaults live to true, and the code re-enables it whenever no slice and no startUrls are set. The consequence is that live: false on its own is overridden — to run hourly slices only, set live: false and tick at least one hour toggle.

How to scale to bulk trend extraction

Bulk here means time and URLs, not countries: country is a single string, so one run covers one market. Within that market you can queue up to 24 boards at once — Live plus hour1 through hour23 — each one its own HTTP GET and its own labelled block of rows. 🔗 Extra getdaytrends URLs (startUrls) accepts any number of additional country or hourly pages directly, which is also the way to cover several markets in a single run; those rows come back with timePeriod: "Custom" and the market read from the URL. URLs are deduplicated by exact string before fetching, so a startUrls entry that matches a toggle-built URL character-for-character is fetched once. For many markets on a repeating basis, the usual pattern is one scheduled run per market.

What can you do with Twitter trend data?

  • 📰 A newsroom social editor watching a single market runs Live plus hour1 and hour2, then diffs trend across the three timePeriod values to separate a story that is climbing from one that already peaked.
  • 📈 A social media manager planning same-day posts sorts on rank where sourcePanel is board and uses trendPageUrl to open the source page for any trend whose meaning is not obvious from the name alone.
  • 🧵 A brand monitoring analyst schedules hourly runs for one market and matches trend against a watch list, using snapshotTimeUtc rather than time as the join key so repeated runs of the same board collapse into one row per hour.
  • 🏆 A trend researcher turns on includeTopPanels and reads panelDetail on longest_trending rows to find topics with real staying power rather than ones that spiked for ten minutes.
  • 🤖 An AI engineer building a content agent indexes trend, countryName and snapshotTimeUtc into a vector store so the agent can answer "what was trending in Japan at 03:00 UTC" from observed rows instead of a model's stale training data.
  • 🌍 A market researcher comparing regions queues several markets through startUrls in one run and groups the dataset by countryName to see which topics are local and which cross borders.

Every one of these is callable from an agent framework over the Apify API, since the Actor is a standard HTTP-triggered run.

The source serves this Actor without a proxy, and that is the default: proxy prefills useApifyProxy: false and a plain run goes out on a direct connection, logging Proxy: NO_PROXY. Requests carry a native Linux-container browser fingerprint rather than a default HTTP client signature — the user-agent and the sec-ch-ua-platform client hint both say Linux, deliberately, because an OS-versus-stack mismatch is a well-known hard-deny signal.

If you do select a proxy, it is validated before the run starts: the Actor makes one test request through it and, if that fails, falls back to Apify RESIDENTIAL and then to a direct connection, warning at each step. The tier it ended on is logged and written into RUN_SUMMARY.json as proxyUsed. Only the first entry of proxyUrls and the first Apify proxy group are used; a session ID is chosen once per run and reused for every request in it.

A response counts as good only when it is a 200 and contains the expected result container, so a soft block or a wrong page is caught rather than parsed into hollow rows. Retries are exponential with jitter — roughly 1, 2, 4 then 8 seconds — up to maxRetries extra attempts. Two statuses skip retrying entirely because retrying cannot help: 520, the source's own origin failure, and 404. There is no captcha solving anywhere in this Actor, because the page does not put a challenge in front of these requests.

When a slice cannot be fetched or parses to zero rows, the run does not stop: that slice gets one uncharged accounting row and the remaining slices continue.

⬇️ Input

Thirty parameters, none of them required, read straight from the input schema. The 23 hourly toggles share one identical shape and are collapsed into a single row below.

ParameterRequiredTypeDescriptionExample Value
countryNostringMarket to pull the trend board for. Closed enum of 63 values: "" for worldwide plus 62 ISO alpha-2 codes, all verified live against the source's own country list. One market per run. Default ""."GB"
liveNobooleanScrape the board as it stands right now, ranks 1-50. Default true. Re-enabled automatically if no slice and no startUrls are set, so false on its own has no effect.true
includeTopPanelsNobooleanAdds the page's two sidebar panels — "top by score" and "longest trending", 10 rows each — labelled in sourcePanel and carrying panelDetail. Off by default because their rows repeat board trends with different meaning. Default false.false
hour1hour23Noboolean23 separate parameters, one per hour of rewind: hour1, hour2, hour3hour23. All boolean, none declares a default in the schema, so all are off unless you tick them. See the note below for exactly what enabling one does.true
startUrlsNoarrayExtra getdaytrends country or hourly pages to scrape directly, on top of the toggles. requestListSources editor. Rows are labelled timePeriod: "Custom" and stamped with the market read from the URL. Default [].[{"url": "https://getdaytrends.com/japan/5/"}]
maxRetriesNointegerExtra attempts per URL for a page that fails or comes back without a trend table, with exponential backoff. Minimum 0, maximum 10, default 3. 0 means a single attempt.3
requestTimeoutSecsNointegerPer-request HTTP timeout in seconds. Also used for the proxy validation request. Minimum 5, maximum 300, default 30.30
proxyNoobjectProxy strategy, proxy editor. Prefilled {"useApifyProxy": false} — no proxy, which is what the source expects. An unreachable selection falls back to Apify RESIDENTIAL, then to a direct connection.{"useApifyProxy": false}

What the hourly toggles actually are. Each one selects an hour-of-rewind archive page, not a snapshot cadence and not an hour of the day. hour5 fetches the board as it stood 5 hours ago from a single page at /{country}/5/, once, inside the same run — it does not schedule anything and it does not poll. The range is hour1 to hour23; there is no hour24, because the 24-hour and older slices live in the archive URL space that answers HTTP 520. Enabling one adds exactly one HTTP GET and one more labelled block of rows to the same dataset, tagged timePeriod: "5 hours ago" and stamped with that page's own snapshotTimeUtc. They are fully independent of live and of each other, so ticking Live plus all 23 hours is a legitimate 24-board run.

Two more things the schema does not show:

The six retired date keys — yesterday, day2, day3, weekAgo, monthAgo, yearAgo — are still accepted for backward compatibility. Each produces a warning and an uncharged accounting row rather than being ignored in silence.

Time flags are also read from a nested flags object, so {"flags": {"hour3": true}} is equivalent to {"hour3": true}. Top-level keys take precedence. This exists for older inputs; use the top-level form.

Example input

The United Kingdom board now, plus three hourly rewinds and the side panels, with one extra market added by URL. The hourly toggles not listed are omitted, which is the same as false.

{
"country": "GB",
"live": true,
"includeTopPanels": true,
"hour1": true,
"hour2": true,
"hour6": true,
"startUrls": [
{ "url": "https://getdaytrends.com/japan/5/" }
],
"maxRetries": 3,
"requestTimeoutSecs": 30,
"proxy": { "useApifyProxy": false }
}

⬆️ Output

One dataset row per trend per time slice, typed and normalized, with the same key names on every run. Rows are pushed as each page is parsed, so the dataset fills while the run is still going and a crash part-way through never loses what has already landed. Export as JSON, CSV or Excel, or read the dataset through the Apify API. A run also writes RUN_SUMMARY.json to the run's key-value store, carrying the proxy tier used, the target market, includeTopPanels, per-slice row counts, and lists of failed URLs and unsupported requests. One naming quirk there: runStartedAt is stamped when the summary is written, which is at the end of the run, not the start.

Charging is per saved row: one row_result event per parsed trend row, board and panel alike. Accounting rows are pushed uncharged — a slice that failed still lands in your dataset as a diagnostic, but no event is charged for it. The marker is errorReason, and it is omitted from result rows rather than set to null, so filter clean rows with rec.get("errorReason") is None.

Deduplication happens at the URL level only, before fetching. Trend names are not deduplicated: the same trend legitimately appears once per time slice, and again as a panel row when includeTopPanels is on.

Example output

A board row from the live United Kingdom slice:

{
"time": "2026-07-25T09:14:02.481230Z",
"timePeriod": "Live",
"trend": "#Wimbledon",
"rank": 1,
"sourcePanel": "board",
"panelDetail": null,
"snapshotTimeUtc": "2026-07-25T09:00:00Z",
"country": "GB",
"countrySlug": "united-kingdom",
"countryName": "United Kingdom",
"trendPageUrl": "https://getdaytrends.com/trend/%23Wimbledon/",
"sourceUrl": "https://getdaytrends.com/united-kingdom/"
}

The same run's longest_trending panel row, produced because includeTopPanels was on:

{
"time": "2026-07-25T09:14:02.481230Z",
"timePeriod": "Live",
"trend": "Centre Court",
"rank": 3,
"sourcePanel": "longest_trending",
"panelDetail": "14 hours",
"snapshotTimeUtc": "2026-07-25T09:00:00Z",
"country": "GB",
"countrySlug": "united-kingdom",
"countryName": "United Kingdom",
"trendPageUrl": "https://getdaytrends.com/trend/Centre%20Court/",
"sourceUrl": "https://getdaytrends.com/united-kingdom/"
}

And an uncharged accounting row for a slice that could not be fetched — the only shape that carries errorReason:

{
"time": "2026-07-25T09:14:31.775410Z",
"timePeriod": "6 hours ago",
"trend": null,
"rank": null,
"sourcePanel": null,
"panelDetail": null,
"snapshotTimeUtc": null,
"country": "GB",
"countrySlug": "united-kingdom",
"countryName": "United Kingdom",
"trendPageUrl": null,
"sourceUrl": "https://getdaytrends.com/united-kingdom/6/",
"errorReason": "http_520_origin_down"
}

The errorReason values you can receive: http_520_origin_down, http_404_not_found, no_trend_table, no_rows_parsed, fetch_failed, http_<status> for any other status code, request_error:<ExceptionName> for a transport failure, and unsupported_time_slice_upstream_520 for one of the six retired date keys.

How does it work?

For each time slice you enable, the Actor builds one URL — /{country}/ for Live, /{country}/{hour}/ for a rewind — deduplicates the list, and issues a single plain HTTP GET per URL with a native Linux-container browser fingerprint. There is no headless browser, no JavaScript execution, no login and no cookie jar. Requests go out directly by default; a selected proxy is tested first and degrades to Apify residential and then to direct if it is unreachable.

A response is accepted only when it is a 200 that actually contains the expected result container, which catches soft blocks that arrive dressed as success. The HTML is then read for two things: the four ranking tables, separated into the real trend board and the two sidebar panels so board rank stays meaningful, and the inline currentDate JavaScript value that gives the board's true UTC hour. Per-trend links are rewritten from the country-scoped form the page emits to the worldwide form that actually resolves.

Only publicly visible data is collected, and the output key names stay the same whatever the source does to its layout.

Integrations

Twitter Trends Scraper is an Apify Actor, so it works with anything that can call the Apify API or consume a dataset.

The callable Actor id is your username plus the Actor's lowercase, hyphenated slug:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<YOUR_USERNAME>/twitter-trends-scraper").call(run_input={
"country": "GB",
"live": True,
"hour1": True,
"hour2": True,
"includeTopPanels": False,
})
for rec in client.dataset(run["defaultDatasetId"]).iterate_items():
if rec.get("errorReason") is None and rec["sourcePanel"] == "board":
print(rec["snapshotTimeUtc"], rec["rank"], rec["trend"], rec["countryName"])

Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request. Check errorReason first so an accounting row never reaches your reporting code.

No-code tools (n8n, Make, Zapier)

In n8n, use the Apify node — or an HTTP Request node pointed at the Apify run endpoint with your token — and pass the same JSON input shown above; an IF node on errorReason keeps accounting rows out of the branch that writes to your sheet, and a Filter node on sourcePanel keeps panel rows out of a ranked-board report. In Make, the Apify module supports run-and-wait, so an hourly trends pull can feed a Google Sheets, Airtable or Slack step directly. Both platforms handle scheduling, which is how you turn the Live slice into a rolling hourly archive without writing any code.

Scraping publicly accessible trend listings is broadly treated as permissible where no authentication is bypassed, and this Actor reads only a logged-out public page — no account, no cookie, no session.

The output is aggregate platform signals: a topic name, a rank, a market, an hour. There are no author handles, no user IDs, no post text and no engagement counts in any field this Actor writes, so GDPR and equivalent personal-data regimes do not attach to this output the way they would to a profile scraper. The one nuance worth naming: a trend name is free text, so trend can occasionally contain a person's name or an @-style handle simply because that is what was trending. That makes the individual value incidental personal data even though the record is not a profile — if you retain trend history in bulk, treat trend as free text that may contain names rather than as a guaranteed-anonymous label.

What applies primarily is contractual and database-related: the terms of service of the aggregator page being read, X's own terms and its rights in the underlying trend data, and database or unfair-competition rules covering systematic reuse of a compiled dataset.

Consult legal counsel if your use case involves republishing this data as a competing trends dataset, or bulk retention where trend names are matched back to individuals.

❓ Frequently asked questions

The five most used are trend, rank, snapshotTimeUtc, countryName and timePeriod. A result row carries 12 keys in total, and an uncharged accounting row carries those 12 plus errorReason. See the data fields table above for all of them.

No. No X account, no login, no cookie, no bearer token, no API key and no captcha-solver key. In practice no session token is needed either — the page is served to anonymous requests, which is why the default proxy setting is no proxy at all. The only credential involved is your Apify token.

Where does the trend data actually come from?

From the public HTML of getdaytrends.com, a third-party Twitter/X trends aggregator — not from x.com and not from the X API. This matters for two reasons. The available markets, hours and archives are whatever that source publishes, not whatever X exposes. And sourceUrl on every row tells you exactly which page a value came from, so any figure is traceable back to a page you can open yourself.

Why is there no tweet or post volume field?

Because it could not be populated honestly. The volume column measured empty at every source tested, and the fallback selector that used to fill it was writing the trend name into it on a share of live rows. The field was removed rather than shipped as a permanently-null or occasionally-wrong column. Competing Actors do advertise a volume field; one of their listings notes that X removed the post-count display from trends in January 2026.

You get an uncharged accounting row rather than silence. If the page cannot be fetched, the row carries the typed reason — http_404_not_found if the source does not serve that market or hour, http_520_origin_down if the source's own origin fails, no_trend_table if a soft block or wrong page survived every retry. If the page loads but yields zero parsable rows, the reason is no_rows_parsed and snapshotTimeUtc is still filled in from the page, which is a useful signal that you reached the right page and the table shape changed. 520 and 404 are not retried, since retrying cannot fix either. The run continues with the remaining slices, and the failure is also listed under stats.failedUrls in RUN_SUMMARY.json. Filter these rows out with rec.get("errorReason") is None.

Yes, combine freely — live and the 23 hourly toggles are independent, not mutually exclusive. Each enabled slice is one page fetch returning that market's board of up to 50 ranked rows, plus up to 20 more rows if includeTopPanels is on. Twenty-four slices are available per market (Live plus hour1 to hour23), and startUrls adds as many extra pages as you like on top, including pages for other markets. country itself is a single string, so a run has one dropdown market; cover more by listing their URLs in startUrls or by scheduling one run per market.

Yes to both. It is callable as a standard HTTP endpoint through the Apify API, so LangChain, CrewAI, n8n or a hand-written tool definition can invoke it and receive typed, normalized JSON with consistent field names across runs — no HTML parsing, no selectors, no cleanup pass. Pass a row straight into a context window, index it into a vector store, or hand it to an agent tool. Have the agent read errorReason first so a diagnostic row is handled as a failure rather than summarised as data.

Checked on the Apify Store on 25 July 2026: karamelo/twitter-trends-scraper is the most used and documents 35 markets including World, eight fixed time periods (Live, 1, 3, 6, 12 and 24 hours, 2 and 3 days) and an output of time, timePeriod, trend and volume; its listing carries an update noting X removed the post-count display around 13 January 2026, and states it relies on proxies. fastcrawler/x-twitter-trends-scraper-2025 documents a single country string input and an output of rank, topic, tweet_volume and last_updated, with tweet_volume shown as strings such as "Under 10k". fastcrawler/twitter-x-trends-scraper-api-real-time-trend-tracker documents those same four output fields, with country taken as a country name, rank as a string, and tweet_volume returning 0 when the value is hidden.

Observable differences in this Actor: rank is a typed integer rather than a string; 62 markets plus worldwide; 23 individually selectable hourly rewinds instead of a fixed period list; the board's real UTC snapshot hour as its own field; board rows separated from the two sidebar panels via sourcePanel; a canonical per-trend URL; and uncharged accounting rows for failed slices. A UTC snapshot field, panel separation and a per-trend URL are not documented on any of the three listings above. This Actor has no volume field at all, which is a real trade-off against all three. These are field-list and input-flexibility differences you can verify yourself — not performance claims.

What happens when the source page changes its layout?

The scraper is maintained, and your field names and types stay put regardless. Layout change is the real exposure here, since the data comes from rendered HTML — so parsing is anchored on the ranking tables' own classes and on the inline date value rather than on brittle positional selectors, and a section that moves degrades to a null field or an uncharged no_rows_parsed row rather than corrupting a good one. The soft-block guard is part of the same design: a 200 without the expected container is treated as a failure, not as an empty result.

Yes, and that is both the default and the recommended setting — the source serves this Actor on a direct connection, so proxy prefills to no proxy and most runs never need one. If you do select one, validation and fallback are automatic: one test request, then Apify RESIDENTIAL, then direct, with the tier used logged and written to RUN_SUMMARY.json. You never create a proxy account or rotate an IP yourself.

Which Twitter trend fields work best for AI training data and RAG indexing?

For RAG indexing: trend is the high-information text field, and it chunks best paired with countryName and snapshotTimeUtc so a retrieved chunk carries its own market and hour instead of floating free. panelDetail is worth indexing alongside them when panels are on, since it is the only field carrying the source's own qualifier for a trend. For training or modelling: rank, country, timePeriod and snapshotTimeUtc are the structurally consistent fields across every record, and sourcePanel is the flag to filter on first so panel rows never get treated as board ranks. All values return as typed primitives — integers, strings or null — so no normalization pass is needed before indexing.

Scraper NameWhat it extracts
X Twitter Posts SearchSearch results on X for a query — the posts behind a trend
Twitter X Posts ScraperPublic posts from X/Twitter
Twitter X Reply ScraperReplies under a given X post
Reddit Trends Scraper by Sort, Time and Score FilterTrending Reddit posts with sort, time-window and score filters
TikTok Trending Hashtags Scraper by Multiple CountriesTrending TikTok hashtags across several countries
Google News Scraper by Publisher Site FilterNews coverage filtered to a specific publisher or domain

💬 Your feedback

Found a bug, or need a market or time slice that is not covered? Open an issue on the Actor's Issues tab. Reports that include the exact input JSON and the sourceUrl from the row that looked wrong are the fastest to reproduce and fix — and if a slice turns out to be one the source no longer serves, the issue is the quickest way to get that confirmed in writing.