# TikTok Scraper - Videos by Keyword, Hashtag & Profile (`lanternlab318/tiktok-scraper`) Actor

- **URL**: https://apify.com/lanternlab318/tiktok-scraper.md
- **Developed by:** [Lantern Lab](https://apify.com/lanternlab318) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.49 / 1,000 videos

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 Scraper - Videos by Keyword, Hashtag or Profile

Collect public TikTok videos three ways: by **keyword**, by **hashtag** or from a **profile**. No login, no cookies, no account of your own. The Actor only reads what TikTok shows a signed-out visitor. **$2.49 per 1,000 videos, plus $0.005 per run, and an input that finds nothing costs nothing.** See [Pricing](#pricing).

Input:

```json
{
  "searchQueries": ["pasta recipe"],
  "hashtags": ["cooking"],
  "profiles": ["https://www.tiktok.com/@nasa"],
  "maxResults": 30
}
```

Output - one row per video (`recordType: "video"`), plus one free `input_summary` row per keyword, hashtag or profile. Sample from a real run:

```json
{
  "recordType": "video",
  "platform": "tiktok",
  "videoId": "7700397098994273549",
  "url": "https://www.tiktok.com/@nasa/video/7700397098994273549",
  "description": "NASA 🤝 college football. We heard y’all like a good air show. Roll Tide!",
  "hashtags": [],
  "publishedAt": "2026-10-25T00:33:47+00:00",
  "playCount": 110300,
  "likeCount": 7362,
  "commentCount": 239,
  "shareCount": 229,
  "collectCount": 474,
  "durationSecs": 16,
  "isImagePost": false,
  "coverUrl": "https://p16-common-sign.tiktokcdn.com/...",
  "isAd": false,
  "language": "en",
  "author": { "username": "nasa", "nickname": "NASA", "followerCount": 2100000, "verified": true },
  "music": { "id": "7700397137514793741", "title": "original sound - NASA", "authorName": "NASA", "isOriginal": false },
  "inputType": "profile",
  "input": "nasa",
  "position": 2,
  "collectedAt": "2026-10-30T12:49:27+00:00"
}
```

Why this one:

- **Exact caps.** `maxResults: 30` returns at most 30 videos per keyword, hashtag or profile - never a page more, never charged for more.
- **Hashtag results carry the hashtag.** TikTok's hashtag feed sometimes lists videos whose caption does not contain the tag; they are dropped (and counted in `excludedNotTagged`), not sold to you.
- **No duplicates.** Videos are de-duplicated on TikTok's video ID across the whole run; a repeat is skipped and not charged.
- **Empty is not the same as blocked.** Every input gets an `input_summary` row: `complete`, `limit_reached`, `partial`, `blocked`, `no_results` or `not_found`, with the reason. A run that returns no videos is not charged at all - not even the run fee.

### What it does

- **Keyword search:** the videos TikTok shows signed-out visitors for a keyword on its public keyword page (`tiktok.com/discover/<keyword>`), in TikTok's order.
- **Hashtag:** the top of the hashtag's public video feed (about 100 videos for a signed-out visitor), plus the hashtag's total video and view counts in the summary row.
- **Profile:** a public account's videos, newest first, plus the account's follower and video counts in the summary row.
- For each video: ID, link, caption, hashtags, publish time, plays, likes, comments, shares, saves, duration, cover image, author (username, nickname, follower count, verified badge) and sound.

### What it does not do

- **Not TikTok's signed-in search.** TikTok's search box requires login for full search; this Actor uses the public keyword page, which shows about 60-100 videos per common keyword. Rare or made-up phrases have no such page and come back `no_results` with `search:no_public_keyword_page`, free of charge.
- **No login-only content**, no private accounts (reported `not_found` with `profile:private_account`).
- **No video downloads** and **no transcripts** - for spoken text use [TikTok Transcript](https://apify.com/lanternlab318/tiktok-transcript).
- **No comments** - use [TikTok Comments Scraper](https://apify.com/lanternlab318/tiktok-comments) with the video links from this Actor.
- **Minimal personal data.** Only the author's public username, nickname, follower count and verified badge. No user IDs, bios, emails or avatars.

### Input

| Field | Default | Meaning |
| --- | --- | --- |
| `searchQueries` | - | Keywords, one per line. |
| `hashtags` | - | `cooking`, `#cooking` or `https://www.tiktok.com/tag/cooking`. |
| `profiles` | - | `nasa`, `@nasa` or `https://www.tiktok.com/@nasa`. |
| `maxResults` | 30 | Videos per keyword, hashtag or profile (1-1,000). |
| `proxyConfiguration` | Apify Residential, US | Used only for requests TikTok refuses direct - in practice hashtag lists. |
| `requestDelaySecs` | 0.5 | Minimum pause between two requests. |

At least one keyword, hashtag or profile is required.

### Output

One dataset with two kinds of rows. Two views are predefined: **Videos** and **Per-input status**. A `RUN_SUMMARY` record in the key-value store has run totals: request outcomes per API path, proxy traffic, and the first refusals.

Video fields:

| Field | Meaning |
| --- | --- |
| `videoId`, `url` | TikTok's video ID and link. |
| `description`, `hashtags` | Caption as published; hashtags in it, lower-case. |
| `publishedAt` | When TikTok published it (UTC). |
| `playCount`, `likeCount`, `commentCount`, `shareCount`, `collectCount` | Counters as TikTok reports them at collection time. |
| `durationSecs`, `isImagePost` | Length in seconds (null for photo posts). |
| `coverUrl` | Cover image. TikTok's image links expire after a while. |
| `isAd`, `language` | As TikTok reports them; null when the list does not say. |
| `author` | `username`, `nickname`, `followerCount`, `verified`. |
| `music` | `id`, `title`, `authorName`, `isOriginal`. |
| `inputType`, `input`, `position` | Which input produced the row and its position in that input's results. |
| `collectedAt` | When the Actor read it (UTC). |

A field TikTok does not provide is `null`, never guessed.

#### `input_summary` rows

| `collectionStatus` | Meaning |
| --- | --- |
| `complete` | TikTok's list ended before your cap (`list_end:...` in `reasons` says how), and for profiles and hashtags it covers at least 90% of the count TikTok displays. |
| `limit_reached` | Stopped at `maxResults` (or at the run's maximum cost). |
| `partial` | Something is missing; `reasons` says what - for example `refused:empty_body` after some videos were read, or `hashtag:feed_ended_below_video_count`. |
| `blocked` | TikTok refused the list before any video was read. |
| `no_results` | The keyword has no public keyword page, or the list was empty. |
| `not_found` | The hashtag or account does not exist, or the account is private. |
| `invalid_input` | The input is not a keyword, hashtag or profile. |

Also: `videosCollected`, `duplicatesSkipped`, `excludedNotTagged`, `pagesRead` and `detail` (hashtag: ID, title, video and view counts; profile: username, nickname, verified, follower and video counts).

### How it works

The Actor opens tiktok.com once in a real browser as a signed-out visitor and then reads TikTok's own web API with that visitor session, the way the website does. Requests go **direct first**. When TikTok refuses a request direct - in our tests only the hashtag feed - that kind of request is retried through the proxy you configured and stays on it for the rest of the run; if TikTok still refuses, the Actor opens a new visitor session a few times before marking the input `blocked` or `partial`.

### How much you get

Measured on Apify on 2026-10-30, no proxy used (every request was answered direct), one visitor session per run. TikTok changes its web client often, so numbers after a change may differ.

- **Keyword search:** 82 and 94 videos for `skincare routine` and `electric bike review` before TikTok's keyword page ended - about 60-100 videos per keyword is what a signed-out visitor is shown.
- **Hashtag:** TikTok serves a signed-out visitor only the top of a hashtag's feed - about 100 videos (91 tagged videos for `#booktok`, which has 88 million). When the feed ends far below the hashtag's video count, the summary row says `partial` with `hashtag:feed_ended_below_video_count`, never `complete`. In the same feed, 11 of 108 listed videos (and 70 of 100 for `#cooking`) did not carry the hashtag and were dropped.
- **Profile:** all 61 videos of `@nasa`, newest first, matching the 61 the profile shows.
- **Caps:** with `maxResults: 30`, each of the three inputs returned exactly 30 videos.
- **Duplicates:** 0 in the output. Asking for `nasa` and `@NASA` in one run returned the 61 videos once; the second input reports `duplicatesSkipped: 62` (TikTok repeats a pinned video).
- **Empty inputs:** a made-up keyword, hashtag and account came back `no_results` / `not_found`, and the run was not charged.

If you need more than the first ~100 videos of a keyword or hashtag, split it into more specific keywords or hashtags.

### Pricing

Pay per event: **$2.49 per 1,000 videos, plus $0.005 per run.** Apify platform usage, including proxy traffic, is included; you pay only these two events.

| Event | Price | Charged for |
| --- | --- | --- |
| `result` | $0.00249 ($2.49 per 1,000) | Each video row. `input_summary` rows are free. |
| `run-start` | $0.005 | Once per run, together with the first video. A run that returns no video is not charged. |

Example: 3 keywords x 100 videos = 300 videos costs $0.005 + 300 x $0.00249 = $0.752.

To cap a run, set **Maximum cost per run** in the run options. When it is reached, the Actor stores no further videos, marks the input it cut short `limit_reached` with `cap:maxTotalChargeUsd`, says so in the status message, and does not start the remaining inputs.

### Notes

- Respect TikTok's terms and local law, and only collect what you need. Usernames and nicknames are personal data in many jurisdictions.
- TikTok changes its web client often. `RUN_SUMMARY` shows request outcomes per API path and the first refusals.

### Licenses

Request signing is adapted from [Douyin_TikTok_Download_API](https://github.com/Evil0ctal/Douyin_TikTok_Download_API) by Evil0ctal and contributors, licensed under the Apache License 2.0. The license and notice are in `dtk/LICENSE` and `dtk/NOTICE`.

# Actor input Schema

## `searchQueries` (type: `array`):

Keywords to find videos for, one per line. Results are the videos TikTok shows signed-out visitors on its public keyword page (tiktok.com/discover/<keyword>).

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

Hashtags, one per line: cooking, #cooking or https://www.tiktok.com/tag/cooking. Every returned video carries the hashtag in its caption.

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

Public accounts, one per line: nasa, @nasa or https://www.tiktok.com/@nasa. Videos come newest first.

## `maxResults` (type: `integer`):

Collection stops at exactly this many videos for each input; you are never charged for more.

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

Requests go direct first. Only a request TikTok refuses direct (usually hashtag lists) is retried through this proxy.

## `requestDelaySecs` (type: `number`):

Minimum pause between two requests from the same visitor session.

## Actor input object example

```json
{
  "searchQueries": [
    "pasta recipe"
  ],
  "maxResults": 30,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "requestDelaySecs": 0.5
}
```

# Actor output Schema

## `videos` (type: `string`):

No description

## `inputs` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# 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 = {
    "searchQueries": [
        "pasta recipe"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("lanternlab318/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 = {
    "searchQueries": ["pasta recipe"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("lanternlab318/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 '{
  "searchQueries": [
    "pasta recipe"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call lanternlab318/tiktok-scraper --silent --output-dataset

```

## MCP server setup

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