X (Twitter) Media Scraper | $0.40/1K | Photos and Videos
Pricing
from $0.32 / 1,000 media returneds
X (Twitter) Media Scraper | $0.40/1K | Photos and Videos
Export photos, videos and gifs from X (Twitter) accounts or posts. One row per attachment with every video encoding, bitrate, dimensions, duration, alt text, preview image, and the parent post with its author and engagement. Direct links, ready to download, as JSON, CSV or Excel. By FeedMiner.
Pricing
from $0.32 / 1,000 media returneds
Rating
0.0
(0)
Developer
FeedMiner
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
8 hours ago
Last modified
Categories
Share
🎞️ Every encoding, not just one. A video comes back with its full variant list, best first: the highest bitrate for archiving, the smallest for a preview. Tools that return a single link decide for you.
💸 $0.40 per 1,000 files, down to $0.32 on the larger Apify plans. The visible alternatives charge $2.99 and $5.00 per 1,000 for the same links, plus a start fee on every run. No start fee here.
🖼️ The post travels with the file. Each row carries the parent post, its date, its author and its engagement, so a media export doubles as a content performance table.
♿ Alt text included. The accessibility description creators write is on the row, which is the field content teams and dataset builders ask for first.
| FeedMiner · X (Twitter) suite • Posts, profiles, audiences, media, trends | ||
➤ You are here | Brand watch, only what is new · $0.12/1K mentions | Quote authors and repliers · $0.15/1K accounts |
Copy to your AI assistant
feedminer/x-media-scraper on Apify. Exports photos, videos and gifs from X (Twitter) accounts or individual posts. One row per attachment with id, type, url (best encoding), previewUrl, contentType, bitrate, width, height, durationMs, altText, variants[] and the parent post (tweetId, tweetUrl, text, createdAt, likeCount, viewCount, author). Call ApifyClient("TOKEN").actor("feedminer/x-media-scraper").call(run_input={...}), then client.dataset(run["defaultDatasetId"]).list_items().items. Input: handles (string[]), startUrls (string[] of post URLs or ids), mediaTypes (string[] of photo|video|gif), maxItems (int), sessions (array, optional). Full spec: GET https://api.apify.com/v2/acts/feedminer~x-media-scraper/builds/default (Bearer TOKEN) → inputSchema, actorDefinition.storages.dataset, readme. Token: https://console.apify.com/account/integrations
How to scrape media from X (Twitter)
Basic: everything an account posted
{ "handles": ["nasa"], "maxItems": 200 }
Videos only, for a highlight reel
{ "handles": ["nasa"], "mediaTypes": ["video"], "maxItems": 50 }
A single post you already have
{ "startUrls": ["https://x.com/NASA/status/2089425053599289546"] }
A batch of posts from another run
{"startUrls": ["https://x.com/NASA/status/2089425053599289546","https://x.com/NASA/status/2089802824678879526"],"maxItems": 100}
Pasted posts are read together, so a hundred URLs cost one or two requests rather than a hundred.
Photos from several accounts, for a brand asset sweep
{ "handles": ["stripe", "adyen"], "mediaTypes": ["photo"], "maxItems": 400 }
Input parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
handles | array | Accounts whose media you want, as handles or profile URLs | |
startUrls | array | Individual posts to read, as post URLs or ids | |
mediaTypes | array | Leave empty for everything | |
maxItems | integer | 1000 | Stop once this many attachments have been collected |
Advanced
| Parameter | Type | Default | Description |
|---|---|---|---|
sessions | array | Optional. The Actor works without one. X accounts of your own, for anyone who would rather the run used accounts they control | |
debugMode | boolean | false | Verbose logs for support |
Coming from another media downloader
tweetUrls, links, urls and postUrls are all read as posts, and onlyVideos or onlyImages select the kind. The input you already send elsewhere works unchanged.
What an account changes here
Nothing about what you get, only how fast. An account makes an account's media tab answer straight away; without one the same rows still come back, they just take longer.
What data can you extract from X media?
| Field | Type | Description |
|---|---|---|
id | string | Media id |
type | string | photo, video or animated_gif |
url | string | Direct link to the file. For a video, the best encoding |
previewUrl | string | Still image, the poster frame for a video |
contentType | string | MIME type of url, for example video/mp4 |
bitrate | integer | Bitrate of url, in bits per second |
width height | integer | Original dimensions |
durationMs | integer | Video duration in milliseconds |
altText | string | Accessibility description written by the author |
variants | array | Every encoding, best first, each with url, bitrate, contentType |
tweetId | string | Parent post id |
tweetUrl | string | Parent post link |
text | string | Parent post text |
createdAt | string | Parent post date, RFC 3339 |
likeCount viewCount | integer | Parent post engagement |
author | object | Handle, name, bio, followers, verification, avatar |
Example output
{"id": "2089424963400818688","type": "video","url": "https://video.twimg.com/amplify_video/2089424963400818688/vid/avc1/1920x1080/xyz.mp4","previewUrl": "https://pbs.twimg.com/media/HP8hAu4WEAAJLH7.png","contentType": "video/mp4","bitrate": 10368000,"width": 1920,"height": 1080,"durationMs": 45120,"altText": "Artemis II crew waves before boarding","variants": [{ "url": "...1920x1080...", "bitrate": 10368000, "contentType": "video/mp4" },{ "url": "...1280x720...", "bitrate": 2176000, "contentType": "video/mp4" }],"tweetId": "2089425053599289546","tweetUrl": "https://x.com/NASA/status/2089425053599289546","author": { "userName": "NASA", "followers": 79000000 }}
Column mapping, if you are switching
| Here | igview |
|---|---|
url | downloadUrl |
previewUrl | thumbnail |
type | mediaType |
durationMs | video.duration |
variants | video.quality, one value there, the whole ladder here |
tweetUrl | tweetUrl |
text | text |
author.userName | username |
author.name | name |
The difference that matters is variants: one encoding there, every encoding X
offers here, best first, so archiving and previewing come from the same row.
Pricing: Pay Per Event (PPE)
| Event | Price |
|---|---|
| Media returned | $0.0004 |
That is $0.40 per 1,000 files, with no start fee and no charge for a run that returns nothing. The price steps down on its own for the larger Apify plans, to $0.36 and then $0.32 per 1,000.
What a run costs
| An account's media tab, 600 files | $0.24 |
| A 5,000 file archive | $2.00 |
Free tier
The Apify free plan includes $5 of usage every month, which is roughly 12,000 files here. No card required.
Advanced usage
Pick your own quality. url is the best encoding, and variants holds the rest. Archiving takes the first, a preview grid takes the last, and a bandwidth budget takes the one under your bitrate ceiling.
Build a training set with captions. altText plus previewUrl is an image and description pair, straight out of the export.
Rank creative by performance. Every row carries the parent post's likes and views, so sorting media by viewCount tells you which visual actually worked.
Four photos in one post stay four rows. A gallery is not collapsed to its first image, which is where several tools quietly lose three quarters of the files.
Links have a lifetime. X's file links are served from its own CDN and are stable for a long while, not forever. Download what you need close to the run rather than storing links for months.
A pasted post always answers. When X's usual route refuses a post, the run reads it through the same public endpoint that powers embedded posts on other websites. The files and their encodings come back identical; the parent post's view count is the only field that endpoint does not publish, and it stays at zero rather than being guessed.
Integrate X media into your stack
Python
from apify_client import ApifyClientimport requestsclient = ApifyClient("YOUR_APIFY_TOKEN")run = client.actor("feedminer/x-media-scraper").call(run_input={"handles": ["nasa"],"mediaTypes": ["video"],"maxItems": 25,})for row in client.dataset(run["defaultDatasetId"]).list_items().items:with open(f"{row['id']}.mp4", "wb") as f:f.write(requests.get(row["url"]).content)
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });const run = await client.actor('feedminer/x-media-scraper').call({handles: ['nasa'],maxItems: 100,});const { items } = await client.dataset(run.defaultDatasetId).listItems();const light = items.map((m) => m.variants?.at(-1)?.url ?? m.url);
No code
Push rows to Google Sheets, Airtable, Slack, Zapier, Make or n8n from the Integrations tab of the run.
Performance
| Measure | Value |
|---|---|
| Long lists of links | A hundred pasted post links cost barely more time than one |
| Depth | 626 files from one large account's media tab, measured |
| Parallelism | Up to four accounts at a time |
| Partial results | Kept and delivered when a limit is reached |
FAQ
Does it download the files? It returns direct links to them, which is what an integration, a downloader or a browser needs. Downloading is three lines in the Python example above.
Why does a video have several links?
X encodes each video at several qualities. url is the best one, variants is the full list, best first.
Are the links permanent? They are served by X's CDN and stay valid for a long time, not forever. Fetch what you keep close to the run.
Do I get media from replies too?
The media tab of an account is what X publishes as its media. Point startUrls at a specific post to get exactly that post's attachments.
Can I filter by date?
Not in this actor. Use X Advanced Search Scraper with since and until to get the post URLs, then paste them here.
What about a protected account? Its media is not public, and the run reports it instead of failing.
Is alt text always there? Only when the author wrote one. The field is empty otherwise, never invented.
Can I get a thumbnail for a photo?
previewUrl is the image itself for a photo, and the poster frame for a video.
Support
A file you cannot reach, a variant that looks wrong, a format you need: write from the actor page.
Legal compliance
This actor reads media that X publishes publicly. It does not access private accounts or direct messages. Media is copyrighted by its authors: exporting links for analysis, monitoring or archiving is not a licence to republish. Personal data in the output is subject to GDPR, CCPA and similar regimes.
FeedMiner · Fast, honestly priced scrapers, kept alive when platforms change.
X Tweet Scraper · X Profile Scraper · X Follower Scraper · X Advanced Search Scraper · X Media Scraper · X Mentions Monitor · X Engagement Scraper · X Likes and Reposts Scraper · X Trends Scraper · All FeedMiner Actors