TikTok Scraper — Profiles, Videos, Hashtags, Search
Pricing
from $0.40 / 1,000 results
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
from $0.40 / 1,000 results
Rating
0.0
(0)
Developer
Coor Yu
Maintained by CommunityActor stats
1
Bookmarked
8
Total users
5
Monthly active users
a day 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. Optional user-selected residential proxy support. 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 | Default Apify Proxy pool; explicit residential choice is honored | Always residential or hidden fallback |
| Asset download | Optional MP4 + cover JPG → Key-Value Store | Not supported |
Proxy policy
Default runs use the default Apify Proxy pool, pinned to US unless you choose
another country. The actor never auto-switches to residential IPs. If you
explicitly select RESIDENTIAL or provide custom proxyUrls, the actor uses
that proxy configuration as requested.
The legacy fallbackToResidential input is ignored and does not trigger an
automatic residential fallback.
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,"apifyProxyCountry": "US"},"fallbackToResidential": false,"blockImages": true,"maxConcurrency": 4,"requestTimeoutSecs": 45,"maxAttemptsPerSeed": 2,"maxSearchAttempts": 2}
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 1-2 and keep input batches smaller. |
| 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.