# Twitter Trends Scraper (`scraper-engine/twitter-trends-scraper`) Actor

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.

- **URL**: https://apify.com/scraper-engine/twitter-trends-scraper.md
- **Developed by:** [Scraper Engine](https://apify.com/scraper-engine) (community)
- **Categories:** Social media, Lead generation, Automation
- **Stats:** 32 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

$19.99/month + usage

To use this Actor, you pay a monthly rental fee to the developer. The rent is subtracted from your prepaid usage every month after the free trial period.You also pay for the Apify platform usage, which gets cheaper the higher Apify subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#rental-actors

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### Twitter Trends Scraper — Rank 1-50, Hourly Snapshots by Country

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.

### What is Twitter Trends Scraper?

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 Category | On the public trends page | Not 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 trend | ❌ | Not published — X removed the count display, and the field measured empty at every source tested |
| Date archives: yesterday, last week, last month, last year | ❌ | The source answers every archive URL with HTTP 520 |
| The posts, authors and engagement behind a trend | ❌ | Not on this page at all — no handles, no post text, no counts |
| City-level targets, WOEIDs, multi-country runs | ❌ | One 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.

### What data can I extract with Twitter Trends Scraper?

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 Name | Description |
| ----- | ----- |
| `trend` | The 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 |
| `rank` | Integer 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 |
| `sourcePanel` | Which 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 |
| `panelDetail` | The panel's own figure, as text — for example `564 score` or `14 hours`. `null` on every `board` row |
| `timePeriod` | The slice this row belongs to: `Live`, `1 hour ago`, `2 hours ago` … `23 hours ago`, or `Custom` for a row from `startUrls` |
| `snapshotTimeUtc` | The 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 |
| `time` | When this row was scraped, ISO 8601 UTC with microseconds. Identical for every row parsed from the same page |
| `country` | ISO alpha-2 code for the market, e.g. `GB`. `null` on a worldwide run |
| `countrySlug` | The 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 |
| `countryName` | Human-readable market name, e.g. `United Kingdom`, or `Worldwide` when no market applies |
| `trendPageUrl` | Canonical 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 |
| `sourceUrl` | The 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 |
| `errorReason` | **Present 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.

### How to use Twitter Trends Scraper

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.

### How does Twitter Trends Scraper handle rate limits and blocking?

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.

| Parameter | Required | Type | Description | Example Value |
| ----- | ----- | ----- | ----- | ----- |
| `country` | No | string | Market 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"` |
| `live` | No | boolean | Scrape 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` |
| `includeTopPanels` | No | boolean | Adds 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` |
| `hour1` … `hour23` | No | boolean | **23 separate parameters**, one per hour of rewind: `hour1`, `hour2`, `hour3` … `hour23`. 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` |
| `startUrls` | No | array | Extra 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/"}]` |
| `maxRetries` | No | integer | Extra 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` |
| `requestTimeoutSecs` | No | integer | Per-request HTTP timeout in seconds. Also used for the proxy validation request. Minimum `5`, maximum `300`, default `30`. | `30` |
| `proxy` | No | object | Proxy 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`.

```json
{
  "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:

```json
{
  "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:

```json
{
  "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`:

```json
{
  "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.

#### Calling Twitter Trends Scraper from Python

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

```python
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.

### Is it legal to scrape Twitter trending topics?

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

#### What Twitter trend fields does Twitter Trends Scraper return?

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.

#### Does Twitter Trends Scraper require a Twitter/X account, login or API key?

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.

#### What happens if a market has no trends, or the hour I asked for has no page?

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`.

#### How many trends can I get in one run, and can I combine time slices?

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.

#### Does Twitter Trends Scraper work with Claude, ChatGPT and other AI agent tools, and is the output LLM-ready?

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.

#### How does Twitter Trends Scraper compare to other Twitter trends scrapers?

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.

#### Can I use Twitter Trends Scraper without managing proxies?

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.

### 🔗 Related scrapers

| Scraper Name | What it extracts |
| ----- | ----- |
| X Twitter Posts Search | Search results on X for a query — the posts behind a trend |
| Twitter X Posts Scraper | Public posts from X/Twitter |
| Twitter X Reply Scraper | Replies under a given X post |
| Reddit Trends Scraper by Sort, Time and Score Filter | Trending Reddit posts with sort, time-window and score filters |
| TikTok Trending Hashtags Scraper by Multiple Countries | Trending TikTok hashtags across several countries |
| Google News Scraper by Publisher Site Filter | News 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.

# Actor input Schema

## `country` (type: `string`):

Market to pull the Twitter/X trend board for. Leave empty for worldwide trends. All 62 codes below are verified live on the source; this actor scrapes ONE market per run.

## `live` (type: `boolean`):

Scrape the trend board as it stands right now (ranks 1-50). If no time slice at all is selected, this one is used.

## `includeTopPanels` (type: `boolean`):

The page carries two extra sidebar panels next to the 50-trend board: 'top by score' and 'longest trending' (10 rows each). Their trends repeat board trends with different meaning, so they are OFF by default. Turn on to add 20 labelled rows (sourcePanel = top\_by\_score / longest\_trending) carrying panelDetail such as '564 score' or '14 hours'.

## `hour1` (type: `boolean`):

Scrape the trend board as it stood 1 hour ago.

## `hour2` (type: `boolean`):

Scrape the trend board as it stood 2 hours ago.

## `hour3` (type: `boolean`):

Scrape the trend board as it stood 3 hours ago.

## `hour4` (type: `boolean`):

Scrape the trend board as it stood 4 hours ago.

## `hour5` (type: `boolean`):

Scrape the trend board as it stood 5 hours ago.

## `hour6` (type: `boolean`):

Scrape the trend board as it stood 6 hours ago.

## `hour7` (type: `boolean`):

Scrape the trend board as it stood 7 hours ago.

## `hour8` (type: `boolean`):

Scrape the trend board as it stood 8 hours ago.

## `hour9` (type: `boolean`):

Scrape the trend board as it stood 9 hours ago.

## `hour10` (type: `boolean`):

Scrape the trend board as it stood 10 hours ago.

## `hour11` (type: `boolean`):

Scrape the trend board as it stood 11 hours ago.

## `hour12` (type: `boolean`):

Scrape the trend board as it stood 12 hours ago.

## `hour13` (type: `boolean`):

Scrape the trend board as it stood 13 hours ago.

## `hour14` (type: `boolean`):

Scrape the trend board as it stood 14 hours ago.

## `hour15` (type: `boolean`):

Scrape the trend board as it stood 15 hours ago.

## `hour16` (type: `boolean`):

Scrape the trend board as it stood 16 hours ago.

## `hour17` (type: `boolean`):

Scrape the trend board as it stood 17 hours ago.

## `hour18` (type: `boolean`):

Scrape the trend board as it stood 18 hours ago.

## `hour19` (type: `boolean`):

Scrape the trend board as it stood 19 hours ago.

## `hour20` (type: `boolean`):

Scrape the trend board as it stood 20 hours ago.

## `hour21` (type: `boolean`):

Scrape the trend board as it stood 21 hours ago.

## `hour22` (type: `boolean`):

Scrape the trend board as it stood 22 hours ago.

## `hour23` (type: `boolean`):

Scrape the trend board as it stood 23 hours ago.

## `startUrls` (type: `array`):

Optional. Scrape additional getdaytrends country or hourly pages directly, on top of the toggles above. Rows from these are labelled timePeriod = 'Custom'. Example: https://getdaytrends.com/japan/5/

## `maxRetries` (type: `integer`):

How many times to retry a page that fails or comes back without a trend table, with exponential backoff. Default is 3.

## `requestTimeoutSecs` (type: `integer`):

Per-request HTTP timeout. Default is 30.

## `proxy` (type: `object`):

Defaults to no proxy, which is what the source expects. If a selected proxy is unreachable the actor falls back to Apify RESIDENTIAL, then to a direct connection.

## Actor input object example

```json
{
  "country": "",
  "live": true,
  "includeTopPanels": false,
  "startUrls": [],
  "maxRetries": 3,
  "requestTimeoutSecs": 30,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "proxy": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scraper-engine/twitter-trends-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "proxy": { "useApifyProxy": False } }

# Run the Actor and wait for it to finish
run = client.actor("scraper-engine/twitter-trends-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "proxy": {
    "useApifyProxy": false
  }
}' |
apify call scraper-engine/twitter-trends-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=scraper-engine/twitter-trends-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/f1SRel8MQjkyZoIlN/builds/pCHgoIYrtlo2Qs25X/openapi.json
