Twitter X Tweets Scraper By Keyword & Hashtag
Pricing
from $2.99 / 1,000 results
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
Maintained by CommunityActor stats
0
Bookmarked
7
Total users
1
Monthly active users
14 days ago
Last modified
Categories
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 want | X session cookies needed? |
|---|---|
| Keyword / hashtag / phrase search (all four tabs) | Yes — auth_token + ct0 |
| A profile's posts, reposts and quotes | No |
| A single post URL | No |
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
- 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. - Four search tabs, not two —
Latest,Top,Media, andPeople. 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. - 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. resolved_queryon every row — the literal query string that was sent to X. Nothing about the compilation is hidden.- Profiles and single post URLs, keyless — the base actor's behaviour, unchanged.
Inputs
Search
| Input | Type | Notes |
|---|---|---|
searchTerms | array of strings | One 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). |
searchProduct | Latest | Top | Media | People | Which X result tab. People returns user rows and no post rows. Default Latest. |
includeSearchTerms | boolean | Writes matched_search_term on each search row. Default true. |
Search filters (all compiled to X operators)
| Input | Compiles to | Applied |
|---|---|---|
fromHandle | from:<handle> | X server-side |
toHandle | to:<handle> | X server-side |
conversationId | conversation_id:<id> | X server-side |
quotedPostId | quoted_tweet_id:<id> | X server-side |
postedAfter | since_time:<utc epoch> | X server-side, exact to the second + UTC re-check locally |
postedBefore | until_time:<utc epoch> | X server-side, exact to the second + UTC re-check locally |
minLikes | min_faves:<n> | X server-side + local re-check |
minRetweets | min_retweets:<n> | X server-side + local re-check |
minReplies | min_replies:<n> | X server-side + local re-check |
searchLanguage | lang:<code> | X server-side + local re-check |
mediaFilter | filter:media / -filter:media | X server-side + local re-check |
onlyWithLinks | filter:links | X server-side only |
postedAfter / postedBefore accept an absolute date (2026-07-01) or a relative one (7 days).
Base inputs (unchanged, still functional)
| Input | Type | Notes |
|---|---|---|
startUrls | array of strings | Profile URLs, @handles, numeric user IDs, or single post URLs. Keyless. Optional in this actor — you can run on keywords alone. |
maxTweets | integer 1–1000 | Rows per profile and per search term. Default 10. |
sortOrder | recent | oldest | popular | Re-orders the rows collected in this run. |
includeReplies | boolean | A profile's own replies. Needs the session. |
auth_token, ct0 | secret strings | The X session. Stored encrypted; never written to the dataset or the log. |
proxyConfiguration | proxy object | Off 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:
| Field | Meaning |
|---|---|
source_type | search, profile or post — how this row was found |
matched_search_term | the search term that found it (null on profile/post rows) |
resolved_query | the exact query string sent to X |
search_product | which tab produced it |
result_rank | 1-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.
Latestreaches back a limited distance regardless ofmaxTweets. Absence from these results is not evidence that nobody posted. LatestandTopare not comparable and must not be mixed in one analysis.search_productships on every row so a merged export stays honest.result_rankis 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-DDare applied on the SESSION's local day boundary (measured ~UTC+6) anduntil: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-02returned rows from2026-06-30T18:30Zto2026-07-02T17:59Z. This actor compiles tosince_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 inresults_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
maxTweetsacross several terms can exhaust it; when that happens the term ends with arate_limitedsummary 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 themaxTweetsceiling of 1000, so it can never cut a run short before your own limit does. Mediaover-delivers. A 20-row request returned 48 posts. The row cap counts every row, so you are never charged abovemaxTweets.from:is not re-checked locally. A repost row reports the ORIGINAL post's author and counts (the base contract), so a localfrom:check would delete real matches. Same forto:,conversation_id:,quoted_tweet_id:andfilter:links.- No per-post geo columns.
legacy.place,geoandcoordinatesare null on effectively every post, so no location column is shipped for post rows rather than an always-empty one. (locationon auserrow 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
Peopletab is the exception: it does return protected ACCOUNTS, and they are flagged honestly —is_protectedwastrueon 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 firematches 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
Listssearch tab is not offered — it returned 0 posts and 0 accounts under both parsers, so there is nothing to ship. results_dropped_by_filternormally 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 besideresults_returnedandresults_kepton 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 belowminRetweetsafter 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.