TikTok Scraper — Profiles, Videos, Hashtags, Search avatar

TikTok Scraper — Profiles, Videos, Hashtags, Search

Pricing

from $0.40 / 1,000 results

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

from $0.40 / 1,000 results

Rating

0.0

(0)

Developer

Coor Yu

Coor Yu

Maintained by Community

Actor stats

1

Bookmarked

8

Total users

5

Monthly active users

a day 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. Optional user-selected residential proxy support. 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)
ProxyDefault Apify Proxy pool; explicit residential choice is honoredAlways residential or hidden fallback
Asset downloadOptional MP4 + cover JPG → Key-Value StoreNot 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 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 1-2 and keep input batches smaller.
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.