# TikTok Scraper (`badr_xba/tiktok-scraper`) Actor

TikTok posts without an API key: views, likes, comments, shares, saves, caption, hashtags, sound and author. Optional MP4, cover, avatar and subtitle downloads, since TikTok's own links expire. Works from post URLs, hashtags and related videos. Example: hashtag 'cooking', 20 videos.

- **URL**: https://apify.com/badr\_xba/tiktok-scraper.md
- **Developed by:** [Badr Bouargan](https://apify.com/badr_xba) (community)
- **Categories:** Social media, Videos
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 result scrapeds

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?

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 Scraper – Posts, Hashtags, Profiles & Stats

![TikTok Scraper](https://api.apify.com/v2/key-value-stores/paDGjcmD1ZMsRJLZS/records/tiktok-banner.png?signature=11eu1YhNaSAe6wwU19iRW)

Extract **TikTok posts** with no API key and no account: views, likes, comments, shares, saves, the caption and its hashtags, the sound behind the video, the author and their follower count, plus the video file itself if you want it. Read any post you already have a link to, follow a hashtag, or pull the figures of a profile.

### What you can do with it

- **Measure a hashtag**: the posts published under it, their views and engagement, and how often the whole hashtag has been watched.
- **Track creators**: followers, total likes, number of videos and bio for a list of accounts, run on a schedule to build a history.
- **Study posts you already know**: paste a list of links and get every figure TikTok shows, plus a few it does not display.
- **Build a sound report**: which track a post uses, whether it is original, and who published it.
- **Archive content**: download the MP4, the cover image, the author's avatar and the subtitles, because TikTok's own media links expire within days.
- **Widen a sample**: from one post, collect the fifteen or so videos TikTok recommends next to it.

### What this Actor can and cannot read — please read this first

TikTok is the strictest site of its kind, and the honest picture matters more than a long feature list. Everything below was measured against the live site, not assumed.

| What you want | Does it work? | How |
| --- | --- | --- |
| Every figure of a post you have the link to | **Yes, always** | One plain request to the post's page |
| The figures of a hashtag (views, ID) | **Yes** | TikTok's own hashtag endpoint |
| The posts published under a hashtag | **Yes** | A real browser loads the hashtag page |
| The figures of a profile (followers, likes, bio, videos) | **Yes, always** | One plain request to the profile page |
| Videos recommended next to a post | **Yes** | A real browser loads the post and starts it playing |
| **The list of a profile's own videos** | **Residential addresses only** | TikTok answers this one request with an empty body from every datacenter address tested |
| Keyword search | **No, deliberately** | TikTok's `robots.txt` disallows `/search` to every crawler. Hashtags cover the same ground and are allowed |
| Comments, followers and following lists | **No** | TikTok serves these to signed-in sessions only |

The one limit worth planning around is the profile feed. It was tested from four different datacenter addresses, several times each, loading the page directly and by clicking through from a post like a visitor would: TikTok replies `HTTP 200` with a body of zero bytes every single time. The account's own figures — followers, likes, bio, number of videos — are in the page itself and always come back.

So when a profile feed comes back empty, the Actor saves a row with the code `PROFILE_FEED_UNAVAILABLE`, and it does not retry: retrying costs you money and changes nothing. It says so in the run log too. Nothing is ever silently dropped.

### Why this Actor

- **No API key, no account, no cookies to paste.** It reads what TikTok serves to any visitor.
- **No forged signatures.** TikTok signs the calls behind its video lists. Instead of faking those signatures — which breaks every few weeks — a real browser loads the page and the Actor reads the answers TikTok's own code receives. That is why it keeps working.
- **It retries from another address.** TikTok accepts some addresses and quietly refuses others. A page that comes back empty is tried again from somewhere else, up to three times, before it is reported as a failure.
- **Failures become rows.** Every input that could not be read is saved with an error code, so a spreadsheet tells you what is missing instead of hiding it.
- **The cheap path stays cheap.** Post URLs, hashtag figures and profile figures never start a browser.

### Example output

![Real output](https://api.apify.com/v2/key-value-stores/paDGjcmD1ZMsRJLZS/records/tiktok-output.png?signature=1PvNc0jGid5uurlYt4ejj)

### How it works

1. Post URLs are read one by one from their own page, which carries every field inside it.
2. Hashtags and profiles get their counters the same cheap way.
3. If you asked for video lists, Chromium opens the hashtag, profile or post page, scrolls it, and the Actor keeps the posts TikTok loads into it. On a post page it also starts the video playing, because TikTok only asks for its recommendations once the post is running.
4. Posts are de-duplicated on their ID, filtered on date and popularity, and saved.
5. If you asked for files, each post is re-read over the session that will download them — TikTok signs its media links for one session only.

### Step-by-step tutorial

1. Paste a few links in **Post URLs**, or type a hashtag in **Hashtags**, or an account in **Profiles**. Any combination works.
2. Set **Videos per hashtag or profile** — 20 is a sensible first run.
3. Optional: **Published after** (`2026-01-01`, or a phrase such as `7 days`), **Minimum views**, **Minimum likes**.
4. Optional: switch on the files you want under **Files to download**.
5. Leave the proxy on Apify Proxy. If you want a profile's video list or related videos, open the proxy settings and pick the **RESIDENTIAL** group.
6. Press **Start**, then export as JSON, CSV, Excel or HTML.

### Input example

```json
{
  "postURLs": [
    "https://www.tiktok.com/@khaby.lame/video/7425321081298341152"
  ],
  "hashtags": ["cooking", "parisfrance"],
  "profiles": ["nasa"],
  "resultsPerPage": 20,
  "scrapeRelatedVideos": false,
  "oldestPostDate": "90 days",
  "minPlayCount": 10000,
  "shouldDownloadCovers": true
}
```

### Output example

One post, shortened:

```json
{
  "id": "7425321081298341152",
  "text": "Bring it on #learnfromkhaby #comedy",
  "textLanguage": "en",
  "createTimeISO": "2024-10-13T17:58:13.000Z",
  "playCount": 21100000,
  "diggCount": 859900,
  "commentCount": 7304,
  "shareCount": 21700,
  "collectCount": 43045,
  "repostCount": 0,
  "isAd": false,
  "isSlideshow": false,
  "locationCreated": "US",
  "hashtags": [
    { "id": "1697657584273413", "name": "learnfromkhaby" },
    { "id": "1606334392378370", "name": "comedy" }
  ],
  "authorMeta": {
    "id": "127905465618821121",
    "name": "khaby.lame",
    "nickName": "Khabane lame",
    "verified": true,
    "fans": 163000000,
    "heart": 2700000000,
    "video": 1353,
    "profileUrl": "https://www.tiktok.com/@khaby.lame"
  },
  "musicMeta": {
    "musicId": "7425321107177491233",
    "musicName": "original sound",
    "musicAuthor": "Khabane lame",
    "musicOriginal": true,
    "duration": 21
  },
  "videoMeta": {
    "height": 1024,
    "width": 576,
    "duration": 21,
    "definition": "540p",
    "format": "mp4",
    "coverUrl": "https://p16-common-sign.tiktokcdn-us.com/…",
    "subtitleLinks": []
  },
  "webVideoUrl": "https://www.tiktok.com/@khaby.lame/video/7425321081298341152",
  "input": "https://www.tiktok.com/@khaby.lame/video/7425321081298341152"
}
```

### Output fields

#### A post

| Field | What it holds |
| --- | --- |
| `id`, `webVideoUrl` | The post's ID and its address |
| `text`, `textLanguage` | The caption and the language TikTok detected |
| `createTime`, `createTimeISO` | Publication date, as a timestamp and as a readable date |
| `playCount`, `diggCount`, `commentCount`, `shareCount`, `collectCount`, `repostCount` | Views, likes, comments, shares, saves, reposts |
| `hashtags`, `mentions` | Hashtags with their IDs, and the accounts mentioned |
| `authorMeta` | The author: handle, display name, verification, bio, followers, total likes, number of videos, avatar, profile URL |
| `musicMeta` | The sound: title, author, whether it is original, duration, cover, play URL |
| `videoMeta` | Dimensions, duration, quality, codec, cover images, download address, subtitle tracks |
| `isAd`, `isSponsored`, `isSlideshow`, `isPinned`, `isAigc` | What kind of post it is |
| `duetEnabled`, `stitchEnabled`, `commentsTurnedOff` | What the author allows |
| `labels`, `effectStickers`, `locationCreated` | Topic labels, effects used, and the country TikTok attaches to the post |
| `slideshowImages` | The images of a photo post |
| `videoUrl`, `coverUrl`, `avatarUrl`, `musicCoverUrl`, `subtitleUrls` | Where the files were saved, when downloading is switched on |
| `input`, `searchHashtag`, `searchProfile`, `relatedTo` | Which of your inputs produced this row |

`locationCreated` is only present on posts read from their own URL; TikTok leaves it out of the lists it serves to a hashtag page.

#### A hashtag

`id`, `name`, `viewCount`, `hashtagUrl`. TikTok stopped publishing a video count on this endpoint, so the field is left empty rather than filled with a zero.

#### A profile

`id`, `name`, `nickName`, `verified`, `privateAccount`, `signature`, `avatar`, `fans`, `following`, `heart`, `video`, `friends`, `profileUrl`.

### Error codes

| Code | What happened |
| --- | --- |
| `INVALID_INPUT` | The line is not a TikTok post URL or ID |
| `POST_NOT_FOUND_OR_PRIVATE` | The post is gone, private, or blocked in the region the request came from |
| `POST_SENSITIVE` | TikTok marks the post as age-restricted |
| `NOT_FOUND` | No account or hashtag by that name, after several attempts from different addresses |
| `PROFILE_FEED_UNAVAILABLE` | TikTok returned an empty video list for the profile. Switch to the RESIDENTIAL proxy group |
| `RELATED_UNAVAILABLE` | TikTok recommended nothing next to the post, from any address tried |
| `FEED_UNAVAILABLE` | A video list came back empty from every address tried |
| `BLOCKED` | TikTok refused the page from every address tried |
| `RATE_LIMITED` | TikTok served an empty shell instead of the page |
| `MEDIA_UNAVAILABLE` | A file could not be downloaded — usually an expired link |
| `BROWSER_UNAVAILABLE` | Chromium could not start in the container |

### Proxy

Keep **Apify Proxy** on. The datacenter group is enough for everything except one thing: post URLs, hashtag figures, **hashtag videos**, **related videos** and profile figures all work on it.

Only a profile's own video list needs the **RESIDENTIAL** group. The Actor notices which group you picked: without a residential one it tries each profile feed once and reports the result, instead of billing you for retries that cannot succeed.

### Speed and cost

Measured on real runs, at the default 2 GB of memory:

| Run | Rows | Time | Platform cost |
| --- | --- | --- | --- |
| 2 hashtags + 1 profile, 20 videos each | 19 | 1 min 53 s | $0.014 |
| 2 post URLs + their related videos | 17 | 2 min 20 s | $0.017 |
| 3 post URLs + 2 profiles, no browser, 1 GB | 5 | 1 min 25 s | $0.005 |

That is **$0.70 to $1.30 of platform usage per 1,000 rows**, and it gets cheaper as runs grow: opening the browser is a fixed cost paid once, so twenty hashtags cost barely more than two. Downloading video files is the expensive option — TikTok serves MP4s slowly, roughly five minutes for a single one.

### Frequently asked questions

**Do I need a TikTok account or an API key?**
No. Nothing to create, nothing to paste.

**Why is there no keyword search?**
TikTok's `robots.txt` tells every crawler not to visit `/search`, `/search/video` and `/search/user`. This Actor respects that. Hashtags are explicitly allowed and reach the same content.

**Why did a profile return its followers but no videos?**
TikTok answers the profile's video-list request with an empty body unless the request comes from a residential address — tested from four datacenter addresses, always zero bytes. Pick the RESIDENTIAL proxy group and run it again. The row with the code `PROFILE_FEED_UNAVAILABLE` tells you exactly that, and the Actor does not waste your credit retrying.

**Is there a way to follow a creator without a residential proxy?**
Yes, indirectly. Post URLs always work, so a list of links gives you every figure. And the videos recommended next to one of that creator's posts often include more of their work — switch on **Collect videos related to each post URL**.

**Can I get the comments?**
No. TikTok serves comments to signed-in sessions only, and this Actor does not ask you for an account.

**Why are the downloaded files stored instead of just linked?**
TikTok signs its media links for one session and expires them within days. A link saved in a spreadsheet stops working; a downloaded file does not.

**Can I run it on a schedule?**
Yes. Followers, views and likes change every day, so a daily run on the same hashtags or accounts builds a history you can chart.

### Legal note

This Actor reads only what TikTok publishes to anyone, and it follows TikTok's `robots.txt`. It collects no private data and never signs in. Figures about people — follower counts, captions, handles — are personal data in some jurisdictions: have a lawful basis before you store them, and check TikTok's terms for your own use case.

# Actor input Schema

## `postURLs` (type: `array`):

TikTok post addresses, one per line: https://www.tiktok.com/@nasa/video/1234567890. A bare video ID or a vm.tiktok.com short link works too. This is the fastest and most reliable input — every field comes from the post's own page.

## `hashtags` (type: `array`):

Hashtags to follow, with or without the #: cooking, travel, smallbusiness. You get the hashtag's own figures (views, number of videos) plus the posts published under it.

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

TikTok accounts, as nasa, @nasa or a full profile URL. Account figures — followers, likes, number of videos, bio — always come back. Their video lists need the RESIDENTIAL proxy group; see the README.

## `resultsPerPage` (type: `integer`):

How many posts to collect under each hashtag and for each profile. Post URLs you list yourself are never limited by this.

## `maxItems` (type: `integer`):

A ceiling for the whole run, across every input. Leave at 0 for no ceiling.

## `scrapeHashtagVideos` (type: `boolean`):

On, each hashtag also returns its posts. Off, you only get the hashtag's counters, which is much faster and cheaper.

## `scrapeProfileVideos` (type: `boolean`):

On, each profile also returns its posts. This is the one part of the Actor that TikTok reserves for residential addresses: without that proxy group it is tried once and reported, never retried at your expense. Account figures come back either way.

## `scrapeRelatedVideos` (type: `boolean`):

Adds the posts TikTok recommends next to each post URL you listed — about fifteen per post. A cheap way to widen a sample around a topic, or to find more of a creator's work without a residential proxy.

## `oldestPostDate` (type: `string`):

Keep only posts published after this date. A date such as 2026-01-31, or a phrase such as 7 days.

## `newestPostDate` (type: `string`):

Keep only posts published before this date.

## `minPlayCount` (type: `integer`):

Drop posts watched fewer times than this.

## `minDiggCount` (type: `integer`):

Drop posts with fewer likes than this.

## `shouldDownloadVideos` (type: `boolean`):

Saves each MP4 in the run's storage and adds its address to the row. TikTok's own media links expire within days, so downloading is the only way to keep them. Expect a much longer, heavier run.

## `shouldDownloadCovers` (type: `boolean`):

Saves the thumbnail of each post.

## `shouldDownloadAvatars` (type: `boolean`):

Saves the profile picture of each author, once per account.

## `shouldDownloadMusicCovers` (type: `boolean`):

Saves the artwork of the sound used by each post.

## `shouldDownloadSubtitles` (type: `boolean`):

Saves the subtitle tracks TikTok publishes for a post, when it has any.

## `shouldDownloadSlideshowImages` (type: `boolean`):

Photo posts carry images instead of a video: this saves them, up to twenty per post.

## `proxyConfiguration` (type: `object`):

TikTok is strict about addresses. Datacenter proxies are enough for post URLs, hashtags and account figures. Profile video lists need the RESIDENTIAL group.

## Actor input object example

```json
{
  "postURLs": [
    "https://www.tiktok.com/@khaby.lame/video/7425321081298341152"
  ],
  "hashtags": [
    "cooking"
  ],
  "profiles": [
    "nasa"
  ],
  "resultsPerPage": 20,
  "maxItems": 0,
  "scrapeHashtagVideos": true,
  "scrapeProfileVideos": true,
  "scrapeRelatedVideos": false,
  "shouldDownloadVideos": false,
  "shouldDownloadCovers": false,
  "shouldDownloadAvatars": false,
  "shouldDownloadMusicCovers": false,
  "shouldDownloadSubtitles": false,
  "shouldDownloadSlideshowImages": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

Author, caption, views, likes, comments, shares and publication date.

## `authors` (type: `string`):

Account figures and the sound behind each post.

## `media` (type: `string`):

Video dimensions and the files the run downloaded.

## `problems` (type: `string`):

Every input the run could not read, with its error code.

## `allFields` (type: `string`):

Every field, including hashtags, mentions, labels and video metadata.

## `runStats` (type: `string`):

Posts, profiles and hashtags saved, plus what was filtered or failed.

# 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 = {
    "postURLs": [
        "https://www.tiktok.com/@khaby.lame/video/7425321081298341152"
    ],
    "hashtags": [
        "cooking"
    ],
    "profiles": [
        "nasa"
    ],
    "resultsPerPage": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("badr_xba/tiktok-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 = {
    "postURLs": ["https://www.tiktok.com/@khaby.lame/video/7425321081298341152"],
    "hashtags": ["cooking"],
    "profiles": ["nasa"],
    "resultsPerPage": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("badr_xba/tiktok-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 '{
  "postURLs": [
    "https://www.tiktok.com/@khaby.lame/video/7425321081298341152"
  ],
  "hashtags": [
    "cooking"
  ],
  "profiles": [
    "nasa"
  ],
  "resultsPerPage": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call badr_xba/tiktok-scraper --silent --output-dataset

```

## MCP server setup

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