Threads Scraper — Posts, Profiles, Search & Hashtags
Pricing
Pay per usage
Threads Scraper — Posts, Profiles, Search & Hashtags
Scrape Threads posts, profiles, hashtags and search results without login. Exact like/reply/repost counts, images, videos, batch mode, pay per result.
Pricing
Pay per usage
Rating
0.0
(0)
Developer
Parker
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
10 days ago
Last modified
Categories
Share
Languages: README.md | README.zh-TW.md
Scrape Threads without logging in: posts, profiles, hashtag feeds, search results and replies — with exact engagement counts, full-resolution media, and batch processing.
What you get
Every post is one dataset record:
{"id": "DZaExc0ESvs","url": "https://www.threads.com/@zuck/post/DZaExc0ESvs","author": {"username": "zuck","displayName": "Mark Zuckerberg","profileUrl": "https://www.threads.com/@zuck","avatarUrl": "https://...","isVerified": true},"content": "Interesting Biohub conversation with No Priors...","timestamp": "2026-06-06T21:41:30.000Z","stats": { "likes": 832, "replies": 302, "reposts": 57, "shares": 0, "quotes": 23 },"images": ["https://..."],"videos": [],"links": ["https://open.spotify.com/..."],"source": "profile_posts","profile": "zuck","scrapeMethod": "graphql"}
Counts are exact. Most Threads scrapers read the numbers off the rendered page,
where Threads writes 1.5K and 5.6M — so 1,543 arrives as 1500, and a profile
with 5,687,751 followers is recorded as 5,600,000. This Actor reads Threads' own
JSON payloads, so you get the real integers. It also means extraction does not break
when Threads ships a UI change or serves a different interface language.
| No login required | All four actions work anonymously. Cookies are optional. |
| Exact stats | likes, replies, reposts, quotes as integers, never rounded. |
| Full-resolution media | Largest image variant, carousel media, videos, GIFs, link previews. |
| Batch mode | Many keywords / usernames / hashtags / post URLs in one run. |
| Transparent | Every record says which engine produced it (scrapeMethod). |
Quick start
- Set Action to
Search Posts. - Type a keyword, e.g.
openai. - Set Max Items to
20. - Click Start.
That's it — results land in the Output tab. To scrape a profile instead, switch
Action to User Profile and enter a username without the @.
Proxy. Meta blocks datacenter IPs aggressively. Residential proxies succeed far more often, at a higher cost per GB. Datacenter proxies are usually fine for low-volume profile and post runs.
Pricing
This Actor uses pay per event — you pay for results, not for compute time.
$5.00 per 1,000 results — every stored record costs the same, whether it is a post, a reply, a search hit or a profile.
| Event | When it fires | Price |
|---|---|---|
actor-start | Once per run | $0.005 |
post-output | Per post, reply or search/hashtag result stored | $0.005 |
profile-output | Per profile record stored | $0.005 |
A profile run with includePosts charges one profile-output plus one post-output
per post returned. A 500-post run costs $2.51: 500 × $0.005, plus half a cent to start.
Two guarantees worth knowing:
- You are only charged for stored results. Charging happens after the record is written, never before.
maxItemsis capped to your budget before scraping starts. If your run's charge limit only covers 30 posts, the Actor scrapes 30 and finishes as a success with a status message saying so — it never overruns the limit.
The Pricing tab on this Actor's page is the source of truth.
Input
Set action to one of search, hashtag, profile, post. Only the fields for the
chosen action are used.
Common
| Field | Type | Description | Default |
|---|---|---|---|
action | string | search, hashtag, profile or post | profile |
maxItems | integer | Max posts to store (1–1000) | 50 |
proxyConfiguration | object | Proxy settings | Apify Proxy |
Per action
| Action | Required | Optional |
|---|---|---|
search | keyword | filter (recent | top) |
hashtag | tag (with or without #) | filter |
profile | username (without @) | includePosts (default true) |
post | postUrl | maxReplies (falls back to maxItems) |
{ "action": "search", "keyword": "artificial intelligence", "filter": "recent", "maxItems": 50 }
{ "action": "profile", "username": "zuck", "includePosts": true, "maxItems": 20 }
{ "action": "post", "postUrl": "https://www.threads.com/@zuck/post/ABC123xyz", "maxReplies": 50 }
Rate limiting
| Field | Type | Description | Default |
|---|---|---|---|
requestDelayMs | integer | Delay before each page load | 1000 |
maxRetries | integer | Retries when a page fails or is rate limited | 3 |
Retries use exponential backoff, and a login wall or rate limit rotates to a fresh session and proxy IP before retrying.
Authentication (optional)
Cookies unlock the location and joinedDate profile fields and help when Threads
shows a login wall. Scraping while logged in carries a risk to that account — use
a throwaway one.
| Field | Type | Description | Default |
|---|---|---|---|
useCookies | boolean | Enable cookie/storage injection | false |
storageState | object | Playwright storageState.json content | – |
To export: log into Threads with Playwright, then
await context.storageState({ path: 'storageState.json' }), and paste the JSON.
If useCookies is on but storageState is empty, the run falls back to no-auth mode
with a warning. Only cookies and localStorage for Threads domains are injected;
sessionStorage is not supported.
Output
Post record
| Field | Type | Description |
|---|---|---|
id | string | Threads short code, unique per post |
url | string | Direct link to the post |
author.username | string | Author's handle |
author.displayName | string | Author's display name |
author.profileUrl | string | Link to the author's profile |
author.avatarUrl | string | Avatar image URL |
author.isVerified | boolean | Whether the author is verified |
content | string | Post text |
timestamp | string | ISO 8601 publish time |
stats.likes | integer | Likes |
stats.replies | integer | Replies |
stats.reposts | integer | Reposts |
stats.shares | integer | Reshares — Threads does not report this for every post |
stats.quotes | integer | Quote-posts (JSON tiers only) |
images | array | Full-resolution image URLs, incl. carousels and link previews |
videos | array | Video URLs |
links | array | External links |
quotedPost | object | Minimal quoted-post info, if present |
source | string | search, hashtag, post, reply, profile, profile_posts |
parentId | string | For replies: the post being replied to |
profile | string | For profile_posts: the profile that was scraped |
scrapeMethod | string | embedded, graphql or dom — see below |
Profile record
Marked with type: "profile".
| Field | Type | Description |
|---|---|---|
username | string | Handle, without @ |
displayName | string | Display name |
profileUrl | string | Profile URL |
avatarUrl | string | Avatar image URL |
bio | string | Bio text |
isVerified | boolean | Verified badge |
followersCount | integer | Followers — exact unless scrapeMethod is dom |
followingCount | integer | Usually absent, see FAQ |
postsCount | integer | Usually absent, see FAQ |
location | string | null | Requires login cookies |
joinedDate | string | null | Requires login cookies |
partial | boolean | True when optional fields are missing |
missingFields | array | Which fields were unavailable |
Posts from the profile page are stored as separate records with
source: "profile_posts".
scrapeMethod — knowing when data degrades
| Value | Meaning |
|---|---|
embedded | Read from the JSON shipped with the page. Exact counts. |
graphql | Read from an intercepted GraphQL response. Exact counts. |
dom | Fallback: parsed from rendered HTML. Counts may be rounded, some fields absent. |
A healthy run is almost entirely embedded/graphql. If you see mostly dom, Threads
has changed its payload and the fallback is carrying the run — please open an issue.
Run summary
Each run writes a RUN_SUMMARY record to the key-value store, linked from the Output
tab: items per source, the engine mix, charged events and the stop reason.
{"totalItems": 13,"posts": 12,"profiles": 1,"bySource": { "search": 3, "hashtag": 3, "profile": 1, "profile_posts": 3, "post": 1, "reply": 2 },"engineMix": { "embedded": 13 },"engineMixPercent": { "embedded": 100 },"stopReason": null}
Data quality
Posts missing content, author or a valid timestamp are dropped rather than stored.
Profiles are kept even when incomplete, but flagged with partial: true and
missingFields.
Batch mode
Process many targets in one run. Batch mode activates when at least one list is non-empty; the single-value fields are then ignored.
{"action": "search","keywords": ["vibe coding", "machine learning"],"usernames": ["zuck", "openai"],"tags": ["AI", "tech"],"postUrls": ["https://www.threads.com/@user/post/ABC123"],"maxItems": 20,"concurrency": 2}
| Field | Type | Description | Default |
|---|---|---|---|
keywords | array | Search keywords | unset |
usernames | array | Usernames, without @ | unset |
tags | array | Hashtags, without # | unset |
postUrls | array | Post URLs | unset |
concurrency | integer | Targets processed in parallel (1–5) | 2 |
maxItems applies per target. Higher concurrency is faster but much more likely
to trip rate limiting.
FAQ
Why do I get fewer results than maxItems?
Threads caps what it serves anonymously — roughly 10 posts for a search and 20 for a
profile, varying by account and query. The Actor delivers everything it can reach;
supplying login cookies raises the ceiling.
Do I need to log in?
No. All four actions work anonymously. Cookies only add location / joinedDate and
help past login walls — and put the account you use at some risk.
Why are followingCount and postsCount empty?
Threads does not send them to anonymous requests at all — the fields are absent from
the payload, not merely hidden. Rather than invent numbers, the Actor lists them in
missingFields.
What happens if Threads shows a login wall? The session and its proxy IP are retired and the request is retried on a fresh identity. If every retry is blocked, the run fails with a status message saying so rather than quietly returning an empty dataset.
How do I avoid rate limiting?
Use residential proxies, raise requestDelayMs, lower concurrency, and leave a few
minutes between runs.
Can I scrape private accounts? No. Only public data. Private accounts, deleted posts and region-restricted content are not accessible.
Why is some post text truncated?
Threads truncates long posts in feeds. Scrape the post URL directly with the post
action to get the full text.
Are usernames with dots supported?
Yes — @user.name works.
Is this legal? The Actor collects only publicly visible data and does not bypass access controls. You are responsible for how you use the data, including compliance with Threads' Terms of Service, applicable copyright, and data-protection law such as GDPR when records concern identifiable people. Cookie authentication is opt-in and may breach Threads' terms for the account used. See the disclaimer below.
Is there an official API? Yes — the Threads API is the better choice for accessing your own account data: higher limits, full history, no blocking. This Actor covers the public data the API does not expose.
Limitations
Honest expectations, so nothing surprises you mid-run:
- Volume is capped by Threads, not by this Actor. Infinite scroll stops serving after a while, so results may be fewer than requested.
- No private, deleted or region-restricted content.
- Rate limiting is real; frequent runs from the same IP will hit it.
- Login walls appear intermittently and are retried on a new session, but a sustained wall will end the run.
followingCount/postsCountare unavailable anonymously.- Threads can change its payload at any time.
scrapeMethodand the weekly smoke test exist so that gets caught quickly.
Development
npm installnpm run lintnpm run buildnpm test # offline: fixtures + local serversnpm run smoke:live # live checks against threads.com
npm run smoke:auth runs the authenticated profile check; supply
THREADS_STORAGE_STATE_PATH=/path/to/storageState.json or
THREADS_STORAGE_STATE_JSON='{"cookies":[...]}'.
Fixtures under test/fixtures/ are generated by node scripts/capture-fixtures.mjs
and must never be hand-edited — the mapper tests assert exact values from them.
Support
Report problems via the Issues tab on the Actor page. Including the run ID and the
RUN_SUMMARY record makes diagnosis much faster.
Changelog
See CHANGELOG.md. Current version: 0.2.
License
Apache License 2.0 — see LICENSE.
Disclaimer
This tool collects publicly available data for research and analysis. You are responsible for using it in compliance with Threads' Terms of Service and applicable law. The authors accept no responsibility for misuse.
Keywords: Threads scraper, Threads API, Meta Threads, social media scraper, Instagram Threads, Threads posts, Threads data extraction, social media monitoring, Threads automation, influencer discovery, follower count scraper