# TikTok Videos Scraper: Hashtags, Profiles, Video URLs, No Login (`neverempty/tiktok-videos-scraper`) Actor

For influencer vetting, trend research and campaign reports: TikTok videos from hashtags, profiles or URLs with views, likes, comments, shares, saves, caption, sound, author and date. No login. Hashtag results carry the hashtag. No filter fees. Monitoring returns only new videos. CSV, Excel, JSON.

- **URL**: https://apify.com/neverempty/tiktok-videos-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Social media, Videos, Marketing
- **Stats:** 3 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 1,000 video returneds

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 Videos Scraper: Hashtags, Profiles, Video URLs, No Login

**For influencer vetting, hashtag and trend research and campaign reports: this TikTok scraper exports the videos of any hashtag, profile or video URL to CSV, Excel or JSON, with no login, no cookies and no TikTok API key.** Each video is one flat row with views, likes, comments, shares, saves, caption, hashtags, sound, post time and the author with follower count and verified badge.

On 2026-10-12 it exported 500 videos of @duolingo in 30 seconds. The same day, TikTok's #cooking page listed 143 videos and 87 of them did not carry #cooking: they were skipped and not charged, so every row you pay for is a #cooking video.

- **Hashtag results that carry the hashtag.** TikTok's hashtag page mixes in videos without the tag. By default only the videos that carry it are returned and charged, and `hashtagInVideo` says so on every row. Turn `onlyVideosWithHashtag` off to get the whole list.
- **Every video of a profile**, newest first, up to 10,000 per account, with `postedAfter` and `postedBefore` to export one period only, at no extra charge.
- **Exactly the video you asked for.** For video URLs a row is returned only when TikTok answers with the same video id, and `input` on every row says which of your entries it came from.
- **Monitoring mode**: schedule it and each run returns only videos it has not returned before.
- **Fewer rows come with the reason.** When a hashtag list ends early, an account is private or does not exist, or a video was deleted, a free row says why and gives the numbers. These rows are never charged.
- **$3.50 per 1,000 videos, down to $1.70 on paid Apify plans.** No start fee and no extra charge for date or hashtag filters. Monitoring adds $0.30 per 1,000 checks.

Unofficial and not affiliated with TikTok. It reads only what TikTok shows to any visitor without logging in: no private accounts, no direct messages, nothing behind a login.

### What people use it for

- **Influencer vetting**: export a creator's last 100 or 1,000 videos with views, likes, comments, shares and saves to see how their posts actually perform, not just their follower count.
- **Hashtag and trend research**: export the videos of `#booktok`, `#skincare` or your campaign hashtag with their views, shares and sounds into a spreadsheet.
- **Campaign reporting**: export what an account posted between a campaign's start and end dates, or refresh the counts of a list of video URLs.
- **Competitor and creator tracking**: run monitoring mode on a list of accounts and hashtags every hour or day and get only the new videos.
- **Data for AI and analytics pipelines**: one clean JSON row per video, numbers as plain integers (39200, not "39.2K").

### How to scrape TikTok hashtag, profile and video data

1. Put your entries in one or more lists: `hashtags` (`booktok`, `#booktok` or the tag URL), `profiles` (`nasa`, `@nasa`, the profile URL or a secUid) or `videoUrls` (the video URL or its id).
2. Set `maxVideosPerInput` (100 by default, up to 10,000) and, if you want one period only, `postedAfter` and `postedBefore` (a date, a time or `30 days`).
3. Click **Start**. The example input (`#booktok`, 20 videos) took about 10 seconds; a profile of 500 videos took 30 seconds.
4. Download the dataset as CSV, Excel or JSON, or call the Actor from the Apify API, Python or Node.js and read the rows as JSON. Add a schedule with `monitoringMode` on to get only new videos each time.

### Input

| Field | Default | What it does |
| --- | --- | --- |
| `hashtags` | none | Hashtags, one per line (commas and spaces also separate entries). Up to 50 per run. |
| `profiles` | none | Usernames, profile URLs or secUids, one per line. Up to 200 per run. |
| `videoUrls` | none | Video URLs or video ids, one per line. Up to 1,000 per run. |
| `maxVideosPerInput` | `100` | Stop each hashtag or account after this many videos. Up to 10,000. |
| `onlyVideosWithHashtag` | `true` | Return only videos that carry the hashtag. Turn off to get every video the hashtag page lists; `hashtagInVideo` then says which ones carry it. |
| `postedAfter` | none | Only videos posted on or after this date (`2026-09-01`, read as 00:00 UTC), time (`2026-09-01T12:00:00Z`) or number of days back (`30 days`). Older videos are not returned or charged. |
| `postedBefore` | none | Only videos posted before this date or time. For accounts, reading starts there, so an older period can be exported without paying for newer videos. Not available in monitoring mode. |
| `monitoringMode` | `false` | Return only videos that were not returned before (see below). |
| `resetMonitoringState` | `false` | Forget everything remembered and start again with a first check. Turn it off again after one run. |
| `useProxy` | `true` | Retry through a US proxy only if TikTok answers with a rate limit or an empty list. |

```json
{
    "hashtags": ["booktok", "#skincare"],
    "profiles": ["nasa", "https://www.tiktok.com/@duolingo"],
    "videoUrls": ["https://www.tiktok.com/@jenna_bushhager/video/7223827494913215786"],
    "maxVideosPerInput": 50
}
```

If you leave all three lists out with monitoring off, the example in the input form is read (`#booktok`, 20 videos) and the run log says so. Empty lists are rejected with a free `invalid-input` row.

### Output

One row per video or photo post. This row was read from Apify on 2026-10-12 (UTC) with the example input:

```json
{
    "status": "ok",
    "inputType": "hashtag",
    "input": "booktok",
    "inputHashtag": "booktok",
    "hashtagInVideo": true,
    "username": "jenna_bushhager",
    "authorId": "6915876645917082629",
    "authorNickname": "Jenna Bush Hager",
    "authorVerified": true,
    "authorFollowerCount": 223700,
    "videoId": "7223827494913215786",
    "videoUrl": "https://www.tiktok.com/@jenna_bushhager/video/7223827494913215786",
    "postType": "video",
    "description": "#WorldBookDay is 4/23, and I've joined the #BookTok community to share our favorite books with you all! Duet to share your favorite read! #PassTheBook",
    "language": "en",
    "createdAt": "2023-04-19T18:20:30.000Z",
    "createTime": 1681928430,
    "playCount": 39200,
    "likeCount": 997,
    "commentCount": 32,
    "shareCount": 36,
    "saveCount": 134,
    "countsMayBeRounded": true,
    "durationSeconds": 82,
    "videoWidth": 708,
    "videoHeight": 1262,
    "coverUrl": "https://p19-common-sign.tiktokcdn-us.com/...",
    "musicId": "7223827501163006766",
    "musicTitle": "original sound",
    "musicAuthor": "Jenna Bush Hager",
    "musicIsOriginal": true,
    "hashtags": ["worldbookday", "booktok", "passthebook"],
    "mentions": [],
    "isAd": false
}
```

Every row also has `source`, `scrapedAt`, `authorSecUid`, `inputProfile`, `usernameMatchesInput`, `profileVideoCount`, `profileFollowerCount`, `imageCount`, `photoTitle`, `isPinned`, `locationName`, `locationAddress`, `locationCity`, `isFirstCheck` and `isNew`. Notes on the columns:

- **Counts are plain integers exactly as TikTok publishes them.** TikTok itself rounds views, likes, comments and shares of 10,000 and more (39,200, 58,300,000); `countsMayBeRounded` is `true` on those rows. `saveCount` is not rounded by TikTok.
- `inputType` (`hashtag`, `profile` or `videoUrl`) and `input` say which of your entries a row came from.
- `hashtagInVideo` is `true` when the video carries the hashtag you asked for, by TikTok's own hashtag tags on the video. TikTok also attaches some hashtags outside the caption (for example through a campaign): on 2026-10-12, 3 of the 20 example rows carried #booktok that way, so `hashtags` (the tags written in the caption) does not list it for them.
- `authorFollowerCount` is the author's follower count TikTok sends with the video; `profileVideoCount` and `profileFollowerCount` are filled for account rows from the profile page.
- `isPinned` is filled only for account rows (`true` for posts pinned to the profile).
- `postType` is `photo` for photo posts: they have `imageCount` and `photoTitle`, and no duration.
- `coverUrl` is a signed TikTok image link that expires after a few days; download the image if you need to keep it.
- `usernameMatchesInput` is `false` when TikTok answers with a different username than the one you gave (a renamed account, or a video URL with another user's name in it).

### FAQ

**How do I scrape TikTok hashtag videos?**
Put the hashtags in `hashtags`, with or without `#`, and click Start. Each video comes back as one row with its views, likes, comments, shares, saves, sound and author, and by default only videos that carry the hashtag are returned and charged. Export the dataset as CSV, Excel or JSON.

**Can I get TikTok data without logging in or a TikTok API key?**
Yes. The Actor reads only what TikTok shows to any visitor, with no login, no cookies and no TikTok API key. You can run it from the Apify Console or call it like an API from the Apify API, Python or Node.js and get JSON back.

**How do I export all videos of a TikTok profile to CSV?**
Put the username in `profiles`, set `maxVideosPerInput` high enough (up to 10,000) and download the dataset as CSV. On 2026-10-12 it returned 500 videos of @duolingo in 30 seconds. Add `postedAfter` and `postedBefore` to export one period only; the date filter costs nothing extra.

**Why did a hashtag return fewer videos than I asked for?**
TikTok's web hashtag page lists a limited number of videos and then stops (on 2026-10-12 the #cooking list ended after 143 videos), and listed videos that do not carry the hashtag are skipped. A free `hashtag-list-ended` row gives both numbers, so you never pay for videos that are not there.

**How much does it cost to scrape TikTok videos?**
$3.50 per 1,000 videos on the Apify Free plan, down to $1.70 per 1,000 on paid plans, with no start fee and no charge for date or hashtag filters. Rows that explain a missing or empty result are free. Monitoring mode adds $0.30 per 1,000 hashtags or accounts checked.

### Rows that are never charged

| Status | Meaning |
| --- | --- |
| `no-such-hashtag` | TikTok says it cannot find the hashtag. |
| `no-such-profile` | TikTok returned no profile for the username (code 10221). |
| `private-account` | The account is private, so its videos were not requested. |
| `list-refused` | TikTok refused an account's video list (code 10201). |
| `no-such-video` | TikTok says the video id does not exist (code 10204). |
| `video-unavailable` | TikTok did not show the video (deleted, private or under review); TikTok's code and message are passed on. |
| `different-video-returned` | TikTok answered with a different video than the id asked for. |
| `no-videos-listed` | The hashtag's or account's list is empty. |
| `no-matching-videos` / `no-videos-in-date-range` | Videos were listed, but none matched the hashtag or date filters (the row gives the counts). |
| `hashtag-list-ended` | TikTok's hashtag list ended before `maxVideosPerInput` (the row says how many were listed and skipped). |
| `max-videos-reached` | The hashtag or account reached `maxVideosPerInput`. |
| `stopped-reading` | Reading stopped part way (a later page could not be read or did not load). The rows returned before that are complete. |
| `verification-page` | TikTok showed a verification page (a check that the visitor is a person). The Actor does not try to get past it; the run stops there. |
| `blocked` | TikTok refused or kept answering with a rate limit or an empty list. |
| `unreadable` | The page could not be read (`tiktokStatusCode` has TikTok's code when it gave one). |
| `invalid-input` / `duplicate` | The entry could not be read, or it repeats an earlier one. |
| `no-new-videos` | Monitoring mode: nothing that was not returned before. |
| `budget-reached` / `not-checked` | The run's maximum total charge had no room for more, or the list was longer than one run takes. |

### Pricing

- **$3.50 per 1,000 videos** returned on the Apify Free plan, down to $1.70 per 1,000 on paid plans, charged only for rows with `status: "ok"`. No start fee, no extra charge for date or hashtag filters.
- In monitoring mode, also **$0.30 per 1,000 checks**: every hashtag or account checked costs one check, whether or not it has new videos. Example: 100 accounts checked every hour is 72,000 checks a month, which is $21.60, plus the new videos returned.

The Actor reads only as many pages as the run's maximum total charge can pay for, and a free row says what was left out.

### Monitoring mode

- **Accounts**: the first run returns up to `maxVideosPerInput` of the newest videos (`isFirstCheck: true`) and remembers the time of the check. Later runs return only videos posted after it that were not returned before (`isNew: true`), however many there are.
- **Hashtags**: the first run returns up to `maxVideosPerInput` videos and remembers them. Later runs read the first `maxVideosPerInput` videos of the hashtag's list again and return only those that were not returned before. TikTok orders hashtag lists by popularity, not by date, so these are videos new to the list, which are not always just posted.
- **Video URLs** are returned on every run with their current counts.
- Videos that a run could not deliver (for example at the maximum total charge) are not remembered, so the next run returns them.
- The memory is kept per account and per hashtag. Do not put the same one in two schedules that can run at the same time.

### Good to know

- Unofficial. Public data only. This Actor is not affiliated with TikTok.
- TikTok's web hashtag pages list a limited number of videos (in this Actor's runs on 2026-10-12: #booktok 99, #cooking 143, #fyp 2), in TikTok's order rather than newest first. Accounts have no such limit: their list goes back to the first post.
- Hashtags are read in a real browser, so they take longer than accounts: about 10 seconds for the example (20 videos) and about 70 seconds for 143 listed videos.
- Keyword search is not included: TikTok's robots.txt asks crawlers not to open its search pages. Give hashtags, accounts or video URLs.
- Location fields are filled only when the creator tagged a place. TikTok does not send the poster's country with the video lists.
- The Actor does not download video files, read comments or follow short links (`vm.tiktok.com`); open a short link in a browser and paste the full URL.
- If TikTok shows a verification page, the run stops there and the rows returned before it are complete.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

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

Hashtags to list the videos of: booktok, #booktok or https://www.tiktok.com/tag/booktok. One per line (commas and spaces also separate entries). Videos come in the order TikTok's hashtag page shows them (popular first, not newest first). Leave empty to skip hashtags.

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

Accounts to list the videos of, newest first: a username (nasa or @nasa), a profile URL (https://www.tiktok.com/@nasa) or a secUid (MS4wLjABAAAA...). One per line. Short links (vm.tiktok.com) are not followed: open one in a browser and paste the profile URL. Leave empty to skip accounts.

## `videoUrls` (type: `array`):

Single videos to read: https://www.tiktok.com/@user/video/1234567890123456789 or the video id alone. One row per video with its current counts. Short links (vm.tiktok.com) are not followed. Leave empty to skip.

## `maxVideosPerInput` (type: `integer`):

Reading a hashtag or an account stops once this many charged video rows have been returned for it, and a free row says so. If TikTok's list ends earlier, a free row says how many it listed. In monitoring mode it also sets how deep each hashtag list is read; for accounts it limits only the first check.

## `onlyVideosWithHashtag` (type: `boolean`):

On (default) = a hashtag returns only videos that carry that hashtag themselves. TikTok's hashtag page also shows related videos without it; those are skipped, not charged, and counted in a free row. Off = every video the hashtag page lists is returned, and the hashtagInVideo column says which ones carry it.

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

Optional. A date (2026-09-01, read as 00:00 UTC), a time (2026-09-01T12:00:00Z) or a number of days back (30 days). Older videos are not returned or charged. For accounts, reading stops once it reaches videos 3 days older than this.

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

Optional. Same formats as postedAfter. Accounts start reading at this time instead of at the newest video, so older periods can be exported without paying for newer videos; hashtags skip newer videos without charging them. Not available in monitoring mode.

## `monitoringMode` (type: `boolean`):

Off = every run returns the videos, charged per row. On = the first run of a hashtag or account returns up to maxVideosPerInput videos and remembers them; later runs return only new ones (accounts: posted after the previous check; hashtags: videos in the first maxVideosPerInput of the list that were not returned before). **In monitoring mode every hashtag or account checked costs $0.30 per 1,000 checks, whether or not it has new videos**, plus the row price for the videos returned. Video URLs are returned every run with their current counts. Remembered per hashtag and per account; do not put the same one in two schedules that can run at the same time.

## `resetMonitoringState` (type: `boolean`):

Clears everything remembered for every hashtag and account, so the next monitoring run treats each one as a first check again. Turn it off again after one run: left on in a schedule, every run is a first check.

## `useProxy` (type: `boolean`):

Every page is requested directly first. Accounts and video URLs: only a rate limit (HTTP 429 or 503), an empty response or no response is retried through a US proxy. Hashtags: only if the hashtag page sends no video list is it opened again through a US datacenter proxy. A verification page is never retried: the run stops there. Off = no proxy is used or paid for.

## Actor input object example

```json
{
  "hashtags": [
    "booktok"
  ],
  "maxVideosPerInput": 20,
  "onlyVideosWithHashtag": true,
  "monitoringMode": false,
  "resetMonitoringState": false,
  "useProxy": true
}
```

# Actor output Schema

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

One row per video (or photo post) of each TikTok account: the video id and URL, the caption, the time it was posted, the view, like, comment, share and save counts, the length, the sound, the hashtags and mentions, whether it is pinned, TikTok's isAd flag and the tagged place, with the account's video and follower counts. In monitoring mode only videos posted since the last check are returned. Accounts that do not exist, private accounts, verification pages and invalid input come back as free rows that say why.

# 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 = {
    "hashtags": [
        "booktok"
    ],
    "maxVideosPerInput": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/tiktok-videos-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 = {
    "hashtags": ["booktok"],
    "maxVideosPerInput": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/tiktok-videos-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 '{
  "hashtags": [
    "booktok"
  ],
  "maxVideosPerInput": 20
}' |
apify call neverempty/tiktok-videos-scraper --silent --output-dataset

```

## MCP server setup

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