TikTok Scraper — Profiles, Videos, Hashtags, Search avatar

TikTok Scraper — Profiles, Videos, Hashtags, Search

Pricing

Pay per usage

Go to Apify Store
TikTok Scraper — Profiles, Videos, Hashtags, Search

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

Coor Yu

Maintained by Community

Actor stats

1

Bookmarked

1

Total users

1

Monthly active users

2 days ago

Last modified

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

FeatureThis actorTypical alternatives
Inputsprofiles + hashtags + search + direct URLsprofiles only
Output27 fields (incl. music, hashtags, mentions, downloads)10-15 fields
Pricing$1.20 / 1,000 results (PPR)$1.70 - $2.50 / 1K
Extraction surfaceInline SIGI_STATE + Universal Data (survives redesigns)Private API (breaks weekly)
ProxyDATACENTER default + auto-fallback to RESIDENTIAL if 0 resultsAlways datacenter (gets blocked) or always residential (8× cost)
Asset downloadOptional MP4 + cover JPG → Key-Value StoreNot supported

Smart proxy fallback (saves you money)

PassProxyCostWhen it runs
1DATACENTER (AUTO)$1 / GBAlways
2RESIDENTIAL$8 / GBOnly 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 resultsPerInput to ~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 hashtagsprofiles, 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

EventCharge
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 deps
npm install
# 2) Set an INPUT.json under storage/key_value_stores/default/
mkdir -p storage/key_value_stores/default
cat > 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 once
npm install -g apify-cli
# Login
apify login
# Push from this folder
cd apify-tiktok-scraper
apify 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.comMonetization → set $1.20 / 1,000 results linked to the video-scraped event.


Architecture

src/main.ts ─ Actor.init → input validation → spawn crawler
src/routes.ts ─ Crawlee router: LIST + DETAIL handlers
src/extractors.ts ─ SIGI_STATE parser + auto-scroll harvester
src/utils.ts ─ URL builders, normalisers
src/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:

  1. Navigate the page with Playwright + fingerprint randomisation.
  2. 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.
  3. Map each item to our canonical schema.
  4. 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

SymptomFix
SIGI_STATE not foundTikTok occasionally serves an interstitial. Increase requestTimeoutSecs to 120.
Empty results on hashtagsTikTok geo-filters tags; set apifyProxyCountry: "US".
429 / 403 burstsLower maxConcurrency to 2; the residential pool will rotate.
Slow scrolling on huge profilesSet 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.