TikTok Scraper — Profiles, Hashtags, Search & Videos
Pricing
from $0.001 / actor start
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
13 hours ago
Last modified
Categories
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:
videosrepostsliked
- Support profile sorting:
latestpopular
- 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/srccd 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
| Field | Type | Default | Description |
|---|---|---|---|
profiles | string[] | [] | TikTok usernames or profile URLs. |
hashtags | string[] | [] | Hashtag names or hashtag URLs. |
search | string[] | [] | Search queries or TikTok search URLs. Use user:query to search profiles. |
startUrls | string[] | [] | Direct TikTok URLs. |
resultsPerPage | integer | 20 | Maximum number of items per scraping task. |
profileScrapeSections | string[] | ["videos"] | Profile tabs to scrape: videos, reposts, liked. |
profileSorting | string | latest | Profile sorting: latest or popular. |
shouldDownloadVideos | boolean | false | Download videos to Key-Value Store. |
shouldDownloadCovers | boolean | false | Download cover images. |
shouldDownloadSubtitles | boolean | false | Download subtitle files when available. |
shouldDownloadAvatars | boolean | false | Download author avatars. |
excludePinnedPosts | boolean | false | Exclude pinned posts from profile results. |
proxyCountryCode | string | "None" | Apify Proxy country code. Use "None" for no country preference. |
maxProfilesPerQuery | integer | 10 | Maximum profiles scraped from a profile search query. |
maxConcurrency | integer | 5 | Maximum concurrent media downloads and internal operations. |
maxRequestRetries | integer | 3 | Maximum navigation retries. |
webhookUrl | string | null | null | Optional 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 Code | Meaning |
|---|---|
NOT_FOUND | The requested page or entity was not found. |
POST_NOT_FOUND_OR_PRIVATE | The post is not found, private, or deleted. |
PROFILE_PRIVATE | The profile is private. |
PROFILE_EMPTY | The profile has no public posts. |
SEARCH_QUERY_NOT_FOUND | The search query did not return results. |
INVALID_URLS | The URL is invalid or unsupported. |
CAPTCHA_CHALLENGE | TikTok presented a captcha challenge. |
UNKNOWN_ERROR | An 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 Name | Description |
|---|---|
scrape_profile_result | Charged for each successfully scraped result item. |
download_video | Charged 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_USERNAMEwith your Apify usernameYOUR_APIFY_TOKENwith your Apify API tokentiktok-scraperwith your Actor name if different
MCP Integration
The file src/mcp_handler.py includes an MCP-compatible handler.
Available tools:
scrape_tiktok_profilescrape_tiktok_hashtagscrape_tiktok_searchscrape_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 asynciofrom mcp_handler import MCPHandlerasync 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/@usernamehttps://www.tiktok.com/@username/video/123https://www.tiktok.com/tag/fyphttps://www.tiktok.com/search?q=dance
Development
File Responsibilities
| File | Responsibility |
|---|---|
src/main.py | Apify Actor entry point. |
src/tiktok_scraper.py | Core Playwright scraping logic. |
src/models.py | Pydantic input and output models. |
src/utils.py | URL normalization, parsing, embedded state extraction, error mapping. |
src/rate_limiter.py | Async rate limiting and backoff. |
src/media_downloader.py | Media downloading to Key-Value Store. |
src/mcp_handler.py | MCP 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