X (Twitter) Tweets Scraper — Any Account's Timeline avatar

X (Twitter) Tweets Scraper — Any Account's Timeline

Pricing

from $0.34 / 1,000 results

Go to Apify Store
X (Twitter) Tweets Scraper — Any Account's Timeline

X (Twitter) Tweets Scraper — Any Account's Timeline

Collect everything a public X account has posted, starting from nothing but its handle. Every post arrives as one clean row: what was said, when it was said, and how far it travelled. Several accounts in one run, and no X login, cookie or account anywhere in it.

Pricing

from $0.34 / 1,000 results

Rating

0.0

(0)

Developer

The Netaji

The Netaji

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

X (Twitter) Tweets Scraper

Collect everything a public X account has posted, starting from nothing but its handle. Every post comes back as one clean row: what was said, when it was said, and how far it travelled. The author's follower count rides along on each row, so reach arrives with the post instead of costing a second lookup per account.

No X login, cookie or session is involved at any point in a run. Nothing has to be connected, and no account of anyone's is put at risk to collect a timeline.

Several accounts can be collected in one run. Bare handles, @handles and pasted profile links are all accepted, and a single list may mix them.

Accepted input

handles is required and takes one or more accounts. maxItems caps how many rows are saved across the whole run rather than per account, and defaults to 100; setting it to 0 removes the cap. resumeCursor continues a previous run instead of starting at the top of a timeline. enrichParentPost attaches the post that each reply is answering, and is off by default.

{
"handles": ["nasa", "https://x.com/SpaceX"],
"maxItems": 200
}

Response fields

One row per post, newest first.

FieldContents
tweet_id, tweet_urlThe post's identifier, and a link to it
textThe post body; is_retweet and is_long_post qualify it
is_long_posttrue when text is a long post's complete body rather than its first 280 characters
posted_at, langPublish time in X's own date format, and X's language guess
author_id, author_handle, author_nameThe account that posted it
author_followers_countThat account's follower count, on every row
author_verified, author_is_blue_verifiedLegacy check and current mark
author_profile_image_urlThe author's avatar
like_count, retweet_count, reply_count, quote_countEngagement counts
bookmark_count, view_countBookmarks and impressions, both as numbers
is_retweet, retweeted_tweetWhether the row is a retweet, and the original in full when it is
is_quote, quoted_tweetWhether the post quotes another, and that post in full
conversation_id, reply_to_tweet_id, reply_to_handlePosition within a thread
entitiesX's own hashtags, urls, user_mentions, symbols and media structure
possibly_sensitiveX's sensitive-content flag
requested_handleThe handle the run asked for, before X re-cased it
reply_to_tweetThe post this row replies to; present only when enrichParentPost is on
{
"tweet_id": "2087966752512561302",
"tweet_url": "https://x.com/NASA/status/2087966752512561302",
"text": "Yesterday's total solar eclipse in parts of Europe – and the partial eclipse in parts of North America – gave us spectacular views. @NASAHQPhoto was on location to capture the sights. See more: https://t.co/IQ6yWeVbXl",
"posted_at": "Thu Aug 13 18:17:21 +0000 2026",
"lang": "en",
"author_handle": "NASA",
"author_followers_count": 92309471,
"like_count": 16970,
"reply_count": 258,
"view_count": 1792803,
"bookmark_count": 1180,
"is_retweet": false,
"requested_handle": "nasa"
}

Questions that come up

Why did a run save fewer posts than maxItems?

X treats a requested page size as a hint rather than an instruction, and it decides when to stop handing out pages. Over eight consecutive pages of one account, asking for twenty posts each time, X delivered 12, 17, 18, 14, 16, 16, 16 and 13: 122 posts, zero duplicates, and not one page returning the number asked for. A run therefore ends when X stops offering the next position, which can happen before maxItems is reached. It never ends merely because a page came back thin; a walk that treated a short page as the end would stop on page one and report twelve posts for an account holding tens of thousands.

How far back into a timeline does a run reach?

Further than any number I am willing to print. The measured walk above reached 122 posts and was still being offered another page, so it was never run to exhaustion; the depth is a floor rather than a ceiling. The tweet_count field on an account's profile record is how many posts X states the account has made, not how many a timeline walk reaches, and it should not be used as a budget for a run.

Can a long export be picked up where it stopped?

Yes. A resume token is printed to the run log on every page that offers one, and supplying the last one as resumeCursor continues from that position rather than re-walking the top of the timeline. A token is a position within one account's stream and carries no meaning in another's, so it applies to the first account in the list and is ignored for the rest; resuming is done one account at a time.

Why does text start with RT @ on some rows?

Because the row is a retweet, and that truncated form is X's own. The original post is published whole on the same row under retweeted_tweet, with its own author, engagement counts and untruncated text, so nothing is lost. Quote posts behave the same way: text is the commentary and quoted_tweet is the post being commented on. Both halves are published rather than one being substituted for the other, because they answer different questions: the outer record is what the account did, and the nested one is what it amplified.

Are long posts cut off at 280 characters?

No. When is_long_post is true, text is the complete body. X's ordinary post field would return a 4,000-character post as its first 280 characters, with no ellipsis and no marker, which is a truncation that reads exactly like a complete post; that is the failure the flag exists to prevent.

Where are the images and videos on a post?

Inside entities, in X's own structure, alongside hashtags, urls, user_mentions and symbols, each carrying the character offsets into text where it appears. Anything already written against a Twitter payload reads those keys as they stand. There is deliberately no flattened column of media links beside them: the shape varies by attachment type, and publishing one column would mean guessing at key names I have not measured.

Can search results, the replies under a post, follower lists or a media tab be collected?

No, and not by any setting. Each of those needs a logged-in X account, and this Actor holds none. That is the trade for an Actor that asks for no login, no cookie and no session, and it is a measured boundary rather than something still to be built. reply_count still reports how many replies X counts on a post, entities still carries a post's media, and a thread is still walkable upward: reply_to_tweet_id is an ordinary post identifier.

What does enrichParentPost actually cost?

One extra request per reply, billed once per reply whose parent is returned. Rows that are not replies are untouched and cost nothing, so the option is close to free on an account that mostly posts and roughly doubles the request count on an account that mostly replies. A parent that has been deleted, or that sits on a protected account, leaves the reply row unchanged and is not billed. The same hop can be taken by hand afterwards, since reply_to_tweet_id is a plain post identifier; the option exists to do it inside one run rather than two.

What happens to a handle that is wrong, retired, or protected?

A handle outside X's own rule of 1 to 15 letters, digits or underscores is reported in the run log and skipped before a request is spent on it. A handle that does not resolve to any account is likewise reported and skipped, because X answers an unknown account as a normal empty response rather than as an error and there is nothing to retry. A protected account returns an empty timeline, since this Actor holds no X account and follows nobody. In every case the remaining accounts in the list are still collected. Duplicates are collapsed before any request is made, so nasa, @NASA and https://x.com/nasa in one list are one account and one set of charges.

Why is view_count empty on some posts?

Because X published no view count for them, which is normal on older posts. Null means that no count was stated and is never collapsed to 0, so an average over the column is not quietly dragged towards zero by posts that simply predate the metric. Views and bookmarks are both numbers rather than text, so sorting on them orders 54752 above 9 instead of the reverse.

Are missing fields dropped from a row?

No. Every row has the same shape, and a field absent upstream is null rather than omitted. The three flags a reader has to branch on are the exception in the other direction: is_retweet, is_quote and is_long_post are always true or false, never null, so a check on one never has to handle a third case.

Which column joins back to the input list?

requested_handle. X returns handles in its own casing, so a run asking for nasa produces NASA in author_handle; requested_handle echoes the value supplied, reduced from an @handle or a pasted profile link, so a join never depends on two sides agreeing about normalisation.

If a field returns null where a value is clearly present on x.com, the Actor's Issues tab is the fastest way to reach me; a post identifier in the report is usually enough to reproduce it.

X (Twitter) Profile Scraper returns the accounts themselves rather than their posts, including the bio, join date, and the full set of counts X publishes. X (Twitter) Post Scraper takes post links directly and returns the same row shape as this Actor, which makes it the better choice once specific posts are known; for a whole timeline this Actor is the cheaper one, because a single request returns a page of posts rather than one.