Twitter X Tweets Scraper Scraper By Keyword & Hashtag
Pricing
from $2.99 / 1,000 results
Twitter X Tweets Scraper 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
4
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
X (Twitter) Scraper — Posts by Keyword, Hashtag and Author Data
Find every public X (Twitter) post matching a keyword, hashtag or exact phrase — not just posts from accounts you already track. This X scraper compiles typed filters (date window, minimum likes/reposts/replies, language, media, from:/to:, thread ID, quoted-post ID) into X's own search grammar and writes the exact compiled query onto every row as resolved_query, so nothing about the match is a black box. Each row also carries the post's full text, engagement counts, media, and the author's profile data — handle, name, followers, bio, verification status. It is a strict superset of the base Twitter X Tweets Scraper Scraper: profile timelines and single post URLs still work keylessly, with no keyword required. Built for social listening teams, PR and marketing agencies, researchers, and AI agents that need fresh X data without a developer-approved API key.
🐦 What is the X (Twitter) Scraper By Keyword & Hashtag?
It is an Apify Actor that searches X for posts matching a keyword or hashtag, or pulls posts from specific profiles and post URLs, and returns one normalized JSON row per post (or per account, on the People tab). Profile timelines and single post URLs run keyless, using a guest token — no X account of your own is needed. Keyword and hashtag search, and pulling a profile's own replies, require an X session (two browser cookies from a logged-in x.com session), because X itself returns an empty result to logged-out search requests.
Key capabilities, read from the source:
- Two discovery paths in one run — free-text keyword/hashtag search across all of X, and/or specific targets in
startUrls(a profile handle, a numeric user ID, or a single post URL). Mix both in one run; every row carriessource_type(search,profile, orpost) to tell them apart. - Four X search tabs —
Latest(reverse-chronological),Top(X's relevance ranking),Media(photo/video posts only), andPeople(returns accounts, not posts). - Typed filters compiled server-side — date window, engagement floors, language, media presence, links,
from:/to:handle, thread ID, quoted-post ID — all applied by X before the response is sent, and echoed back asresolved_query. - Full post and author payload — text, likes/replies/reposts/quotes/bookmarks/views, hashtags, mentions, photos, videos, polls, link-preview cards, and the author's followers/following/bio/verification status on every row.
- Repost and reply handling done honestly — a repost row reports the original post's real counts and author (not the retweet wrapper's structural zeros), with the reposting account named separately in
reposted_by. - Standard Apify dataset export — every run's results can be downloaded as JSON, CSV, Excel, HTML table, or RSS directly from the Apify Console or API, no extra conversion step.
📊 What data can I extract with the X (Twitter) Scraper?
Every row is a typed JSON object covering the post text, engagement, media, thread position, and the author's profile — the fields below are the actual keys build_tweet_row() writes in src/base_row.py, not a subset.
| Field | Example value | Use case |
|---|---|---|
id | "2083317461269598348" | Deduplicating posts across runs |
url | "https://x.com/NASA/status/2083317461269598348" | Linking back to the live post |
user_posted | "NASA" | Identifying the author's handle |
name | "NASA" | Author's display name |
description | "Artemis II is go for launch..." | Full post text (untruncated long-form when available) |
date_posted | "2026-07-30T14:02:11.000Z" | Sorting and date-window analysis (ISO 8601 UTC) |
likes / replies / reposts / quotes / bookmarks | 18420 / 612 / 4310 / 208 / 950 | Ranking posts by engagement |
views | 1382044 | Reach signal, where X publishes it |
lang | "en" | Filtering a multilingual export by language |
conversation_id | "2083317461269598348" | Grouping a thread |
is_repost / reposted_by | false / null | Separating original posts from reposts |
is_quote / quoted_post | false / null | Following a quote-post chain |
is_reply / in_reply_to_user | false / null | Isolating replies from top-level posts |
followers / following / posts_count | 55200000 / 297 / 68541 | Sizing the author's audience |
is_blue_verified / verified_type | true / "Business" | Segmenting by verification status |
hashtags / tagged_users | ["Artemis"] / null | Topic and mention analysis |
photos / videos / media | null / [...] / [{...}] | Pulling attached image/video URLs |
resolved_query / matched_search_term / search_product / result_rank | see below | Auditing exactly what was searched and how each row ranked |
📈 Engagement, media and thread data
Every post row carries the raw counts X published at request time — likes, replies, reposts, quotes, bookmarks, views — plus structured poll (choices, live vote counts, counts_are_final) and link_preview (title, description, domain, URL) objects when the post carries a card. Media ships as full lists, not a first-item string: photos (all image URLs), videos (MP4 URLs sorted by bitrate), and media (per-item detail: type, dimensions, alt text, duration). A PR team tracking a product-recall hashtag can filter on likes and views to surface which specific post is actually driving reach, then pull its photos/videos for review — without opening X.
🎯 Targeting and segmentation data
The fields you filter and segment on are the search-compiler inputs and their echo: searchLanguage → lang, mediaFilter → the photo/video lists, minLikes/minRetweets/minReplies → the engagement columns, and the date window (postedAfter/postedBefore) → date_posted. On the author side, followers, is_blue_verified, and verified_type let you segment matched posts by audience size or verification tier after the fact — there is no direct minimum-follower or verification input filter, only post-level content and engagement filters.
⚖️ How does this differ from the official X API?
X's own recent-search endpoint is capped at the last 7 days of posts; reaching further back requires full-archive search, which is gated behind Enterprise access rather than self-serve signup (docs.x.com, checked 2026-08-04). X's current API billing is pay-per-usage: $0.005 per post read, $0.010 per user read, capped at 2,000,000 post reads per monthly billing cycle before an Enterprise plan is required (docs.x.com, checked 2026-08-04).
| Feature | X API v2 (official) | This actor |
|---|---|---|
| Access requirement | Developer account, app registration, billing setup | Apify account; two browser cookies for search |
| Pricing model | Pay-per-usage: $0.005/post read, $0.010/user read | Apify pay-per-event (row_result), no separate per-read fee to X |
| Recent-search lookback | 7 days on the standard search endpoint | Uses X's own consumer search index — not a fixed archive, see below |
| Full-archive search | Enterprise access only | Not offered — see below |
| Query construction | Manual query-string operators per endpoint docs | Typed inputs compiled automatically, echoed back as resolved_query |
| Output shape | Raw GraphQL/REST JSON, varies per endpoint | One normalized row schema across search, profile and post lookups |
| Setup time | OAuth app + key/secret management | Two cookies copied from a browser session |
❓ How far back does X keyword search actually reach?
The official recent-search endpoint stops at 7 days; full-archive search needs Enterprise approval (docs.x.com, checked 2026-08-04). This actor does not bypass that limit — it queries the same consumer SearchTimeline backend X's own website uses for the search box, which the actor's own code documents as a partial, recency-weighted index whose depth X does not disclose. In practice that means a term returning zero rows is not proof nobody posted about it; it means the consumer index did not surface a match. Pagination is capped at 40 pages per search term (roughly 800 raw results scanned before duplicates and filters), and a term is abandoned early if five consecutive pages are entirely removed by your own filters, to protect the shared rate-limit quota.
Use the official X API when you need guaranteed full-archive coverage or already hold Enterprise access. Use this actor when you want a fast keyword/hashtag pull with typed filters and no developer approval process.
🎯 How to use data extracted from X (Twitter)
📣 Social listening and PR teams
Set searchTerms to a brand name or crisis hashtag, minLikes to filter noise, and postedAfter to a relative window ("1 day") to catch what is unfolding right now. The description, likes, views, and resolved_query fields tell you which specific post is driving the conversation and prove exactly what was searched, ready to paste into a Sheet or Slack alert.
🏢 Marketing and social-media agencies
Run the same searchTerms and mediaFilter combination on a schedule across multiple client hashtags, tagging each run's output by matched_search_term. Comparing results_returned and results_kept from the uncharged search_summary row across weeks gives an agency a defensible, timestamped record of campaign reach for client reporting.
🔬 Market research and intelligence
Sweep a topic with searchLanguage set per market and no engagement floor, then group rows by lang and date_posted to chart volume over time and by region. result_rank records X's own ranking position before any local sorting, so a researcher can distinguish "posted a lot" from "X surfaced it prominently."
🤖 AI agents and automated pipelines
An agent framework can call this actor as a tool to pull the latest posts on a topic into context before answering, using id to dedupe against prior calls and resolved_query to log exactly what was asked. Because search and profile rows share one schema, a pipeline can treat "search this hashtag" and "watch this account" as the same downstream shape.
🔽 Input sample
Every field below is required to match .actor/actor.json exactly — names, types, and defaults are copied, not paraphrased.
| Parameter | Required | Type | Default / Constraints | Description |
|---|---|---|---|---|
searchTerms | No | array (stringList) | empty by default | One search per line: plain words, #hashtag, "exact phrase", or X operators (OR, -exclusion, from:). Needs the X session cookies below. |
searchProduct | No | string (select) | "Latest"; enum Latest, Top, Media, People | Which X result tab to search. |
includeSearchTerms | No | boolean | true | Writes matched_search_term on every search row. |
startUrls | No | array (stringList) | prefill ["NASA"] | Profile URL/handle/numeric user ID, or a single post URL. Optional — keyword search alone is enough. |
fromHandle | No | string (textfield) | — | Compiles to from:<handle>. |
toHandle | No | string (textfield) | — | Compiles to to:<handle>. |
conversationId | No | string (textfield) | — | Compiles to conversation_id:<id>. |
quotedPostId | No | string (textfield) | — | Compiles to quoted_tweet_id:<id>. |
postedAfter | No | string (datepicker, absoluteOrRelative) | — | Compiles to since_time:<utc epoch seconds>. Absolute (2026-07-01) or relative (7 days). |
postedBefore | No | string (datepicker, absoluteOrRelative) | — | Compiles to until_time:<utc epoch seconds>, exclusive. |
minLikes | No | integer | 0, minimum 0 | Compiles to min_faves:<n>. |
minRetweets | No | integer | 0, minimum 0 | Compiles to min_retweets:<n>. |
minReplies | No | integer | 0, minimum 0 | Compiles to min_replies:<n>. |
searchLanguage | No | string (select) | "any"; enum any, en, es, pt, fr, de, it, nl, pl, sv, tr, ru, uk, ar, fa, hi, id, th, vi, ja, ko, zh | Compiles to lang:<code>. |
mediaFilter | No | string (select) | "any"; enum any, with_media, without_media | Compiles to filter:media or -filter:media. |
onlyWithLinks | No | boolean | false | Compiles to filter:links. |
maxTweets | No | integer | 10, minimum 1, maximum 1000 | Rows to collect from each profile and from each search term. |
sortOrder | No | string (select) | "recent"; enum recent, oldest, popular | Re-orders rows collected in this run only. |
includeReplies | No | boolean | false | Includes a profile's own replies; requires the X session. |
auth_token | No (functionally required for search) | string (textfield, secret) | — | auth_token cookie from an x.com browser session. |
ct0 | No (functionally required for search) | string (textfield, secret) | — | ct0 cookie (CSRF token) from the same session. |
proxyConfiguration | No | object (proxy editor) | {"useApifyProxy": false} | Apify Proxy configuration. |
{"searchTerms": ["#Artemis", "NASA rocket launch"],"searchProduct": "Latest","includeSearchTerms": true,"startUrls": [],"postedAfter": "7 days","minLikes": 50,"searchLanguage": "en","mediaFilter": "with_media","maxTweets": 100,"sortOrder": "recent","auth_token": "<your auth_token cookie>","ct0": "<your ct0 cookie>","proxyConfiguration": { "useApifyProxy": true }}
Common pitfall: typing a keyword into searchTerms without filling auth_token and ct0. X answers its search endpoint with HTTP 404 and an empty body to every logged-out caller on all four tabs, so the actor fails the run immediately with an explanation rather than returning an empty table that reads as "nobody is posting about this." Leave searchTerms empty and it runs keyless against the profiles in startUrls instead — the default startUrls: ["NASA"] is exactly that path.
🔼 Output sample
Results are typed, normalized JSON — one schema for search results and profile/post lookups, so they can be read side by side — exportable from the Apify dataset as JSON, CSV, Excel, HTML table, or RSS. A post row (type: "tweet") carries all 39 base fields plus the 5 search-provenance fields added by this actor:
{"type": "tweet","id": "2083317461269598348","url": "https://x.com/NASA/status/2083317461269598348","user_posted": "NASA","name": "NASA","description": "Artemis II is go for launch. Watch live as our astronauts head to the Moon. #Artemis","date_posted": "2026-07-30T14:02:11.000Z","likes": 18420,"replies": 612,"reposts": 4310,"quotes": 208,"bookmarks": 950,"views": 1382044,"lang": "en","conversation_id": "2083317461269598348","is_repost": false,"reposted_by": null,"is_quote": false,"is_reply": false,"in_reply_to_user": null,"in_reply_to_post_id": null,"is_pinned": false,"is_verified": true,"is_blue_verified": true,"verified_type": "Business","followers": 55200000,"following": 297,"posts_count": 68541,"profile_image_link": "https://pbs.twimg.com/profile_images/000/nasa.jpg","biography": "Exploring the universe and our home planet.","external_url": "https://www.nasa.gov","hashtags": ["Artemis"],"tagged_users": null,"photos": null,"videos": ["https://video.twimg.com/ext_tw_video/0/pu/vid/1280x720/artemis.mp4"],"media": [{"type": "video","image_url": "https://pbs.twimg.com/ext_tw_video_thumb/0/pu/img/artemis.jpg","alt_text": null,"width": 1280,"height": 720,"video_url": "https://video.twimg.com/ext_tw_video/0/pu/vid/1280x720/artemis.mp4","duration_ms": 93000,"expanded_url": "https://x.com/NASA/status/2083317461269598348/video/1"}],"quoted_post": null,"poll": null,"link_preview": null,"input": { "url": "https://x.com/NASA/status/2083317461269598348" },"source_type": "search","matched_search_term": "#Artemis","resolved_query": "#Artemis min_faves:50 lang:en filter:media since_time:1785600000","search_product": "Latest","result_rank": 3}
Three other row shapes appear in the same dataset, each with its own field set:
type: "user"(People tab 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 5 provenance fields.type: "search_summary"(uncharged, one per search term) —target,resolved_query,search_product,results_returned,results_kept,results_dropped_by_filter,pages_fetched,errorReason,message,input. Makes a term that found nothing visible instead of silently absent.type: "error"(uncharged, one per profile/post target that returned nothing) —target,errorReason(e.g.user_not_found,protected_account,account_suspended,post_not_found),message,input,source_type.
Only tweet and user rows are billed. To pull just the charged data, filter the dataset on type != "error" && type != "search_summary".
🧭 How do you filter and target specific posts?
This is the part worth getting right: X's search rate-limit bucket is shared across all four tabs (187 requests / 15 minutes) on the supplied session, so precise filters matter more than brute-force volume.
- Free-text keyword vs. operator-only run. Type a term in
searchTermsfor a normal keyword/hashtag search. LeavesearchTermsempty but set a filter likeconversationIdorquotedPostId, and the actor runs one search whose entire query is the compiled operators — "every reply in this thread" or "everyone who quoted this post" needs no keyword at all. - Scope precision.
postedAfter/postedBeforecompile to X's exactsince_time:/until_time:epoch operators (not the day-formsince:/until:, which X applies on the session's own local day boundary and measurably leaks rows outside the requested window).fromHandle,toHandle,conversationId, andquotedPostIdnarrow to one author, one reply target, one thread, or one quote chain — all applied by X server-side. - Quality thresholds.
minLikes,minRetweets, andminRepliesare engagement floors applied server-side and re-checked locally against the published count.searchLanguageandmediaFilterwork the same way. There is no minimum-follower or verification-status input filter — segment on thefollowers/is_blue_verifiedoutput columns after the run. - Volume controls.
maxTweets(1–1000) caps rows per profile and per search term independently; every row counts toward it, including the pinned post and the extra posts theMediatab over-delivers. Pagination walks up to 40 pages per search term or 60 per profile before stopping, and a search term is abandoned early after 5 consecutive pages are entirely removed by your own filters, so a too-strict filter combination fails fast instead of burning the shared quota.
Three real examples:
{ "searchTerms": ["battery recall"], "minLikes": 25, "postedAfter": "1 day", "searchLanguage": "en" }
{ "searchTerms": [], "conversationId": "2083317461269598348" }
{ "searchTerms": ["#Artemis"], "fromHandle": "NASA", "mediaFilter": "with_media", "maxTweets": 200 }
▶️ Want to try other X and social scrapers?
| Scraper | What it extracts |
|---|---|
| X (Twitter) User Profile and Tweets Scraper — Posts from a Place | Profile timelines and place-tagged or geo-radius X posts, keyless |
| Threads Search Post Scraper | Threads posts matching a search term |
| Reddit Subreddit Members Scraper By Time Window | Subreddit posters and commenters swept across feeds by time window |
| LinkedIn Profile Posts Scraper By Media & Hashtag | LinkedIn profile posts filtered by media type and hashtag |
| TikTok Data Scraper — Video, Hashtag & Sound Performance | TikTok videos, hashtags, sounds, profiles and comments |
💻 How to extract X data programmatically
This actor runs as a standard Apify Actor: start it from the Console, via a schedule, or with apify-client — one call, structured JSON back, no separate signup with X.
🐍 Python example
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run_input = {"searchTerms": ["#Artemis"],"minLikes": 50,"postedAfter": "3 days","auth_token": "<your auth_token cookie>","ct0": "<your ct0 cookie>",}run = client.actor("<YOUR_USERNAME>/twitter-x-tweets-scraper-scraper-by-keyword-and-hashtag").call(run_input=run_input)for item in client.dataset(run["defaultDatasetId"]).iterate_items():if item.get("type") == "tweet":print(item["user_posted"], item["likes"], item["url"])
📤 Export to spreadsheets or CRM
Download the dataset as CSV directly from the Apify Console or GET /v2/datasets/{id}/items?format=csv, then map columns on import: user_posted → Contact/Handle, description → Post text, likes/views → Engagement, url → Source link, date_posted → Timestamp. No MCP server ships with this actor; any agent framework that can call an HTTP endpoint or the apify-client SDK can invoke it directly.
⚖️ Is it legal to scrape X (Twitter)?
Scraping publicly visible X posts is generally legal — these are posts X itself serves to any logged-out or logged-in visitor, and public posts exist to be read and discovered. This is not a purely anonymous business-data feed, though: every row carries personal data about an identifiable individual — a handle, display name, bio, follower count, and avatar, plus the post text itself. That brings GDPR and CCPA into play if you store or reuse this data: you need a lawful basis for processing, and data-subject rights (access, deletion) still apply even though the data was public when collected. Protected accounts never appear, since X does not serve their posts outside their approved followers. Consult legal counsel for commercial applications involving bulk storage of personal data.
❓ FAQ
Does the output flag deleted, suspended or protected posts and accounts?
Yes. A target that returns nothing gets an uncharged type: "error" row with a typed errorReason — post_not_found, post_unavailable, account_suspended, protected_account, user_not_found, or user_unavailable — and a plain-language message. Nothing is silently dropped.
Can I get author profile data along with the posts?
Yes, on every post row: followers, following, posts_count, biography, external_url, profile_image_link, is_blue_verified, and verified_type all come from the same author lookup that built the row, with no separate request needed.
How accurate are the likes, replies and view counts?
The counts are exactly what X published for that post at request time. A repost row reports the original post's real counts, not the retweet wrapper's structural zeros, so a minLikes filter is never defeated by a repost. Counts change after the fact as a post keeps circulating, so treat every run as a snapshot, not a running total.
How many posts can I get per run?
Up to maxTweets (1–1000, default 10) from each profile and from each search term — the limit is per target, not per run. Pagination is capped at 60 pages per profile timeline and 40 pages per search term in the source code, and duplicates are removed before anything is charged, so you are never billed above what you asked for.
Can I search for every reply in a thread, or every quote of a post, without typing a keyword?
Yes. Leave searchTerms empty and set conversationId or quotedPostId alone — the actor runs one search whose entire compiled query is that operator, which is exactly how you pull an entire reply thread or every quote-tweet of one post.
Are the error and search-summary rows charged?
No. type: "error" and type: "search_summary" rows are pushed without a charged event, specifically so a target or term that found nothing is visible in the dataset without costing you anything. Only type: "tweet" and type: "user" rows are billed.
Does this scraper work with Claude, ChatGPT and other AI agent frameworks?
There is no bundled MCP server for this actor. It is callable as a standard HTTP endpoint through the Apify API, or via the apify-client SDK, so any agent framework that can make an HTTP call or run Python can use it as a tool.
How far back can I search, and why do some searches return nothing?
X's own consumer search index — the same one this actor queries — is partial and recency-weighted, and X does not publish how far back it reaches. A zero-result search is not proof nobody posted; it means the index did not surface a match. See "How does this differ from the official X API" above for the official API's own documented 7-day recent-search limit.
Can I use this without an X developer account or API key?
Yes for profiles and single post URLs — those run keyless. For keyword/hashtag search or a profile's own replies, you need two cookie values (auth_token, ct0) copied from a normal logged-in x.com browser session — no X developer account, app registration, or API key required.
Conclusion
This actor turns X keyword and hashtag monitoring into one typed, auditable dataset call — compiled search operators, honest repost/reply handling, and a normalized row schema shared with plain profile and post scraping. It fits social listening teams, agencies, researchers, and AI pipelines that need fresh X data without a developer-approved API key or per-read billing. Press Start on the Apify Console with the default startUrls: ["NASA"] to see a keyless run first, then add a keyword and the two session cookies once you are ready to search.