# TikTok Hashtag Scraper — Captions as Text, No Login (`jurassic_jove/tiktok-organic-scraper`) Actor

Read organic TikTok videos under any hashtag: the caption as TEXT, the author with their follower count, views, likes and comments. One row per video. No login, no cookies.

- **URL**: https://apify.com/jurassic\_jove/tiktok-organic-scraper.md
- **Developed by:** [Data Runner](https://apify.com/jurassic_jove) (community)
- **Categories:** Social media, Automation
- **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?

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 Hashtag Scraper — Captions as Text, No Login 📹

**Read organic TikTok videos under any hashtag and get the caption as TEXT** —
not an image, not an OCR guess — plus the author with their **follower count**,
views, likes, comments and date. One row per video.

No login. No cookies. No captcha solving.

> **Why the caption matters.** A hashtag on its own tells you nothing.
> `#catinthehat` is the most persistent tag in one downstream tool and all it
> knew was the tag. One read of this Actor returns *"Cat in the hat Trent is
> getting schools shut down"* (327K views) and *"I paid $25,000 for the screen
> used Cat in the hat costume…"* (6.9M views, from an 8.9M-follower account).
> That is what the trend **is**.

***

### What you get — one row per video

```jsonc
{
  "hashtag": "gamescom",        // exactly as you typed it
  "geo": "US",
  "videoId": "7678812088428530957",
  "caption": "From Hoyoverse comes NODUS FALL, an original action co-op game. #gamescom",
  "author": "thegameawards",
  "authorName": "The Game Awards",
  "authorFollowers": 1200000,   // null if unreadable — never 0
  "authorVerified": true,
  "views": 2000000,
  "likes": 266600,
  "comments": 1240,
  "shares": 3100,
  "createdAt": "2026-08-27T20:32:56.000Z",
  "url": "https://www.tiktok.com/@thegameawards/video/7678812088428530957",
  "scrapedAt": "2026-09-05T22:45:00.000Z",
  "sourceUrl": "https://www.tiktok.com/tag/gamescom"
}
```

### Who uses this

- **Trend and culture tools** — a hashtag is an id; the captions are the meaning.
- **Print-on-demand and merch sellers** — the joke in the caption is the design
  brief. Printed merch is overwhelmingly jokes, and jokes live in captions.
- **Brand and agency teams** — who is posting under your tag, and how big are they.
- **Researchers** — caption text at volume, with engagement and dates attached.

### Input

```json
{ "hashtags": ["gamescom", "catinthehat"], "geos": ["US"], "maxVideosPerHashtag": 30 }
```

| Field | Default | Notes |
|---|---|---|
| `hashtags` | — | Required. The `#` is optional. Up to 50 per run. |
| `geos` | `["US"]` | One row set per hashtag × region, never merged. |
| `maxVideosPerHashtag` | `30` | A page serves 30 and pages from there. Max 150. |
| `sessionCookie` | — | Optional. Only for age- or region-restricted content. |
| `proxyConfiguration` | **off** | Not needed to get in — see FAQ. |

### Pricing

**$0.0005 per video row** — $0.50 per 1,000.

A hashtag that came back blocked or empty produces no rows and **costs
nothing**. You pay for videos, not for attempts.

***

### What it does NOT do, on purpose

- **It never invents a zero.** A metric that could not be read comes back
  `null`, not `0`. A zero would rank a video TikTok did not count below one
  that genuinely has nothing — the opposite conclusion.
- **A video with no caption returns `""`, never a description of the image.**
  The question is what it *says*, not what it shows. Describing the thumbnail
  would be inventing text next to measured numbers.
- **It never returns a short list when it was blocked.** If TikTok's Security
  Check took every hashtag, the run ends **red** with nothing pushed. A
  half-read hashtag is indistinguishable from a quiet one, and that mistake is
  expensive in one direction only.
- **It never normalizes your hashtag.** `CatInTheHat` goes out as
  `CatInTheHat`. Lower-casing on the way out has twice cost a downstream tool
  hundreds of already-measured terms that then read as "not measured".
- **It never truncates a caption.** The joke is usually at the end.
- **It never mixes regions.** What surfaces in one country is not what
  surfaces in another, so rows are per region and never merged.
- **It never solves a captcha.** It does not need to — see below.

***

### FAQ

#### Do I need a TikTok account, login or cookies?

**No.** Measured 5 Sep 2026 from a fresh, signed-out browser:

| page | result |
|---|---|
| `www.tiktok.com/tag/gamescom` | ✅ **200, no captcha, 804 KB, 120 video links** |
| `www.tiktok.com/search?q=gamescom` | 200 but **0** video links in the DOM |
| `www.tiktok.com/explore` | the general feed, not the hashtag's |

TikTok's wall is on other surfaces — the shop and profiles — not on a hashtag
page. You can still pass a `sessionCookie` to reach age- or region-restricted
content; it is optional.

#### Do I need a proxy?

Not to get in. What a proxy buys is spreading the reads so one address is not
burned at volume, which is why it is a switch and not a requirement.

#### Where do the videos actually come from?

The hashtag page **does not** ship them in its HTML:
`__UNIVERSAL_DATA_FOR_REHYDRATION__` is 262 KB and holds **zero** video ids.
The videos arrive in a separate signed request the page makes for itself
(`/api/challenge/item_list/`, 922 KB, 30 items, with `hasMore` and `cursor`),
which is why this Actor drives a real browser rather than calling an endpoint.

⚠️ The response is recognised **by its shape, not its URL**. Pinning the exact
route would make this Actor return zero videos the week TikTok renames it —
and it would do that silently, on a hashtag that plainly has videos.

#### Is `views` the video's total play count?

It is TikTok's own `playCount` at the moment of reading. It moves; the row
carries `scrapedAt` so you know when.

#### What if a video has no caption?

`caption` is `""`. That is a fact about the video, not missing data.

#### What if TikTok changes its page?

The run ends **red** and the summary names which hashtags failed and why —
`captcha`, `no-videos`, `navigation-failed`. It never finishes green with a
short list, because downstream "three videos" and "we were blocked" look
identical and only one of them is a quiet hashtag.

***

### Output

- **Dataset** — one row per video.
- **Key-value store `OUTPUT`** — the run summary, including `withCaption` (how
  many rows carried caption text) and **which hashtags produced nothing and
  why**, named rather than merely counted.

***

*Keywords: tiktok hashtag scraper, tiktok scraper, tiktok api, tiktok video
scraper, tiktok captions, scrape tiktok hashtag, tiktok trending videos,
tiktok data, tiktok no login, tiktok without cookies, tiktok creator data,
tiktok follower count, social listening, meme research, tiktok content
research, tiktok engagement data.*

# Actor input Schema

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

The hashtags to read, with or without the «#». Sent EXACTLY as you type them — capitals are preserved, because normalising is the consumer's job, not the scraper's. One row per video, per hashtag and region.

## `geos` (type: `array`):

Two-letter codes. The same hashtag is read once per region and rows are never merged: what surfaces in one country is not what surfaces in another.

## `maxVideosPerHashtag` (type: `integer`):

A hashtag page serves 30 and pages from there. The cap is 150, not «all»: every extra page is another scroll against a WAF, and the videos that explain a trend are the first ones.

## `sessionCookie` (type: `string`):

⚠️ OPTIONAL, and that is measured. On 5 Sep 2026, with no cookie from a fresh browser, www.tiktok.com/tag/gamescom returned HTTP 200, no captcha, 804 KB and 120 video links. Supply one only to reach age- or region-restricted content. ⚠️ If you do, paste the NAME and the value: DevTools shows only the Value column, and pasting that alone silently signs you out.

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

⚠️ Not needed to get in — the hashtag page is not walled. What a proxy buys is spreading the reads so one address is not burned at volume.

## Actor input object example

```json
{
  "hashtags": [
    "gamescom",
    "catinthehat"
  ],
  "geos": [
    "US"
  ],
  "maxVideosPerHashtag": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One row per video: the caption as TEXT, the author with their follower count, views, likes, comments and date.

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

How many reads were requested, how many succeeded, how many rows carried caption text, and — named, not just counted — which hashtags produced nothing and why. A hashtag that could not be read is not a quiet hashtag.

# 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": [
        "gamescom",
        "catinthehat"
    ],
    "geos": [
        "US"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("jurassic_jove/tiktok-organic-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": [
        "gamescom",
        "catinthehat",
    ],
    "geos": ["US"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("jurassic_jove/tiktok-organic-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": [
    "gamescom",
    "catinthehat"
  ],
  "geos": [
    "US"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call jurassic_jove/tiktok-organic-scraper --silent --output-dataset

```

## MCP server setup

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