TikTok Scraper — Profiles, Hashtags, Search & Videos avatar

TikTok Scraper — Profiles, Hashtags, Search & Videos

Pricing

from $0.001 / actor start

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

TikTok Scraper — Profiles, Hashtags, Search & Videos

Scrape TikTok profiles, hashtags, search & video URLs. Returns enriched post, video and music metadata plus engagement stats, with optional media downloads.

Pricing

from $0.001 / actor start

Rating

0.0

(0)

Developer

API HEROS

API HEROS

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

13 hours ago

Last modified

Share

TikTok Scraper Apify Actor

A production-ready Apify Actor for scraping TikTok profiles, hashtags, search results, and direct video URLs using Python, Playwright, Apify SDK, aiohttp, and Pydantic.

The Actor extracts structured TikTok data, supports optional media downloads to Apify Key-Value Store, uses proxy rotation and rate limiting, returns structured error items, and includes an MCP-compatible tool handler.


Features

  • Scrape TikTok profiles by username or profile URL
  • Scrape hashtags by name or hashtag URL
  • Scrape TikTok search results
  • Scrape direct TikTok video URLs
  • Scrape profile sections:
    • videos
    • reposts
    • liked
  • Support profile sorting:
    • latest
    • popular
  • Extract structured metadata:
    • video text
    • creation time
    • author metadata
    • music metadata
    • video metadata
    • hashtags
    • engagement stats
  • Optional media downloads:
    • videos
    • covers
    • subtitles
    • avatars
  • Store downloaded media in Apify Key-Value Store
  • Inject public download URLs back into dataset items
  • Structured error items for failed pages
  • Apify Proxy integration
  • Proxy rotation per browsing context
  • Async Playwright scraping
  • Anti-detection helpers
  • Rate limiting and retry backoff
  • MCP tool handler for external tool-based integrations
  • Optional webhook trigger at the end of the run

Project Structure

tiktok-scraper/
├── .actor/
│ ├── actor.json
│ └── input_schema.json
├── src/
│ ├── main.py
│ ├── tiktok_scraper.py
│ ├── models.py
│ ├── utils.py
│ ├── rate_limiter.py
│ ├── media_downloader.py
│ └── mcp_handler.py
├── Dockerfile
├── requirements.txt
└── README.md

Technology Stack

  • Python 3.13
  • Apify SDK for Python
  • Playwright
  • Chromium
  • aiohttp
  • Pydantic v2
  • Docker
  • Apify Platform

Base Docker image:

apify/actor-python-playwright:3.13

Installation

1. Clone or create the project

mkdir -p tiktok-scraper/.actor tiktok-scraper/src
cd tiktok-scraper

Place all generated files into the matching paths.

2. Install dependencies

$pip install -r requirements.txt

3. Install Playwright browsers

$playwright install chromium

On some systems, you may also need:

$playwright install-deps chromium

Running Locally with Apify CLI

Install the Apify CLI:

$npm install -g apify-cli

Login:

$apify login

Run the Actor locally:

$apify run

The Actor will use the local Apify storage directory by default.


Running with Docker

Build the image:

apify_api_whqaEDGwEZwot7xT2TbfJrDzHc4e684fSzez

$docker build -t tiktok-scraper .

Run the image:

docker run --rm \
-e ACTOR_TOKEN=$ACTOR_TOKEN \
tiktok-scraper

For full local development, using the Apify CLI is usually easier:

$apify run

Input Configuration

The Actor input is defined in:

.actor/input_schema.json

Example Input

{
"profiles": [
"@tiktok"
],
"hashtags": [
"fyp"
],
"search": [
"dance challenge",
"user:comedian"
],
"startUrls": [
"https://www.tiktok.com/@tiktok/video/1234567890123456789"
],
"resultsPerPage": 20,
"profileScrapeSections": [
"videos",
"reposts",
"liked"
],
"profileSorting": "latest",
"shouldDownloadVideos": false,
"shouldDownloadCovers": false,
"shouldDownloadSubtitles": false,
"shouldDownloadAvatars": false,
"excludePinnedPosts": false,
"proxyCountryCode": "None",
"maxProfilesPerQuery": 10,
"maxConcurrency": 5,
"maxRequestRetries": 3,
"webhookUrl": null
}

Input Fields

FieldTypeDefaultDescription
profilesstring[][]TikTok usernames or profile URLs.
hashtagsstring[][]Hashtag names or hashtag URLs.
searchstring[][]Search queries or TikTok search URLs. Use user:query to search profiles.
startUrlsstring[][]Direct TikTok URLs.
resultsPerPageinteger20Maximum number of items per scraping task.
profileScrapeSectionsstring[]["videos"]Profile tabs to scrape: videos, reposts, liked.
profileSortingstringlatestProfile sorting: latest or popular.
shouldDownloadVideosbooleanfalseDownload videos to Key-Value Store.
shouldDownloadCoversbooleanfalseDownload cover images.
shouldDownloadSubtitlesbooleanfalseDownload subtitle files when available.
shouldDownloadAvatarsbooleanfalseDownload author avatars.
excludePinnedPostsbooleanfalseExclude pinned posts from profile results.
proxyCountryCodestring"None"Apify Proxy country code. Use "None" for no country preference.
maxProfilesPerQueryinteger10Maximum profiles scraped from a profile search query.
maxConcurrencyinteger5Maximum concurrent media downloads and internal operations.
maxRequestRetriesinteger3Maximum navigation retries.
webhookUrlstring | nullnullOptional webhook URL triggered at the end of the run.

Profile Input Examples

Username:

{
"profiles": ["@tiktok"]
}

Without @:

{
"profiles": ["tiktok"]
}

Profile URL:

{
"profiles": ["https://www.tiktok.com/@tiktok"]
}

Profile liked videos:

{
"profiles": ["https://www.tiktok.com/@tiktok/liked"]
}

Multiple profile sections:

{
"profiles": ["@tiktok"],
"profileScrapeSections": ["videos", "reposts", "liked"]
}

Hashtag Input Examples

Hashtag name:

{
"hashtags": ["fyp"]
}

With #:

{
"hashtags": ["#fyp"]
}

Hashtag URL:

{
"hashtags": ["https://www.tiktok.com/tag/fyp"]
}

Search Input Examples

Normal video search:

{
"search": ["dance challenge"]
}

Profile search:

{
"search": ["user:comedian"]
}

Direct TikTok search URL:

{
"search": ["https://www.tiktok.com/search?q=dance"]
}

Direct TikTok user search URL:

{
"search": ["https://www.tiktok.com/search/user?q=comedian"]
}

When using profile search, the Actor collects up to maxProfilesPerQuery profile URLs and scrapes them.


Start URL Examples

Video URL:

{
"startUrls": [
"https://www.tiktok.com/@username/video/1234567890123456789"
]
}

Profile URL:

{
"startUrls": [
"https://www.tiktok.com/@username"
]
}

Hashtag URL:

{
"startUrls": [
"https://www.tiktok.com/tag/fyp"
]
}

Search URL:

{
"startUrls": [
"https://www.tiktok.com/search?q=dance"
]
}

Output Dataset

Successful items are pushed to the default Apify dataset.

Example Output Item

{
"id": "1234567890123456789",
"text": "Example video description #fyp",
"textLanguage": "en",
"createTimeISO": "2026-01-01T12:00:00+00:00",
"isAd": false,
"isPinned": false,
"authorMeta": {
"id": "1234567890",
"name": "username",
"profileUrl": "https://www.tiktok.com/@username",
"nickName": "User Name",
"verified": false,
"signature": "Example bio",
"bioLink": "https://example.com",
"avatar": "https://example.com/avatar.jpg",
"fans": 100000,
"following": 100,
"heart": 5000000,
"video": 250
},
"musicMeta": {
"musicName": "Original Sound",
"musicAuthor": "username",
"musicOriginal": true,
"playUrl": "https://example.com/music.mp3",
"musicId": "987654321"
},
"videoMeta": {
"height": 1024,
"width": 576,
"duration": 15,
"coverUrl": "https://example.com/cover.jpg",
"originalCoverUrl": "https://example.com/original-cover.jpg",
"definition": "720p",
"format": "mp4",
"subtitleLinks": [],
"playUrl": "https://example.com/video.mp4"
},
"diggCount": 1000,
"shareCount": 200,
"playCount": 50000,
"collectCount": 300,
"commentCount": 150,
"hashtags": [
{
"id": "123",
"name": "fyp",
"title": "fyp"
}
],
"webVideoUrl": "https://www.tiktok.com/@username/video/1234567890123456789",
"input": "@username",
"searchQuery": null
}

Downloaded Media Fields

When media downloads are enabled, the Actor adds download URLs back into the dataset item.

Video Download

Enabled by:

{
"shouldDownloadVideos": true
}

Added field:

{
"videoMeta": {
"downloadedUrl": "https://api.apify.com/v2/key-value-stores/STORE_ID/records/videos/example-video.mp4"
}
}

Cover Download

Enabled by:

{
"shouldDownloadCovers": true
}

Added field:

{
"videoMeta": {
"downloadedCoverUrl": "https://api.apify.com/v2/key-value-stores/STORE_ID/records/covers/example-cover.jpg"
}
}

Avatar Download

Enabled by:

{
"shouldDownloadAvatars": true
}

Added field:

{
"authorMeta": {
"downloadedAvatarUrl": "https://api.apify.com/v2/key-value-stores/STORE_ID/records/avatars/example-avatar.jpg"
}
}

Subtitle Download

Enabled by:

{
"shouldDownloadSubtitles": true
}

Added field:

{
"videoMeta": {
"downloadedSubtitleLinks": [
{
"originalUrl": "https://example.com/subtitle.vtt",
"downloadedUrl": "https://api.apify.com/v2/key-value-stores/STORE_ID/records/subtitles/example-subtitle.vtt",
"language": "en"
}
]
}
}

Error Items

If scraping fails, the Actor pushes a structured error item to the dataset.

Example Error Item

{
"url": "https://www.tiktok.com/@privateuser",
"input": "@privateuser",
"error": "The profile is private.",
"errorCode": "PROFILE_PRIVATE"
}

Error Codes

Error CodeMeaning
NOT_FOUNDThe requested page or entity was not found.
POST_NOT_FOUND_OR_PRIVATEThe post is not found, private, or deleted.
PROFILE_PRIVATEThe profile is private.
PROFILE_EMPTYThe profile has no public posts.
SEARCH_QUERY_NOT_FOUNDThe search query did not return results.
INVALID_URLSThe URL is invalid or unsupported.
CAPTCHA_CHALLENGETikTok presented a captcha challenge.
UNKNOWN_ERRORAn unexpected scraping error occurred.

Proxy Configuration

The Actor supports Apify Proxy.

By default, it attempts to create a proxy configuration using:

{
"proxyCountryCode": "None"
}

To prefer a specific country:

{
"proxyCountryCode": "US"
}

The Actor creates a new proxy context per scraping task when proxy configuration is available.


Rate Limiting

The Actor includes an asynchronous rate limiter with:

  • minimum delay between requests
  • randomized delay
  • error backoff
  • concurrency control

Default rate limiter settings in src/main.py:

AsyncRateLimiter(
min_delay=1.2,
max_delay=2.8,
max_concurrency=input_model.maxConcurrency,
)

You can adjust these values if needed.


Pay-Per-Event Pricing

The Actor includes Pay-Per-Event pricing placeholders in:

.actor/actor.json

Configured events:

Event NameDescription
scrape_profile_resultCharged for each successfully scraped result item.
download_videoCharged for each successfully downloaded video.

Example pricing placeholder:

{
"pricingModel": "PAY_PER_EVENT",
"pricing": {
"payPerEvent": {
"scrape_profile_result": {
"title": "Scraped TikTok result item",
"unitPrice": 0.0005
},
"download_video": {
"title": "Downloaded TikTok video",
"unitPrice": 0.001
}
}
}
}

Adjust pricing values before publishing.


Webhook Support

You can provide an optional webhookUrl in the input.

{
"webhookUrl": "https://example.com/webhook"
}

At the end of the run, the Actor sends a POST request with a payload similar to:

{
"eventType": "ACTOR.RUN.SUCCEEDED",
"actorRunId": "RUN_ID",
"datasetId": "DATASET_ID",
"keyValueStoreId": "KEY_VALUE_STORE_ID",
"items": 20,
"errors": 1,
"downloads": 5,
"finishedAt": "2026-07-21T12:00:00+00:00"
}

Apify API Usage

After deploying the Actor to Apify, it can be started through the Apify REST API.

Example:

curl -X POST \
"https://api.apify.com/v2/acts/YOUR_USERNAME~tiktok-scraper/runs?token=YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"profiles": ["@tiktok"],
"resultsPerPage": 10
}'

Replace:

  • YOUR_USERNAME with your Apify username
  • YOUR_APIFY_TOKEN with your Apify API token
  • tiktok-scraper with your Actor name if different

MCP Integration

The file src/mcp_handler.py includes an MCP-compatible handler.

Available tools:

scrape_tiktok_profile
scrape_tiktok_hashtag
scrape_tiktok_search
scrape_tiktok_url

Example MCP JSON-RPC Request

{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "scrape_tiktok_profile",
"arguments": {
"profile": "@tiktok",
"resultsPerPage": 5
}
}
}

Example Python Usage

import asyncio
from mcp_handler import MCPHandler
async def main():
handler = MCPHandler()
response = await handler.handle_jsonrpc(
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "scrape_tiktok_profile",
"arguments": {
"profile": "@tiktok",
"resultsPerPage": 5,
},
},
}
)
print(response)
asyncio.run(main())

Output Stored in Key-Value Store

The Actor stores a run summary in the default Key-Value Store under:

OUTPUT

Example:

{
"finishedAt": "2026-07-21T12:00:00+00:00",
"items": 20,
"errors": 1,
"downloads": 5,
"datasetId": "DATASET_ID",
"keyValueStoreId": "KEY_VALUE_STORE_ID",
"actorRunId": "RUN_ID"
}

Downloaded media files are also stored in the default Key-Value Store under prefixes:

videos/
covers/
avatars/
subtitles/

Anti-Detection

The Actor uses several anti-detection helpers:

  • hides navigator.webdriver
  • sets realistic browser headers
  • sets realistic viewport and locale
  • injects stealth initialization scripts
  • uses randomized delays
  • rotates proxy contexts when Apify Proxy is available
  • dismisses common modal overlays where possible

TikTok may still present captchas, login walls, or temporary blocks.


Limitations

TikTok uses aggressive anti-scraping protections. Scraping results may vary depending on:

  • IP quality
  • proxy country
  • TikTok UI changes
  • embedded JSON structure changes
  • account visibility
  • region restrictions
  • captcha challenges
  • login requirements
  • rate limits

Private profiles, deleted videos, unavailable hashtags, and captcha walls may produce structured error items.


Troubleshooting

CAPTCHA_CHALLENGE

TikTok requires captcha verification.

Possible fixes:

  • use better proxies
  • reduce request volume
  • change proxy country
  • retry later
  • use residential proxies if available

PROFILE_PRIVATE

The profile is private.

No public data can be extracted.

PROFILE_EMPTY

The profile exists but no public posts were found.

Possible causes:

  • empty profile
  • region restriction
  • TikTok UI change
  • scraping was blocked

SEARCH_QUERY_NOT_FOUND

The search query returned no extractable results.

Possible fixes:

  • try a different query
  • check whether TikTok search requires login
  • use a different proxy country

INVALID_URLS

The URL is not a supported TikTok URL.

Supported examples:

https://www.tiktok.com/@username
https://www.tiktok.com/@username/video/123
https://www.tiktok.com/tag/fyp
https://www.tiktok.com/search?q=dance

Development

File Responsibilities

FileResponsibility
src/main.pyApify Actor entry point.
src/tiktok_scraper.pyCore Playwright scraping logic.
src/models.pyPydantic input and output models.
src/utils.pyURL normalization, parsing, embedded state extraction, error mapping.
src/rate_limiter.pyAsync rate limiting and backoff.
src/media_downloader.pyMedia downloading to Key-Value Store.
src/mcp_handler.pyMCP tool handling and JSON-RPC routing.

Local Testing Example

Create an input file:

cat > input.json <<'EOF'
{
"profiles": ["@tiktok"],
"resultsPerPage": 5,
"shouldDownloadVideos": false,
"shouldDownloadCovers": false,
"shouldDownloadSubtitles": false,
"shouldDownloadAvatars": false
}
EOF

Run with Apify CLI:

$apify run

Check local storage:

storage/

Docker Build

$docker build -t tiktok-scraper .

Compliance Notice

Use this Actor responsibly.

Before scraping TikTok, make sure you comply with:

  • TikTok Terms of Service
  • applicable privacy laws
  • data protection regulations
  • platform rate limits
  • intellectual property rules
  • local legal requirements

Do not use this Actor to collect private data without authorization or to violate platform policies.


License

Adjust this section according to your project needs.

Example:

MIT License