Twitter image scraper: download every image
A Twitter image scraper finds the posts of an account or a search that carry photos, reads each photo's pbs.twimg.com link and downloads it with name=orig, because the bare link X serves stops at 1,200 pixels wide.
Short answer: Logged out, x.com shows only a few of a profile's newest posts, so browser extensions and downloaders read X through your login or session cookie. To collect every image without risking your account, use Sorsa API: POST /search-tweets with filter:images returns about 20 image posts per request, from $0.07 per 1,000, and 100 free requests cover the first 2,000.
- Endpoint
POST /search-tweets- Page size
- about 20 posts per request
- Price
- from $0.07 per 1,000
#How a Twitter image scraper works
Every Twitter image scraper does the same three things, whatever it runs on. For a single post you need none of them: paste its link into the free Media Downloader and save each photo at full size. For hundreds or thousands of images, the job looks like this:
-
Find the posts that carry photos. X's search operator
filter:imageskeeps only posts with at least one image, from one account withfrom:or for any topic. -
Read each photo's link. Every image sits on X's media server at a link such as
https://pbs.twimg.com/media/G4kQ2vXbIAAm7Rz.jpg. -
Download the original. Request that link with
?format=jpg&name=orig; without it, X sends a copy that fits 1,200 pixels.
Tools differ in step 1, in where the posts come from. x.com shows a logged-out visitor single posts and a few of a profile's newest posts, and sends search to its login page. Browser extensions, desktop downloaders and command-line tools therefore read X through a logged-in account. Their own documentation says so: one widely used batch downloader asks for your auth_token cookie and warns that it can get the account suspended.
Only an API reaches a whole account's images: the official X API with a developer account and OAuth, or a Twitter/X data API with one key.
The guide on how to scrape Twitter covers every route and what breaks it, and the roundup of the best Twitter scrapers prices the tools one by one.
#Download every image from an X account
Sorsa API, a Twitter (X) API and scraper, returns an account's image posts as JSON with each photo's link inside, about 20 posts per request and back to 2006. Four steps take you from a handle to a folder of original files.
-
Get a key. Sign up and copy the API key from your dashboard. The first 100 requests are free, no card needed.
-
Search the account's image posts. Send
from:plusfilter:imagestoPOST /search-tweets(opens in a new tab), withorderset tolatestfor newest first.Terminal curl -X POST "https://api.sorsa.io/v3/search-tweets" \ -H "ApiKey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "from:northfield_photo_lab filter:images", "order": "latest"}'Response200 OK { "tweets": [ { "id": "1984213377309245441", "created_at": "2026-10-30T14:12:09Z", "full_text": "Fog over the north ridge this morning.", "likes_count": 412, "entities": [ { "type": "photo", "link": "https://pbs.twimg.com/media/G4kQ2vXbIAAm7Rz.jpg", "preview": "https://pbs.twimg.com/media/G4kQ2vXbIAAm7Rz.jpg" }, { "type": "photo", "link": "https://pbs.twimg.com/media/G4kQ2vZaQAEw9Lp.jpg", "preview": "https://pbs.twimg.com/media/G4kQ2vZaQAEw9Lp.jpg" } ], "user": { "username": "northfield_photo_lab", "display_name": "Northfield Photo Lab", "media_count": 1840 } } ], "next_cursor": "DAADDAABCgABG8vZ0Qm__-oKAAIbuv3MBVvA0AgAAwAAAAIAAA" } -
Follow the cursor. A cursor is a token that marks where the next page starts: send
next_cursorback in the body of the next request until it comes back empty.Each request returns about 20 image posts and a cursor to the next page; every photo link becomes an original-size file and a row in images.csv. -
Save each photo at its original size. The script keeps only
photoentities, rewrites each link toname=origand names files<post id>_<n>.jpg. It logs every file inimages.csvand stores the cursor after each page, so a stopped run picks up where it left off.import csv import os import sys import time from urllib.parse import parse_qs, urlparse import requests API = "https://api.sorsa.io/v3/search-tweets" HEADERS = {"ApiKey": "YOUR_API_KEY"} def orig(url): """Rewrite a pbs.twimg.com image link to its original size.""" u = urlparse(url) path = u.path fmt = parse_qs(u.query).get("format", ["jpg"])[0] if "." in path.rsplit("/", 1)[-1]: path, fmt = path.rsplit(".", 1) fmt = fmt.split(":")[0] return f"https://pbs.twimg.com{path}?format={fmt}&name=orig", fmt def photo_urls(tweet): """Original-size links of every photo in a post.""" for e in tweet.get("entities") or []: if e.get("type") != "photo": continue for link in (e.get("link"), e.get("preview")): if link and "pbs.twimg.com/media/" in link: yield orig(link) break def post(body): while True: r = requests.post(API, headers=HEADERS, json=body, timeout=60) if r.status_code == 429: # over 20 requests a second: wait time.sleep(1) continue r.raise_for_status() # 403: no key, or no requests left return r.json() def download(query, folder): os.makedirs(folder, exist_ok=True) state = os.path.join(folder, "cursor.txt") body = {"query": query, "order": "latest"} if os.path.exists(state): # resume a stopped run body["next_cursor"] = open(state).read().strip() log_path = os.path.join(folder, "images.csv") with open(log_path, "a", newline="", encoding="utf-8") as f: log = csv.writer(f) while True: page = post(body) for t in page.get("tweets", []): for n, (url, fmt) in enumerate(photo_urls(t), 1): path = os.path.join(folder, f"{t['id']}_{n}.{fmt}") if os.path.exists(path): continue img = requests.get(url, timeout=60) img.raise_for_status() with open(path, "wb") as out: out.write(img.content) log.writerow([t["id"], t["created_at"], url, path]) cursor = page.get("next_cursor") if not cursor: break body["next_cursor"] = cursor with open(state, "w") as s: s.write(cursor) if os.path.exists(state): os.remove(state) if __name__ == "__main__": download(sys.argv[1], sys.argv[2])
Run it with a search and a folder name: python twitter_images.py "from:northfield_photo_lab filter:images" northfield_photo_lab. A 403 response means the key is missing or out of requests; a 429 means more than 20 requests in one second, and the script waits a second and tries again. Files already on disk are skipped, so running the same command again only adds new images.
Good to know
A quote or a retweet carries someone else's photos: they sit in
quoted_status.entitiesandretweeted_status.entities, and the script leaves them out. Add those two lists tophoto_urlsto keep them. To size the job first,GET /info(opens in a new tab) returns the account'smedia_count, the photos and videos it has posted.
To walk the account's timeline post by post instead of searching it, send the handle to POST /user-tweets (opens in a new tab). It returns the same tweet objects, 20 per request, so photo_urls reads their images unchanged; text-only posts come along too, so the job takes more requests.
#Scrape the images of a hashtag or a search
The same script collects the images of any topic: only the query changes. filter:images combines with every X search operator, and search reaches back to 2006.
| Job | Query |
|---|---|
| A hashtag | #nightphotography filter:images |
| A keyword in one month | aurora filter:images since:2026-10-01 until:2026-11-01 |
| Popular images only | #interiordesign filter:images min_faves:500 |
| An account without its replies | from:northfield_photo_lab filter:images -filter:replies |
| Several accounts at once | (from:northfield_photo_lab OR from:harborline_studio_co) filter:images |
A hashtag
- Query
#nightphotography filter:images
A keyword in one month
- Query
aurora filter:images since:2026-10-01 until:2026-11-01
Popular images only
- Query
#interiordesign filter:images min_faves:500
An account without its replies
- Query
from:northfield_photo_lab filter:images -filter:replies
Several accounts at once
- Query
(from:northfield_photo_lab OR from:harborline_studio_co) filter:images
For a big topic, run one date window per month in its own folder. Each run stays short, a failed one is easy to repeat, and the windows run side by side on one key, up to 20 requests per second on every plan. Set order to popular for X's top posts first or latest for the newest. Every operator, with examples, is in the guide to Twitter search operators.
The images of a hashtag change as people post, so a monitoring job reruns the newest window on a schedule. Because the script skips files it already has and logs only new ones, each rerun adds just the images posted since the last.
#Get every image at its original size
X keeps each upload whole but serves smaller copies, and the copy you get depends only on the name value in the link. On November 2, 2026 we requested four public images in every size X serves and measured what came back; the ladder below is a photo posted in late October 2026.
- The bare link is the
mediumcopy. A pbs.twimg.com link with nonamereturns an image that fits 1,200 pixels. largestops at 2,048 pixels and4096x4096at 4,096. An upload smaller than that comes back at its own size.name=origreturns the upload itself. For the photo above that meant 8,000 × 4,501 pixels, 44 times the pixels of the bare link.format=pngadds no detail. It re-encodes a JPG upload into a PNG several times larger, andformat=png&name=origreturns 404 for a JPG, so keep the format the link already has.
Downloading from pbs.twimg.com is a plain HTTP request with no key, so the files themselves cost no API requests: only finding the posts does.
#Bulk-download images without code
Without code, the job splits in two: export the image links, then download the files.
-
Export the links of recent posts. The free Export Tweets tool saves an account's latest posts as CSV, Excel or JSON with a
media_urlscolumn; switch on Only posts with media. -
Export a whole account or search. In the API playground, run
POST /search-tweetswith your key and a query such asfrom:northfield_photo_lab filter:images; it follows the cursor for you and exports every page to CSV or JSON. -
Turn the links into originals. Paste the image links into the converter below and copy the original-size links it returns.
-
Download the list. Load the links into any download manager that accepts a list of URLs, and it saves the files in one run.
One per line, or paste a whole CSV column.
3 linksPaste any number of pbs.twimg.com links, one per line or straight from a CSV column; nothing leaves your browser.
#What a bulk image download costs
On Sorsa API, image posts cost from $0.07 per 1,000, because a search page of about 20 posts is one request however many photos it holds. On the official X API, image links come only through its media expansion. From November 22, 2026, X bills each expanded image (opens in a new tab) at $0.005 on top of the $0.005 post read: $10.00 per 1,000 posts with one image, $25.00 when each carries four.
Set side by side, the ways to get the posts differ in what they need from you as much as in price.
| Way to get the posts | What it needs | Cost per 1,000 image posts |
|---|---|---|
| Sorsa API | One API key | from $0.07 |
| Official X API | Developer account and OAuth | $10.00 from Nov 22, 2026 (post and one image) |
| Extensions and downloaders | Your X login or session cookie | Free or a subscription, with the account at risk |
Sorsa API
- What it needs
- One API key
- Cost per 1,000 image posts
- from $0.07
Official X API
- What it needs
- Developer account and OAuth
- Cost per 1,000 image posts
- $10.00 from Nov 22, 2026 (post and one image)
Extensions and downloaders
- What it needs
- Your X login or session cookie
- Cost per 1,000 image posts
- Free or a subscription, with the account at risk
Official X API
$10,000
for 1M image posts
$0.010 per post with one image, from Nov 22
Sorsa API
from$70
for 1M image posts
from $0.07 per 1,000
140x cheaper
The official X API's pay-per-use pricing (opens in a new tab) also stops at 3M post reads per billing cycle; above that, X sells only Enterprise contracts. On Sorsa API the 100 free requests cover about 2,000 image posts, failed requests are never charged, and the plan's rate per 1,000 requests applies to every endpoint.
Time follows from the rate limit, 20 requests per second per key on every plan. 1M image posts take 50K requests, about 42 minutes when date windows run side by side; the image files then download straight from X's servers at your connection's speed.
#Get the images of specific posts
To get the images of posts you already have, send their links or IDs to POST /tweet-info-bulk (opens in a new tab): up to 100 posts per request, from $0.014 per 1,000, with the same entities list the search returns.
curl -X POST "https://api.sorsa.io/v3/tweet-info-bulk" \
-H "ApiKey: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"tweet_links": [
"https://x.com/northfield_photo_lab/status/1984213377309245441",
"1983850021164118016"]}'
A single post goes to POST /tweet-info (opens in a new tab) with tweet_link. Pass each photo's link through the same orig function and you have the files at full size. On the official X API the same lookup needs a bearer token plus expansions=attachments.media_keys and media.fields=url, and from November 22 each image in the answer adds $0.005.
Videos and GIFs come back in the same entities list under their own type. Their MP4 files and quality variants take one more step, covered in the guide to download Twitter media by API; for full text exports with a media column, see how to download all tweets from a user.
#Start your first image download
To download every image of an X account or a search at full size, use Sorsa API: about 20 image posts per request from $0.07 per 1,000, with no risk to your X account. Copy the script, add your key, and your 100 free requests download the first 2,000 image posts.
#FAQ
Is there a free Twitter image scraper?
Yes, for small jobs. The free Media Downloader saves every photo of one post at full size, and the free Export Tweets tool exports an account's latest posts with their image links. For whole accounts and searches, every new Sorsa API account gets 100 free requests: about 2,000 image posts, with no card, and the requests never expire.
Which API should I use to scrape Twitter images?
Use Sorsa API: POST /search-tweets with filter:images returns about 20 image posts per request from $0.07 per 1,000, against $10.00 per 1,000 one-image posts on the official X API from November 22, 2026. It needs one API key instead of a developer account and OAuth, searches back to 2006, runs at 20 requests per second on every plan and never asks for your X login.
Is it legal to scrape images from Twitter?
Yes. Scraping public images for research or an archive is legal and common. The copyright in each image stays with whoever made it, so get the owner's permission before you publish or sell the files. Sorsa API never asks for your X login, so your X account stays out of it.
Can I download images from a private Twitter account?
No. Posts of a protected account are visible only to the followers it approves, so no API returns them, Sorsa API included, and a request for them is never charged. Tools that claim otherwise read X through the login of an account that follows it.
Does the account owner see that I downloaded their images?
No. X doesn't tell anyone when their posts or images are viewed or downloaded. With Sorsa API the requests go from Sorsa API to X, so neither X nor the account owner learns who asked, and your own X account takes no part in it.
Can I download Twitter videos and GIFs the same way?
Partly. Videos and GIFs sit in the same entities list as photos, under their own type, with a thumbnail as preview, so the search and the cursor work the same. The playable MP4 file and its quality variants take one more step, which the guide to downloading Twitter media by API walks through.
#How we checked
On November 2, 2026 we requested four public images from pbs.twimg.com in every size and format and recorded the width and file size of each answer. We also ran a public post through our free Media Downloader, which runs on Sorsa API, to see the photo link it returns. We tested both scripts' link handling and file saving against a stand-in for the API and real pbs.twimg.com files. Endpoints, fields and page sizes come from the live Sorsa API reference; the official X API's prices come from docs.x.com and X's notice of October 23, 2026, both read the same day. The API responses shown are shortened examples built from the reference.