# TikTok Hashtag Scraper: Posts, Free Date Filter (`automation_craft/tiktok-hashtag-scraper`) Actor

Scrape TikTok hashtag feeds with no login: posts under a tag with play, like, comment, share and save counts, caption, author stats, music and media URLs, plus the exact video and view count of the tag. Date window, minimum plays, per tag cap and ad exclusion are free. JSON, CSV, API. Pay per post.

- **URL**: https://apify.com/automation\_craft/tiktok-hashtag-scraper.md
- **Developed by:** [Automation Craft](https://apify.com/automation_craft) (community)
- **Categories:** Social media, Marketing, Lead generation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 posts

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 Hashtag Scraper: Posts, Free Date Filter

**TikTok Hashtag Scraper**: give it one or more hashtags (`nike`, `#nike` or `https://www.tiktok.com/tag/nike`) and get the posts TikTok's public web feed shows under each tag, with no login, no cookies and no API key. Every post row carries the play, like, comment, share and save counts, the caption with its hashtags and mentions, the author with follower and like counts, the music, TikTok's content category, related searches, caption track links and the media URLs with the time they expire. Each hashtag also gets a free card row with the exact video count and view count TikTok reports for the tag.

Every filter is free: a date window, minimum plays, "only posts that carry the tag", ad exclusion and slideshows only. You pay per post delivered, never for a filtered post, a duplicate, a missing tag or a status row.

### Quick start

1. Open the Actor and type your hashtags into **Hashtags**, one per line: `nike`, `#nike` and tag URLs all work.
2. Set **Maximum posts per hashtag** (30 by default; 0 takes everything the web feed serves, see the ceiling below) and, if you want, **Maximum posts in total**.
3. Add free filters under **Filters (free)**: **Posted on or after**, **Minimum plays**, **Only posts that carry the hashtag**, **Exclude ads**.
4. Run it. Download the **Posts** view as JSON, CSV or Excel, or read the dataset through the API.
5. For a hashtag you re-check on a schedule, set a **Memory name**: posts delivered in an earlier run under that name are skipped for free, so each run pays only for new posts.

### What you get

A delivered post (`type: "post"`, `status: "ok"`) uses the same field names as the most used TikTok scrapers (`authorMeta`, `musicMeta`, `videoMeta`, `diggCount`, `playCount`, `webVideoUrl`), so you can swap without rewriting your pipeline. On top of them every row has `hashtag` (the tag it came from), `feedPosition` (its place in the feed as TikTok served it), `containsTagInCaption`, `pageCursor` and `exitRegion`. Fill rates measured on the Apify platform on 2026-09-25:

| Field | Filled in 1,260 unique posts from 33 platform runs |
|---|---|
| `playCount`, `diggCount`, `commentCount`, `shareCount`, `collectCount` | 1,260 of 1,260 |
| `authorMeta.fans`, `authorMeta.heart` (author at the time of the run) | 1,260 of 1,260 |
| `categoryType`, `textLanguage`, `mediaExpiresAt`, `musicMeta.musicId` | 1,260 of 1,260 |
| `videoMeta.vqScore` | 1,256 of 1,260 |
| `videoMeta.downloadAddr` | 1,246 of 1,260 |
| `hashtags` | 1,141 of 1,260 |
| `musicMeta.playUrl` | 1,109 of 1,260 |
| `effectStickers` | 1,067 of 1,260 |
| `videoMeta.captionsEnabled` true | 1,027 of 1,260 |
| `containsTagInCaption` true | 561 of 1,260 |
| `musicMeta.dspLinks` (streaming links) | 551 of 1,260 |
| `videoMeta.subtitleLinks` (caption track links) | 389 of 1,260 |
| `poi` (tagged place) | 256 of 1,260 |
| `mentions` | 224 of 1,260 |
| `suggestedWords` (related searches) | 146 of 1,260 |
| `isAd` true | 129 of 1,260 |
| `isSlideshow` true | 4 of 1,260 |
| `diversificationLabels`, `locationCreated`, `authorMeta.bioLink` | 0: the feed does not carry them (null) |

| Field | What it is |
|---|---|
| `id`, `webVideoUrl` | TikTok's post id (a string) and the post URL (`/photo/` for slideshows) |
| `text`, `textLanguage`, `hashtags`, `mentions`, `detailedMentions` | caption, its language, hashtags with ids, mentioned accounts with ids |
| `createTime`, `createTimeISO` | when the post was published |
| `playCount`, `diggCount`, `commentCount`, `shareCount`, `collectCount`, `repostCount` | the counts from TikTok's statsV2 block (see the note below); `null`, never 0, when TikTok sends none |
| `authorName`, `authorMeta` | the author with follower, following, like and video counts at the time of the run, verified flag and account settings |
| `musicMeta` | song id, title, artist, original or not, copyright and commerce flags, audio URL and streaming links |
| `videoMeta` | duration, size, cover URLs, `downloadAddr`, renditions, TikTok's video quality score, loudness, caption track links (`subtitleLinks`) with their expiry |
| `categoryType`, `suggestedWords`, `poi` | TikTok's content category number, related searches, the tagged place |
| `isAd`, `isSlideshow`, `imagePost`, `isAigc`, `duetEnabled`, `stitchEnabled` | flags and the slideshow images |
| `mediaUrls`, `mediaExpiresAt` | the signed media URLs and the earliest moment one stops working (about 6 hours after the run for video URLs) |

**About the counts.** TikTok's hashtag feed gives play, like, comment and share counts to 4 significant digits once they reach 10,000 (390,500, not 390,512), and the author's follower and like counts the same way; below 10,000 they are exact. Saves (`collectCount`) are exact at every size (93 of the 99 save counts above 10,000 in our sample had more than 4 significant digits), and so are the hashtag card's `videoCount` and `viewCount`. The Actor returns TikTok's figures as numbers, never estimates.

This listing does not download captions: `videoMeta.subtitleLinks` gives TikTok's caption track links with their expiry. The TikTok Video Scraper returns the transcript text inline.

**The hashtag card** (`type: "hashtag"`, free, on by default) has the tag id, description, commerce flag, `videoCount` and `viewCount`. **The hashtag summary** (`type: "hashtag-summary"`, free) says for each tag how many feed pages and posts TikTok served, how many were delivered, filtered (by reason), repeated or already known, how many page sessions were retired, and why it stopped (`stopReason`: `end-of-results`, `max-per-input`, `max-total`, `charge-limit`, `blocked`, `not-found`). **Status rows** (`type: "status"`, free): `not_found` (the tag does not exist), `empty_feed` (the tag exists but TikTok's web feed shows no posts for it, often a brand tag such as #adidas), `blocked`, `invalid` and `skipped`. The last row is the run summary.

#### How many posts one hashtag returns (measured ceiling)

TikTok's web feed for a hashtag is not the full list of posts that use the tag. Measured on 2026-09-25: one page session scrolled to the end of the feed got 81 to 161 distinct posts per hashtag (median 101, 38 walks) before TikTok stopped serving pages. When you ask for more than one walk gives, the Actor walks the tag again on a fresh page session, which returns mostly the same feed plus new posts: #nike grew to 98, 116, 140 and 161 posts over four walks, #puma to 133, 154, 188 and 196, #fitness to 93, 122, 146 and 164. It walks again only while the last walk delivered enough new posts (50 on the first walk, 10 on later ones), and at most 5 extra times, so a heavily filtered tag is not walked again for a handful of posts. Expect roughly 100 to 200 posts per hashtag, not thousands.

The feed also mixes in posts TikTok relates to the tag: 53 percent of the posts we delivered carry the tag in their caption or hashtag list, and far fewer on small tags (0 of 210 posts on a tag with 22 videos, 35 of 152 on a tag with 106 videos, 51 of 199 on a tag with 3,154 videos). Every row carries `containsTagInCaption`; turn on **Only posts that carry the hashtag** to keep only those. The feed is not in date order, so the date window skips posts rather than stopping early. Ads were 129 of 1,260 posts (10 percent); slideshows were 4 of 1,260 posts, so the slideshow filter rarely finds anything in a hashtag feed.

**Region.** Every page TikTok served was its US web app (`exitRegion: "US"`), whatever the exit. Exits that TikTok answers with its non US app get an empty feed, so the Actor retires them and tries another; there is no country selector. Counts such as plays and likes are the post's global counts.

### How much does it cost to scrape TikTok hashtags?

| Event | FREE and BRONZE | SILVER | GOLD | PLATINUM | DIAMOND | When it is charged |
|---|---|---|---|---|---|---|
| Post | $1.00 per 1,000 | $0.90 | $0.80 | $0.55 | $0.50 | once per unique post delivered that passed your filters |
| Actor start | $0.010 per GB of memory | same | same | same | same | once per run; with the default 4,096 MB that is 4 x $0.010 |

The Store pricing card shows these same prices per 1,000 events: "$1.00 / 1,000" on the Post row means one post costs a tenth of a cent. Filtered posts, duplicates, posts already in your memory, the hashtag card, status rows and the summaries are free. A run with 3 hashtags and 60 posts each (180 posts) costs 180 x $0.001 plus the $0.04 start event, about $0.22 on BRONZE. The start event pays for the headless page load TikTok requires before its feed answers, and it is the same whether 3 or 60 posts pass your filters: a very strict filter over one or two tags (for example 9 posts kept of 250 seen) is the one shape where the start event is most of what you pay, so widen the window or add tags rather than filtering a single tag hard. Platform usage is included: the Actor runs at 4,096 MB because TikTok's web app is CPU bound, and measured runs took 37 to 68 seconds for 3 hashtags.

### Input

| Field | Default | What it does |
|---|---|---|
| `hashtags` | | tags, `#tags` or tag URLs |
| `maxPostsPerHashtag` | 30 | posts per tag; 0 takes everything the feed serves (re-walks included) |
| `maxPosts` | none | total cap across tags; 0 means none |
| `includeHashtagCard` | true | the free card row per tag |
| `postedAfter`, `postedBefore` | | free date window, `YYYY-MM-DD` (UTC). An unreadable date stops the run before anything is charged |
| `minPlays` | | free minimum play count |
| `requireTagInCaption` | false | free: only posts whose caption or hashtag list names the tag |
| `excludeAds`, `slideshowsOnly` | false | free filters |
| `memoryName`, `resetMemory` | | cross run memory of delivered post ids |
| `tryDirectFirst` | true | the first page session uses the Apify platform's own IP, the cheapest and fastest exit we measured (56 of 56 sessions alive) |
| `proxyConfiguration` | datacenter | the proxy for later and replacement sessions (38 of 53 datacenter sessions alive; dead and non US exits are retired at once, never reloaded). Residential works but is never chosen automatically |
| `maxConcurrency` | 1 | page sessions in parallel, 1 to 3. One is the measured fastest and cheapest |

Example input:

```json
{
  "hashtags": ["nike", "#puma", "https://www.tiktok.com/tag/fitness"],
  "maxPostsPerHashtag": 60,
  "postedAfter": "2026-08-01",
  "minPlays": 10000,
  "requireTagInCaption": true,
  "excludeAds": true
}
```

### FAQ

#### Do I need a TikTok account or an API key?

No. The Actor opens the public hashtag page in a headless browser the way a signed out visitor does and reads the feed TikTok serves to that page. Nothing in the input asks for a credential.

#### How many posts can one hashtag return?

One page session gets about 80 to 160 posts per hashtag before TikTok's web feed stops serving pages (measured 2026-09-25); the Actor walks the tag again on fresh sessions while each walk still adds new posts, which reached 115 to 210 per tag. The per hashtag summary row reports where each tag stopped and why.

#### Is the date filter free?

Yes. The date window, minimum plays, tag in caption, ad exclusion and slideshow filters are applied inside the run and cost nothing; you pay only for the posts that pass them.

#### Why do some posts under a hashtag not mention it?

TikTok's feed also ranks posts it relates to the tag. Every row carries containsTagInCaption, and the requireTagInCaption filter keeps only posts whose caption or tag list carries the hashtag.

#### Does it return comments or keyword search results?

No. Comments sit behind a slide puzzle and a login on the web, and keyword search is login walled. The Actor returns hashtag feeds with counts, captions, author stats, music and media URLs.

#### Which country does the feed come from?

TikTok's US web feed. Every page TikTok served during our measurements was its US web app on every exit; exits that get the non US app receive an empty feed and are retired automatically, so there is no country selector. Every row records exitRegion.

#### Why does this Actor run with limited permissions?

Least privilege. It reads and writes only its own run storages, and the optional cross run memory is a named key value store it creates itself.

### What this Actor does NOT do

- No comments: TikTok shows comments on the web only after a slide puzzle and a login prompt.
- No keyword search: TikTok's search is login walled on the web.
- No full history of a hashtag: the web feed stops at roughly 100 to 200 posts per tag (measured above).
- No country selection: TikTok serves its US web feed to every exit that gets a feed.
- No transcripts inline: caption track links only (use the TikTok Video Scraper for transcript text).
- No login, no cookies, no private content.

### API examples

Run it from the command line:

```bash
curl -X POST "https://api.apify.com/v2/acts/automation_craft~tiktok-hashtag-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"hashtags": ["nike"], "maxPostsPerHashtag": 30}'
```

Node.js:

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation_craft/tiktok-hashtag-scraper').call({ hashtags: ['nike', 'puma'], maxPostsPerHashtag: 60, excludeAds: true });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((i) => i.type === 'post').length, 'posts');
```

Python:

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_TOKEN>")
run = client.actor("automation_craft/tiktok-hashtag-scraper").call(run_input={"hashtags": ["nike"], "postedAfter": "2026-09-01"})
posts = [i for i in client.dataset(run["defaultDatasetId"]).iterate_items() if i["type"] == "post"]
```

### More data tools by Automation Craft

- [TikTok Profile Scraper: Exact Stats, No Login](https://apify.com/automation_craft/tiktok-profile-scraper)
- [TikTok Video Scraper: Post Stats, Transcripts](https://apify.com/automation_craft/tiktok-video-scraper)

# Actor input Schema

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

TikTok hashtags to scrape, one per line: nike, #nike or a tag URL such as https://www.tiktok.com/tag/nike. Each post under the tag is one row. A tag that does not exist, or has no public web feed, gets a free status row.

## `maxPostsPerHashtag` (type: `integer`):

Stop a hashtag after this many delivered posts. 0 takes everything TikTok's web feed serves (measured: about 140 posts per hashtag, see the README). Left out, 30.

## `maxPosts` (type: `integer`):

Stop the whole run after this many delivered posts across all hashtags. 0 means no total cap. Filtered posts, duplicates and posts already in your memory do not count.

## `includeHashtagCard` (type: `boolean`):

Add one free row per hashtag (type "hashtag") with the tag id, description and the exact video count and view count TikTok reports for it.

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

Free filter: keep only posts created on or after this day (YYYY-MM-DD, UTC). The feed is not in date order, so older posts are skipped, not a stop signal. An unreadable date stops the run before anything is charged.

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

Free filter: keep only posts created on or before this day (YYYY-MM-DD, UTC, the whole day included).

## `minPlays` (type: `integer`):

Free filter: keep only posts with at least this many plays, as TikTok prints the count. Empty or 0 keeps every post.

## `requireTagInCaption` (type: `boolean`):

Free filter: keep only posts whose caption or hashtag list names the tag you asked for. TikTok's feed also shows related posts without it. Every row carries containsTagInCaption either way.

## `excludeAds` (type: `boolean`):

Free filter: skip promoted posts (isAd true) that TikTok mixes into the feed.

## `slideshowsOnly` (type: `boolean`):

Free filter: keep only photo slideshow posts. The web hashtag feed rarely carries them (1 in 1,541 feed posts in our measurement), so expect few or no rows.

## `memoryName` (type: `string`):

Optional. A name for a cross run memory of delivered post ids. With a memory, a post delivered in an earlier run under the same name is skipped for free, so a scheduled run pays only for new posts.

## `resetMemory` (type: `boolean`):

Forget every post id stored under the memory name before this run starts.

## `tryDirectFirst` (type: `boolean`):

The first page session of a run uses the Apify platform's own IP with no proxy (the fastest and cheapest exit we measured); every later or replacement session uses the proxy below.

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

Proxy for replacement page sessions. The default datacenter group is the measured fallback. Residential proxies work but cost far more per post, so they are never chosen automatically.

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

How many hashtags are scraped in parallel, each in its own page session (1 to 3). One is the measured fastest and cheapest: the page is CPU bound, and a second session starts on a proxy exit.

## Actor input object example

```json
{
  "hashtags": [
    "nike"
  ],
  "maxPostsPerHashtag": 30,
  "maxPosts": 30,
  "includeHashtagCard": true,
  "tryDirectFirst": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "BUYPROXIES94952"
    ]
  },
  "maxConcurrency": 1
}
```

# Actor output Schema

## `rows` (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 = {
    "hashtags": [
        "nike"
    ],
    "maxPostsPerHashtag": 30,
    "maxPosts": 30,
    "includeHashtagCard": true,
    "tryDirectFirst": true,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "BUYPROXIES94952"
        ]
    },
    "maxConcurrency": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/tiktok-hashtag-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": ["nike"],
    "maxPostsPerHashtag": 30,
    "maxPosts": 30,
    "includeHashtagCard": True,
    "tryDirectFirst": True,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["BUYPROXIES94952"],
    },
    "maxConcurrency": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/tiktok-hashtag-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": [
    "nike"
  ],
  "maxPostsPerHashtag": 30,
  "maxPosts": 30,
  "includeHashtagCard": true,
  "tryDirectFirst": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "BUYPROXIES94952"
    ]
  },
  "maxConcurrency": 1
}' |
apify call automation_craft/tiktok-hashtag-scraper --silent --output-dataset

```

## MCP server setup

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