Twitter X Tweets Scraper By Keyword & Hashtag avatar

Twitter X Tweets Scraper By Keyword & Hashtag

Pricing

from $2.99 / 1,000 results

Go to Apify Store
Twitter X Tweets Scraper By Keyword & Hashtag

Twitter X Tweets Scraper By Keyword & Hashtag

Scrape X Twitter tweets reliably for structured data. Capture tweet content, engagement metrics, author profiles, and posting times. Filter by search terms, usernames, or date ranges. Useful for market research, social listening, and audience analysis.

Pricing

from $2.99 / 1,000 results

Rating

0.0

(0)

Developer

SimpleAPI

SimpleAPI

Maintained by Community

Actor stats

0

Bookmarked

7

Total users

1

Monthly active users

14 days ago

Last modified

Share

Twitter X Tweets Scraper Scraper By Keyword & Hashtag

Scrape X (Twitter) posts by keyword, hashtag or phrase from anyone on X — not only from accounts you already know. Type what you want to track, and this X scraper compiles your filters into X's own server-side search grammar, runs the search, and exports one row per post with the full text, engagement counts, media, author profile and the exact query that found it.

It is a strict superset of the base Twitter X Tweets Scraper Scraper: every input the base accepts still works, every column the base produces is still produced by the same code, and a run with no keywords behaves exactly like the base.

Read this first — keyword search needs an X session. X answers its search endpoint (SearchTimeline) with HTTP 404 and an empty body to logged-out callers, on all four result tabs. So:

What you wantX session cookies needed?
Keyword / hashtag / phrase search (all four tabs)Yes — auth_token + ct0
A profile's posts, reposts and quotesNo
A single post URLNo
A profile's own replies (includeReplies)Yes — auth_token + ct0

If you fill the search box and leave the cookies empty, the run fails before spending a single request. It never returns an empty green table, because for topic monitoring "no results" and "we could not ask" look identical and that is the most dangerous wrong answer this actor could give.


What it does

  1. Keyword and hashtag search across all of X — plain words, #hashtags, "quoted phrases", OR, -exclusions. Each line is searched separately; results land in the same table as profile results, with the same columns.
  2. Four search tabs, not twoLatest, Top, Media, and People. People returns accounts, not posts: type: "user" rows with handle, display name, bio, website, follower/following/post counts, self-reported location, account creation date, blue-verified status and verified type.
  3. Typed filters compiled into X's search grammar — a date window, likes / reposts / replies floors, language, media, links, from:, to:, thread ID and quoted-post ID. X applies these before it sends the data, so you are not paying for rows that get thrown away locally.
  4. resolved_query on every row — the literal query string that was sent to X. Nothing about the compilation is hidden.
  5. Profiles and single post URLs, keyless — the base actor's behaviour, unchanged.

Inputs

InputTypeNotes
searchTermsarray of stringsOne search per line. X operators pass through as typed. Empty and no filter set = behaves like the base actor. Empty with a filter set = one search on the compiled operators alone (e.g. every post quoting a given post).
searchProductLatest | Top | Media | PeopleWhich X result tab. People returns user rows and no post rows. Default Latest.
includeSearchTermsbooleanWrites matched_search_term on each search row. Default true.

Search filters (all compiled to X operators)

InputCompiles toApplied
fromHandlefrom:<handle>X server-side
toHandleto:<handle>X server-side
conversationIdconversation_id:<id>X server-side
quotedPostIdquoted_tweet_id:<id>X server-side
postedAftersince_time:<utc epoch>X server-side, exact to the second + UTC re-check locally
postedBeforeuntil_time:<utc epoch>X server-side, exact to the second + UTC re-check locally
minLikesmin_faves:<n>X server-side + local re-check
minRetweetsmin_retweets:<n>X server-side + local re-check
minRepliesmin_replies:<n>X server-side + local re-check
searchLanguagelang:<code>X server-side + local re-check
mediaFilterfilter:media / -filter:mediaX server-side + local re-check
onlyWithLinksfilter:linksX server-side only

postedAfter / postedBefore accept an absolute date (2026-07-01) or a relative one (7 days).

Base inputs (unchanged, still functional)

InputTypeNotes
startUrlsarray of stringsProfile URLs, @handles, numeric user IDs, or single post URLs. Keyless. Optional in this actor — you can run on keywords alone.
maxTweetsinteger 1–1000Rows per profile and per search term. Default 10.
sortOrderrecent | oldest | popularRe-orders the rows collected in this run.
includeRepliesbooleanA profile's own replies. Needs the session.
auth_token, ct0secret stringsThe X session. Stored encrypted; never written to the dataset or the log.
proxyConfigurationproxy objectOff by default.

Output

Post rows (type: "tweet")

Every column the base actor produces, built by the same code path, so a search result and a timeline post are directly comparable:

type, id, url, user_posted, name, description, date_posted, likes, replies, reposts, quotes, bookmarks, views, lang, conversation_id, is_repost, reposted_by, is_quote, is_reply, in_reply_to_user, in_reply_to_post_id, is_pinned, is_verified, is_blue_verified, verified_type, followers, following, posts_count, profile_image_link, biography, external_url, hashtags, tagged_users, photos, videos, media, quoted_post, poll, link_preview, input

Plus, added by this actor:

FieldMeaning
source_typesearch, profile or post — how this row was found
matched_search_termthe search term that found it (null on profile/post rows)
resolved_querythe exact query string sent to X
search_productwhich tab produced it
result_rank1-based position X returned it at, recorded before filtering and sorting

is_pinned is null on search rows. A search response carries no pinned-post entry, so "not pinned" would be an assertion about the author's profile that the search request never asked about. It is true/false only on rows collected from a profile timeline, which is the only place X publishes it. Because search is this actor's headline mode, is_pinned therefore lives in the 🔁 Reposts & quotes view rather than in the default table, where it would be a permanently empty column on any search-only run.

Account rows (type: "user", searchProduct: People only)

id, url, user_posted, name, biography, external_url, followers, following, posts_count, profile_image_link, is_verified, is_blue_verified, verified_type, location, account_created_at, is_protected, plus the five provenance fields above. Shown in the 👥 People results view.

Switch to that view when you run searchProduct: People. An account row has no post text, no timestamp and no engagement counts, so roughly half the default table is blank on a People-only run — the default table is built for post rows, which every other search product returns, and trimming it to the columns both row types share would leave it near-useless for the common case.

Search-term rows (type: "search_summary", uncharged)

One per search term: target (as typed), resolved_query, search_product, results_returned, results_kept, results_dropped_by_filter, pages_fetched, errorReason, message. Shown in the 🧾 Search terms view. A term that found nothing is visible here instead of silently absent.

Skipped-target rows (type: "error", uncharged)

One per profile/post target that produced nothing, with a typed errorReason and a plain-language message.

Only tweet and user rows are charged.

Limits — measured, not assumed

  • Keyword search requires the X session. Not optional, not degradable. See the table at the top.
  • X search is not an archive. The public index is partial, recency-weighted and undisclosed. Latest reaches back a limited distance regardless of maxTweets. Absence from these results is not evidence that nobody posted.
  • Latest and Top are not comparable and must not be mixed in one analysis. search_product ships on every row so a merged export stays honest.
  • result_rank is a fact about one search, not a property of the post. Two runs of the same term minutes apart return different sets and different ordering.
  • The date window uses X's epoch operators, not the day operators. since:YYYY-MM-DD / until:YYYY-MM-DD are applied on the SESSION's local day boundary (measured ~UTC+6) and until: is inclusive of the named day, so a UTC window built from them overshoots at both ends: nasa since:2026-07-01 until:2026-07-02 returned rows from 2026-06-30T18:30Z to 2026-07-02T17:59Z. This actor compiles to since_time: / until_time: (UTC epoch seconds), which were exact on the same window, and still re-checks every row in UTC before saving it. Anything removed is counted in results_dropped_by_filter.
  • The search rate-limit bucket is shared across all four tabs — 187 requests per 15-minute window, on the supplied session. Since X serves roughly 20 rows per request, a single run that asks for a large maxTweets across several terms can exhaust it; when that happens the term ends with a rate_limited summary row and a log line, never with a quietly short result set. If five consecutive pages are wiped out by your own filters, the term is abandoned with a log line rather than spending the rest of that quota. The internal page guard is 60 requests per term (1200 rows), which is above the maxTweets ceiling of 1000, so it can never cut a run short before your own limit does.
  • Media over-delivers. A 20-row request returned 48 posts. The row cap counts every row, so you are never charged above maxTweets.
  • from: is not re-checked locally. A repost row reports the ORIGINAL post's author and counts (the base contract), so a local from: check would delete real matches. Same for to:, conversation_id:, quoted_tweet_id: and filter:links.
  • No per-post geo columns. legacy.place, geo and coordinates are null on effectively every post, so no location column is shipped for post rows rather than an always-empty one. (location on a user row is the account's self-reported profile string, filled on 58 of 80 accounts measured live.)
  • A protected account's POSTS never appear in any post result, and blocked or restricted accounts may not either. The People tab is the exception: it does return protected ACCOUNTS, and they are flagged honestly — is_protected was true on 6 of 80 user rows measured live, so it is a real per-account value and not a constant.
  • A keyword matches text, not meaning. battery fire matches a complaint, a news report, a joke and someone quoting a competitor. Nothing here classifies intent, sentiment or relevance.
  • Everything is one snapshot. Counts are read once, at fetch time, and reported as read. Nothing is stored or compared against a previous run.
  • The Lists search tab is not offered — it returned 0 posts and 0 accounts under both parsers, so there is nothing to ship.
  • results_dropped_by_filter normally reads 0, and that is the correct answer. It counts what the LOCAL re-check removed after X had already applied the same constraint server-side. Because the compiled operators are exact, X usually leaves nothing for the local net to catch, so 0 means "X's own filtering held for this query" — it sits beside results_returned and results_kept on the same summary row, both of which are non-zero, so the row is never a blank column. It is a live counter, not a constant: it was measured firing at 800 dropped / 0 kept while the date window was still compiled to X's inexact day operators, and it will fire again whenever X's index lags a count it published (a post whose repost count fell below minRetweets after indexing).
  • Pressing Start with the stock settings runs the keyless profile scrape. The keyword box is deliberately empty by default, because a stock run with a keyword and no session cookies would (correctly) fail.
  • A zero-row run fails loudly with a terminal status message rather than finishing green and empty.

Compliance

Collects only data X serves publicly to the supplied session. You are responsible for how you use it, including X's terms and any applicable data-protection law. The session cookies you supply are your own; they are stored encrypted by Apify and sent only to api.x.com.