TikTok Scraper — Profiles, Videos, Hashtags, Search
Pricing
Pay per usage
TikTok Scraper — Profiles, Videos, Hashtags, Search
Scrape TikTok profiles, videos, hashtags, and search results. Returns structured JSON with video metadata, engagement stats, music, hashtags, mentions, and optional MP4 / cover downloads. Pay-per-result pricing ($1.20 / 1K). Works as an Apify MCP tool callable by Claude / Cursor / ChatGPT.
Pricing
Pay per usage
Rating
0.0
(0)
Developer
Coor Yu
Maintained by CommunityActor stats
1
Bookmarked
1
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
TikTok Scraper · Apify Actor
Scrape TikTok profiles, videos, hashtags, and search results at scale. Returns structured JSON with full video metadata, engagement stats, music, hashtags, mentions, and optional MP4 / cover downloads.
Pay-per-result pricing. Residential-proxy ready. Built with TypeScript + Crawlee + Playwright on the official Apify Node 20 base image.
Why this actor
| Feature | This actor | Typical alternatives |
|---|---|---|
| Inputs | profiles + hashtags + search + direct URLs | profiles only |
| Output | 27 fields (incl. music, hashtags, mentions, downloads) | 10-15 fields |
| Pricing | $1.20 / 1,000 results (PPR) | $1.70 - $2.50 / 1K |
| Extraction surface | Inline SIGI_STATE + Universal Data (survives redesigns) | Private API (breaks weekly) |
| Proxy | DATACENTER default + auto-fallback to RESIDENTIAL if 0 results | Always datacenter (gets blocked) or always residential (8× cost) |
| Asset download | Optional MP4 + cover JPG → Key-Value Store | Not supported |
Smart proxy fallback (saves you money)
| Pass | Proxy | Cost | When it runs |
|---|---|---|---|
| 1 | DATACENTER (AUTO) | $1 / GB | Always |
| 2 | RESIDENTIAL | $8 / GB | Only for the specific seeds that returned 0 results in pass 1, and only if fallbackToResidential is on (default) |
So 80–90% of seeds run on cheap datacenter IPs, and only the handful TikTok happens to block on this hour get the expensive residential treatment. You can flip fallbackToResidential: false for a hard cost cap.
Input
All fields optional — provide at least one of profiles, hashtags, searchKeywords, or videoUrls.
{"profiles": ["khaby.lame", "https://www.tiktok.com/@charlidamelio"],"hashtags": ["fyp", "comedy"],"searchKeywords": ["summer fashion 2026"],"videoUrls": ["https://www.tiktok.com/@khaby.lame/video/7321234567890123456"],"resultsPerInput": 50,"hashtagPostedAfter": "2026-05-01","hashtagPostedBefore": "2026-05-19","shouldDownloadVideos": false,"shouldDownloadCovers": false,"proxy": {"useApifyProxy": true,"apifyProxyGroups": ["AUTO"]},"fallbackToResidential": true,"maxConcurrency": 5}
Full schema: see ./.actor/input_schema.json.
Hashtag time window
hashtagPostedAfter / hashtagPostedBefore filter hashtag-sourced videos by their TikTok createTime. Accepts ISO 8601:
- Date-only —
"2026-05-01"— anchored to 00:00:00 UTC. - Date-time —
"2026-05-01T14:30:00Z"— used as-is.
The filter runs inside the XHR sniffer, so out-of-window videos are dropped before billing — you only pay for what you keep.
⚠️ Hashtag feeds aren't strictly chronological — TikTok mixes viral-old and fresh content. If you need 50 recent results, set
resultsPerInputto ~150-250 so the filter has enough room to find them. The scroller is given 2× patience (stagnant-threshold 8 vs the default 4) when the filter is active.
The filter is scoped to hashtags — profiles, searchKeywords, and videoUrls ignore both fields.
Output (one row per video)
{"videoId": "7321234567890123456","webVideoUrl": "https://www.tiktok.com/@khaby.lame/video/7321234567890123456","text": "When you finally fix the bug 🐛","createTimeISO": "2026-05-12T14:32:11.000Z","duration": 18,"videoDownloadUrl": "https://v16-webapp-prime.tiktok.com/.../play.mp4","coverUrl": "https://p16-sign-va.tiktokcdn.com/.../cover.jpeg","author": {"id": "6748834234567","uniqueId": "khaby.lame","nickname": "Khaby Lame","verified": true,"avatar": "https://...","followers": 162500000},"music": {"id": "7012345678","title": "Original Sound","author": "khaby.lame","playUrl": "https://..."},"stats": {"playCount": 12500000,"diggCount": 1850000,"commentCount": 23400,"shareCount": 156000,"collectCount": 78000},"hashtags": ["fyp", "comedy"],"mentions": [],"sourceType": "profile","sourceInput": "khaby.lame","scrapedAt": "2026-05-19T07:23:11.234Z"}
If shouldDownloadVideos / shouldDownloadCovers is enabled, additional fields
storedVideoKey / storedCoverKey point to entries in the run's Key-Value Store.
Pricing — Pay per result
| Event | Charge |
|---|---|
video-scraped | $1.20 / 1,000 videos |
Failed requests and retries are free. You only pay for rows that land in the dataset.
Local development
# 1) Install depsnpm install# 2) Set an INPUT.json under storage/key_value_stores/default/mkdir -p storage/key_value_stores/defaultcat > storage/key_value_stores/default/INPUT.json <<'JSON'{"profiles": ["khaby.lame"],"resultsPerInput": 10}JSON# 3) Run locally (uses Apify CLI if installed, otherwise raw node)npm start
Output lands in storage/datasets/default/*.json.
Deploy to Apify
# Install Apify CLI oncenpm install -g apify-cli# Loginapify login# Push from this foldercd apify-tiktok-scraperapify push
The actor builds on Apify's servers; first build takes 3-5 minutes.
To enable pay-per-result pricing after publishing, open the actor on
apify.com → Monetization → set $1.20 / 1,000 results
linked to the video-scraped event.
Architecture
src/main.ts ─ Actor.init → input validation → spawn crawlersrc/routes.ts ─ Crawlee router: LIST + DETAIL handlerssrc/extractors.ts ─ SIGI_STATE parser + auto-scroll harvestersrc/utils.ts ─ URL builders, normaliserssrc/types.ts ─ TypeScript types for input/output/internal shapes.actor/actor.json ─ Actor manifest.actor/input_schema.json ─ UI form schema.actor/dataset_schema.json ─ Dataset views (powers Apify Store preview table).actor/Dockerfile ─ Build (apify/actor-node-playwright-chrome:20)
Extraction strategy:
- Navigate the page with Playwright + fingerprint randomisation.
- Read the embedded
SIGI_STATE(legacy) or__UNIVERSAL_DATA_FOR_REHYDRATION__(modern Universal Data) JSON blob — this is TikTok's own server-rendered Redux state. - Map each item to our canonical schema.
- Auto-scroll until limit hit or 3 stagnant iterations detected.
Why this approach: TikTok's private REST endpoints (/api/post/item_list, etc.) rotate
signatures roughly weekly. The inline state blob is what TikTok's own UI consumes on
first paint — far more stable across redesigns.
Using this actor via MCP (Claude / Cursor / ChatGPT)
The actor is auto-exposed as a tool by Apify MCP Server at https://mcp.apify.com/.
Apify reads input_schema.json to build the tool definition and dataset_schema.json
to advertise the output shape so LLMs know what fields to expect.
Cursor / Claude Desktop config
{"mcpServers": {"apify": {"url": "https://mcp.apify.com/","transport": "streamable-http","headers": {"Authorization": "Bearer apify_api_xxxxxxxx"}}}}
Then prompt your agent:
"Use the tiktok-scraper actor to fetch the latest 30 videos from @khaby.lame and the top 50 results for hashtag #fyp."
Pair this with the tiktok-comments-scraper actor for a complete pipeline:
profile → videos → comments → sentiment analysis.
Reliability tips
| Symptom | Fix |
|---|---|
SIGI_STATE not found | TikTok occasionally serves an interstitial. Increase requestTimeoutSecs to 120. |
| Empty results on hashtags | TikTok geo-filters tags; set apifyProxyCountry: "US". |
| 429 / 403 bursts | Lower maxConcurrency to 2; the residential pool will rotate. |
| Slow scrolling on huge profiles | Set resultsPerInput to a finite cap (e.g. 200). |
License & disclaimer
MIT. Scrape only public data and respect TikTok's terms. You are responsible for compliance with applicable laws and platform policies.