# TikTok Profile Scraper 🎵 Videos, Followers & Engagement (`gatherworks/tiktok-profile-scraper`) Actor

Scrape any TikTok creator profile plus their videos. Followers, likes, views, comments, shares, hashtags, music and engagement rate on every row. Paste handles or profile URLs. Export to JSON, CSV or Excel. No login, no cookies, no API key.

- **URL**: https://apify.com/gatherworks/tiktok-profile-scraper.md
- **Developed by:** [GatherWorks](https://apify.com/gatherworks) (community)
- **Categories:**
- **Stats:** 3 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.30 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## TikTok Profile Scraper 🎵 Videos, Followers & Engagement

**Give it a TikTok handle and get the creator plus their videos — followers,
likes, bio link and verification on a clean row per handle, then every post with
views, shares, hashtags, music and a calculated engagement rate.**

**No account, no cookies, no API key.** Paste handles and run.

***

### 🔍 What is the TikTok Profile Scraper?

Give it handles — `tiktok`, `@tiktok` or a full profile URL, all normalised for
you. Every run returns the **creator profile**:

- 👤 followers, following, total likes, video count, bio and verification
- 🔗 the link in the bio, normalised to a real URL
- 📊 average likes per video and a follower-to-following ratio

…and, in the same run, **each creator's videos**, with views, likes, comments,
shares, saves, a per-video engagement rate, and the hashtags and music lifted out
of each one. Every row costs the same, so there is nothing to weigh up — if you
only want the profiles, switch videos off and stop paying for them.

### ⚡ What can it do?

- **👥 Take many creators at once** and return one dataset covering all of them.
- **🔗 Get the link in the bio** on every creator row, normalised to an absolute
  URL rather than the display text the creator typed.
- **📊 Rank by engagement, not follower count.** Every creator carries an
  average-likes-per-video, and every video carries its own engagement rate.
- **🔢 Filter by views or likes** and ignore the long tail.
- **📅 Filter by date.** Keep only videos inside a window you set.
- **🚧 Cap how far a filter searches**, so a narrow filter stops rather than
  running away with your budget.
- **🔎 Filter before you pay.** A video that does not match is never stored and
  never charged for.
- **📄 Export anywhere.** JSON, CSV, Excel — the dataset is yours.

*The filters and the scan cap decide which videos are kept. Switch videos off
and a run returns creator profiles alone.*

#### 🎯 Engagement rate, calculated for you

Follower counts flatter dead accounts. Every video row carries
`engagementRate` — likes, comments and shares as a percentage of views — and
every creator carries `averageLikesPerVideo`. Those two columns are what
separate a creator worth paying from one with a big number next to their name,
and you get both without exporting to a spreadsheet first.

### ⚡ Speed and accuracy

**Speed.** Measured on real runs: **1 creator with 8 videos in 5 seconds**, and
**3 creators in 3 seconds** with videos switched off.

**Accuracy.** Handles are normalised, so `tiktok`, `@tiktok` and a full profile
URL resolve to the same creator and are collected once. Counts are numbers, not
display text — no `1.2M` strings to parse. Timestamps are ISO 8601. Engagement
rate is computed from the raw counts on the same row, so you can check it.
Hashtags come from the video's own entity list rather than a regex over the
caption. A handle that does not resolve is reported and skipped rather than
returned empty.

### 📊 What data does it return?

**Creator rows** (`rowType: "creator"`) — one per handle, on every run:

| Field | Notes |
|---|---|
| 🏷️ `handle`, `displayName` | Handle and display name |
| 🔗 `profileUrl` | Direct link to the profile |
| 📝 `bioText` | The bio text |
| 🔗 `externalUrl` | The link in the bio, normalised to an absolute URL — `null` when the creator has not set one |
| 🔢 `followersCount`, `followsCount`, `videosCount` | Audience and output, as numbers |
| 📊 `totalLikes`, `averageLikesPerVideo` | Lifetime likes, and likes per video |
| ⚖️ `followerRatio` | Followers divided by following |
| ✅ `isVerified`, 🚩 `isPrivate` | Account state |

**Video rows** (`rowType: "video"`) — the creator's posts, returned by default:

| Field | Notes |
|---|---|
| 📝 `videoCaption`, `captionLength` | The caption and its length |
| 🔗 `videoUrl` | Direct link to the video |
| 📊 `viewsCount`, `likesCount`, `commentsCount`, `sharesCount`, `savesCount` | Engagement, as numbers |
| 📈 `engagementRate`, `engagementTotal` | Engagement as a percentage of views, and as a total |
| ⏱️ `durationSeconds` | Video length |
| 🏷️ `hashtags`, `mentions` | Lifted from the caption's entity list |
| 🎵 `musicTitle`, `musicAuthor` | The sound used |
| 🖼️ `coverUrl`, `playUrl` | Cover image and video file |
| 📅 `publishedAt` | When it was posted, ISO 8601 |

#### 🚫 What it does **not** return

- **Anything from a private account.** Its videos are not publicly visible.
- **Follower lists.** Audience size is a number; the individual followers are not returned.
- **Analytics only the account owner can see** — watch time, audience demographics, traffic sources.
- **Deleted or region-blocked videos.**

### 💰 How much does it cost?

You pay per row, for what the run actually produces — never for runtime, and
never on a subscription.

**💵 $0.0022 per row.** One price for everything the run returns, creator rows
and video rows alike:

| Run | Rows | Cost |
|---|---|---|
| 🟩 100 creators, videos switched off | 100 | 💵 $0.22 |
| 🟨 10 creators + 50 videos each | 510 | 💵 $1.12 |
| 🟧 1 creator + 1,000 videos | 1,001 | 💵 $2.20 |

**A handle that does not resolve is free.** So is a video your filters remove.
**A month you do not run it costs nothing at all** — there is no plan to keep alive.

#### What you get for that

| | Included here |
|---|---|
| 📈 Engagement rate computed per video | ✅ free |
| 📊 Average likes per video, per creator | ✅ free |
| 🏷️ Hashtags and mentions as arrays | ✅ free |
| 🎵 Music title and author | ✅ free |
| 🔎 View, like and date filters | ✅ free |
| 💳 Minimum spend | none |

### 🚀 How do I use it?

1. Add one or more **usernames or profile URLs**.
2. Run as-is — the default returns each creator plus their videos, every row at
   the same rate.
3. Set **maximum videos per creator** to control how many video rows you get, or
   switch **videos** off entirely for profiles alone.
4. Optionally set a views, likes or date filter on the videos.
5. Run, then export the dataset as JSON, CSV or Excel.

### ⚙️ Input

| Field | Notes |
|---|---|
| 👤 `usernames` | Handles, @handles or profile URLs |
| 🎬 `includeVideos` | Collect their videos. **On by default** |
| 🔢 `maxVideosPerCreator` | How many videos to return per creator |
| 🔢 `minViews`, `minLikes` | Keep only videos above these thresholds |
| 📅 `postedAfter`, `postedBefore` | Keep only videos inside this window |
| 🚧 `maxScanned` | How far a filter may search before stopping |

```json
{
  "usernames": ["tiktok", "@khaby.lame"],
  "includeVideos": true,
  "maxVideosPerCreator": 50,
  "minViews": 10000
}
```

### 📄 Output example

A real creator row, captured from a live run:

```json
{
  "rowType": "creator",
  "handle": "gordonramsayofficial",
  "displayName": "Gordon Ramsay",
  "profileUrl": "https://www.tiktok.com/@gordonramsayofficial",
  "bioText": "I cook sometimes too.....",
  "externalUrl": "https://GordonRamsay.com/",
  "followersCount": 40700000,
  "followsCount": 572,
  "videosCount": 709,
  "totalLikes": 729600000,
  "averageLikesPerVideo": 1029055,
  "followerRatio": 71153.85,
  "isVerified": true
}
```

Each creator is then followed by their video rows:

```json
{
  "rowType": "video",
  "videoUrl": "https://www.tiktok.com/@tiktok/video/7679101730352565535",
  "viewsCount": 80757,
  "likesCount": 1888,
  "engagementRate": 3.101,
  "musicTitle": "original sound - tiktok",
  "durationSeconds": 62,
  "authorHandle": "tiktok"
}
```

### ❓ Frequently asked questions

**Do I need a TikTok account?** No. No account, no cookies, no API key.

**What forms of username work?** `tiktok`, `@tiktok`, and
`https://www.tiktok.com/@tiktok` all resolve to the same creator.

**How is engagement rate calculated?** Likes plus comments plus shares, divided
by views, as a percentage. The raw counts are on the same row so you can check it.

**Does it return videos?** Yes, by default. A plain run returns one profile row
per handle followed by that creator's videos, and every row costs the same. Turn
`includeVideos` off if you only want the profiles.

**Can I get the comments on a video?** That is a different job — use the TikTok
Video Scraper, which takes video URLs.

### 🏷️ Pricing

Pay per event — you are charged for output, not for platform time.

| Event | Price | Covers |
|---|---|---|
| 📄 `result` — **primary** | 💵 $0.0022 | Any row returned, creator or video, at one price |
| ▶️ `apify-actor-start` | 💵 $0.00005 | Starting one run, charged once per GB of memory |
| 🔗 Link in bio, follower and like counts | ✅ **free** | On every creator row |
| 📈 Engagement rate on every video | ✅ **free** | Computed on the row |
| 🔎 Filtering and sorting | ✅ **free** | Filtered-out videos are not charged for |
| ❌ Handles that do not resolve | ✅ **free** | Reported, never charged |

**One event, one price.** A creator row and a video row cost exactly the same,
so there is nothing to work out in advance: count the rows you expect and
multiply.

#### 🏅 Rates by tier

The prices above are the standard rate. Higher Apify plans are billed at the
discounted tiers below, automatically:

| Event | FREE | BRONZE | SILVER | GOLD |
|---|---|---|---|---|
| 📄 `result` | 💵 $0.0022 | 💵 $0.0019 | 💵 $0.0016 | 💵 $0.0013 |
| ▶️ `apify-actor-start` | 💵 $0.00005 | 💵 $0.00005 | 💵 $0.00005 | 💵 $0.00005 |

#### 🛑 You are never billed past your limit

Set a maximum charge on the run and the Actor stops when it reaches it, with
every creator collected up to that point complete and saved. Filtered-out videos
are never stored and never billed.

# Actor input Schema

## `usernames` (type: `array`):

One or more TikTok handles. All of these work and are normalised for you: <code>tiktok</code>, <code>@tiktok</code>, or <code>https://www.tiktok.com/@tiktok</code>.

## `includeVideos` (type: `boolean`):

Collect the videos each creator published. <b>On by default</b> — switch it off to return profile rows alone.

## `maxVideosPerCreator` (type: `integer`):

Caps how many videos are returned for each creator. Cost scales linearly with this number.

## `minViews` (type: `integer`):

Keep only videos with at least this many views. Leave empty for no minimum.

## `minLikes` (type: `integer`):

Keep only videos with at least this many likes.

## `postedAfter` (type: `string`):

Keep only videos published on or after this date.

## `postedBefore` (type: `string`):

Keep only videos published on or before this date.

## `maxScanned` (type: `integer`):

Safety cap on how many videos are examined while filtering, so a narrow filter cannot run away with your budget.

## Actor input object example

```json
{
  "usernames": [
    "tiktok",
    "@khaby.lame"
  ],
  "includeVideos": true,
  "maxVideosPerCreator": 50,
  "maxScanned": 1000
}
```

# Actor output Schema

## `creators` (type: `string`):

One row per creator, plus a row for each of their videos with views, likes and engagement rate.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "usernames": [
        "tiktok",
        "@khaby.lame"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("gatherworks/tiktok-profile-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "usernames": [
        "tiktok",
        "@khaby.lame",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("gatherworks/tiktok-profile-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "usernames": [
    "tiktok",
    "@khaby.lame"
  ]
}' |
apify call gatherworks/tiktok-profile-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gatherworks/tiktok-profile-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/in9PHEE7u4atrUkAk/builds/bmfoz9SmRcus92Srg/openapi.json
