# TikTok Profile Scraper - Followers, Likes, Videos (`angaba92/tiktok-profile-scraper`) Actor

Scrape public TikTok profiles by username or URL: followers, following, likes, video count, bio, bio link, verified flags, plus recent videos with views, likes and engagement rate. No login or cookies. Export JSON, CSV, Excel or API. $1.50 per 1,000 profiles.

- **URL**: https://apify.com/angaba92/tiktok-profile-scraper.md
- **Developed by:** [Andres Garcia-Baquero Leon](https://apify.com/angaba92) (community)
- **Categories:** Social media, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## TikTok Profile Scraper - Followers, Likes, Bio, Recent Videos

Extract available public TikTok profile fields: **followers, following, total likes, video count, bio, bio link, verified / seller / private flags and account creation date**. Optional enrichment returns a **recent public-video sample** from the profile embed, with available counters and metadata. Missing fields remain null or absent; coverage is reported explicitly. Derived averages describe only the returned sample. Paste usernames or profile URLs, run, and export to JSON, CSV, Excel or via API. No TikTok login, cookies or browser needed.

**Price: $1.50 per 1,000 profiles** ($0.0015 per profile). Profiles that do not exist are never charged.

### Why use it

| Manually | With this Actor |
|---|---|
| Open each profile, copy numbers by hand | Paste 1-1,000 handles, get a table |
| Rounded numbers in the app | Numeric counts as supplied by TikTok; unavailable values stay null |
| No history | Schedule it daily to track growth |

### Output example

Illustrative field excerpt, not a current live snapshot or a guarantee of field coverage.

```json
{
  "username": "nasa",
  "nickname": "NASA",
  "userId": "7664638705177150477",
  "profileUrl": "https://www.tiktok.com/@nasa",
  "bio": "Making the seemingly impossible, possible.",
  "bioLink": null,
  "verified": true,
  "privateAccount": false,
  "isOrganization": true,
  "isSeller": false,
  "followers": 1797784,
  "following": 23,
  "likes": 9493969,
  "videos": 46,
  "avgLikesPerVideo": 206391,
  "accountCreatedAt": "2026-07-20T...",
  "scrapedAt": "2026-09-24T..."
}
```

### Fields

| Field | Type | Description |
|---|---|---|
| username | string | TikTok handle |
| nickname | string | Display name |
| userId / secUid | string | Stable TikTok IDs (survive handle changes) |
| bio / bioLink | string | Profile description and link in bio |
| verified | boolean | Blue check |
| privateAccount | boolean | Private profile |
| isOrganization / isSeller | boolean | Business or TikTok Shop seller account |
| followers / following | integer or null | Counts as supplied by TikTok |
| likes | integer | Total likes received |
| videos | integer or null | Profile video count as supplied by TikTok |
| avgLikesPerVideo | integer | likes / videos (simple engagement proxy) |
| avatarUrl | string | Profile picture (temporary signed URL) |
| accountCreatedAt | string | ISO date when available |
| recentVideos | array | Recent embed sample (not guaranteed to contain the newest uploads): videoId, url, description, createdAt, views, likes, comments, shares, saves, durationSec, music, hashtags |
| recentVideosCount | integer | Videos returned for the profile |
| recentAvgViews | integer or null | Average over returned videos with a view count |
| recentEngagementRate | number or null | Sum of likes / sum of views over returned videos with both likes and nonzero views |
| lastPostAt | string or null | Newest timestamp inferred from IDs in the returned sample, not necessarily the account's latest post |
| videosStatus | string | ok, partial, none\_public, skipped or unavailable (see coverage below) |
| videosError | string or null | Reason for reduced video coverage |
| recentVideos\[].statsComplete | boolean | True only when views, likes, comments, shares and saves are all present; zero counts are valid |
| recentVideos\[].statsMissingFields | array | Names of missing counters |
| recentVideos\[].statsStatus | string | ok, partial, unavailable or skipped (videoStats=false) |

### How to use

1. Add usernames (`nasa`, `@nasa`) or URLs (`https://www.tiktok.com/@nasa`).
2. Click **Start**.
3. Open the **Profiles** table.
4. Export to CSV, JSON, Excel, or pull via the Apify API.

### Input

| Field | Description |
|---|---|
| profiles | List of usernames or profile URLs. Only an omitted profiles key runs a demo; explicit empty/null/invalid targets do not. |
| includeVideos | Add recent videos (default true). Same price per profile. |
| videoStats | Attempt per-video counters and metadata (default true, slower). Completeness is not guaranteed. |
| maxVideosPerProfile | 1-30 operational cap, default 10. Embed samples have often contained about 10-13 videos; size and recency are source-dependent. |
| maxConcurrency | Parallel requests (default 3). |

```json
{ "profiles": ["nasa", "https://www.tiktok.com/@nba", "@khaby.lame"] }
```

### Use cases

- **Influencer marketing**: vet creators by real recent views, engagement rate and last post date, not only follower counts.
- **Competitor tracking**: schedule daily runs and chart follower growth of brand accounts.
- **Lead lists**: enrich a list of creator handles with bio links and business flags.
- **Research**: build datasets of accounts for social media studies.

### Integrations

Works with Zapier, Make, n8n, Google Sheets, Slack, Airbyte, LangChain and webhooks through the standard Apify integrations. Use Apify Schedules for daily snapshots.

### Pricing

Pay per event: **$0.0015 per profile returned** plus the standard tiny Apify actor-start fee. Not-found, invalid or failed profiles are not charged. Result delivery uses Apify's atomic charged push and honors the result-event budget; the actor-start fee is separate.

### FAQ

**Do I need a TikTok account or cookies?** No. Only public profile data is collected.

**Can I scrape private accounts?** You get the public header (followers, likes, bio); private videos are not accessible.

**Are the numbers exact?** Numeric values are taken from the source payload, not reconstructed from the app display. Missing counters remain null; source accuracy is not independently verified.

**What happens with a wrong username?** A confirmed missing profile is listed under `notFound`; malformed inputs under `invalidInput`; blocked or unparseable profiles under `failed`. No profile event is charged for those entries; the actor-start fee may still apply.

**How many profiles per run?** Hundreds are fine; keep concurrency low for reliability.

**Does it return videos?** It attempts a recent embed sample, often around 10-13 videos, not a full history or a guarantee of the chronologically latest posts. Set `includeVideos=false` for profile-only runs; private profiles also skip video recovery.

### Coverage and run diagnostics

`ok` means the available/requested video sample was recovered, not a complete account history.
`partial` means some requested video data is missing; `unavailable` means the initial video
list could not be recovered. `skipped` means video recovery was disabled (or private on TikTok).
`none_public` requires a recognized empty list/message, never just absent JSON or an unknown layout;
it describes the selected source surface, not proof that the entire account has no public content.
Valid profile/channel metadata is still delivered and charged if optional videos fail.
Missing metadata is not inferred. Video IDs are deduplicated before counting and averaging.

Aliases are deduplicated by stable userId/channelId under a delivery lock before the atomic
result-event charge. Rows without a stable identity are reported as failed rather than charged.
`OUTPUT` contains `requested` (unique normalized targets), `scraped` (accepted charged rows),
`notFound`, `failed`, `invalidInput`, `duplicates` (input and identity aliases with duplicateOf),
`videoIssues`, and `chargeLimitReached`. Invalid-only input writes OUTPUT before raising;
mixed input processes valid targets and records invalid entries without substituting demos.
The default demo is used only when the target key is absent. Reruns are separate runs and can charge again.

### Troubleshooting

| Problem | Solution |
|---|---|
| Profile in `failed` | TikTok temporarily blocked the request; rerun those handles. |
| Profile in `notFound` | Check the spelling; the account may be deleted or banned. |
| videosStatus = unavailable | Video list was temporarily blocked; profile data is still returned. Rerun if needed. |
| Run stopped early | Your max cost per run was reached; raise it. |
| `invalidInput` not empty | Handles may only contain letters, numbers, dots and underscores. |

Only public data is collected. You are responsible for using it in compliance with TikTok's terms and applicable privacy laws (e.g. GDPR).

# Actor input Schema

## `profiles` (type: `array`):

Usernames (nasa, @nasa) or profile URLs (https://www.tiktok.com/@nasa). One profile = one result. Invalid or non-existent profiles are reported in OUTPUT and never charged. Demo targets apply only when this key is absent; explicit empty, null or invalid targets never become demo data. Aliases resolving to the same stable identity are delivered and charged once per run; the actor-start fee is separate.

## `maxConcurrency` (type: `integer`):

Profiles fetched in parallel. Keep low (1-5) for best reliability.

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

Attempt a recent public-video sample from the profile embed. Size and recency are source-dependent, not a full history. Disabled or private profiles report videosStatus=skipped. Same per-profile price even if optional recovery fails.

## `videoStats` (type: `boolean`):

Attempt additional per-video metadata and counters. statsComplete checks views, likes, comments, shares and saves, accepting zero. Missing counters are null and listed in statsMissingFields; disabled recovery sets statsStatus=skipped.

## `maxVideosPerProfile` (type: `integer`):

Operational cap of 1-30 returned videos, default 10. Embed samples have often contained about 10-13; this is not a source-volume or latest-upload guarantee.

## Actor input object example

```json
{
  "profiles": [
    "nasa",
    "khaby.lame",
    "nba"
  ],
  "maxConcurrency": 3,
  "includeVideos": true,
  "videoStats": true,
  "maxVideosPerProfile": 10
}
```

# Actor output Schema

## `results` (type: `string`):

Delivered unique stable-identity rows; optional video coverage and missing values are explicit in each row.

## `summary` (type: `string`):

OUTPUT diagnostic: requested unique normalized targets, scraped accepted charges, notFound, failed, invalidInput, duplicates (input/identity aliases and duplicateOf), videoIssues, chargeLimitReached and error when applicable. Saved before invalid-input errors; actor-start fee is separate.

# 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 = {
    "profiles": [
        "nasa",
        "khaby.lame",
        "nba"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("angaba92/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 = { "profiles": [
        "nasa",
        "khaby.lame",
        "nba",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("angaba92/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 '{
  "profiles": [
    "nasa",
    "khaby.lame",
    "nba"
  ]
}' |
apify call angaba92/tiktok-profile-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,angaba92/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/rq8d56WcuIz0zXJ7F/builds/GJoQoHpTrQPQULyal/openapi.json
