# TikTok Keyword Search Scraper (`scraper-engine/tiktok-keyword-search-scraper`) Actor

TikTok Keyword Search Scraper finds public TikTok posts using targeted keywords and extracts captions, creators, hashtags, views, likes, comments, shares, upload dates, and video URLs. Build structured datasets for trend research, content discovery, and competitor analysis.

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

## Pricing

from $4.99 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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 Keyword Search Scraper — Captions, Stats and Author Data

Search TikTok by keyword and get back every matching video as structured JSON — caption, engagement stats (likes, plays, comments, shares), author profile, hashtags, and video/music metadata, with no login and no API key. Results stream into the dataset the moment each video is found, tagged with the query that produced it. Paste a keyword below and start a run to see it in action.

### What is TikTok Keyword Search Scraper?

TikTok Keyword Search Scraper searches TikTok's public video search for one or more keywords and returns each matching video as a structured JSON record — caption, timestamps, engagement counts, author profile, hashtags, and video/music details. It runs against TikTok's own web search page with no TikTok account, login, or API key required. It's built for developers, marketers, and researchers who need TikTok search results as clean, structured data instead of scraping HTML or reverse-engineering signed API calls themselves.

### What TikTok video data is publicly available to scrape?

Everything a visitor sees on TikTok's public search results page — without logging in — is fetchable: video captions, engagement counts, author username and follower count, hashtags, and video/music details. TikTok gates some things behind a login instead: its sort-by and date-posted filter controls only render for a signed-in session (this is why this Actor applies both client-side, against the data itself, instead of relying on a filter TikTok never sends to anonymous requests).

| Data Category | Publicly Available (no login) | Requires TikTok Login |
| --- | --- | --- |
| Video caption / description | Yes | — |
| Engagement counts (likes, plays, comments, shares, saves) | Yes | — |
| Author username, nickname, avatar, follower count | Yes | — |
| Hashtags and @mentions on a video | Yes | — |
| Video dimensions, duration, cover and playback URLs | Yes | — |
| Music/sound title, artist and playback URL | Yes | — |
| Server-side sort-by / date-posted filter controls | No | Yes (filters UI only renders for a signed-in session) |
| Private or unlisted videos | No | Never returned to an unauthorized viewer, logged in or not |

TikTok Keyword Search Scraper only returns publicly visible data — what any visitor sees. Nothing behind a login wall.

### What data can I extract with TikTok Keyword Search Scraper?

Each run returns caption and identity fields, engagement and status metrics, and author/media/hashtag detail for every matching video, reshaped into TikTok's own `aweme_info` field names so the keys match what TikTok's own app schema uses.

| Field Name | Description |
| --- | --- |
| `aweme_id` | Unique TikTok video ID |
| `aweme_type` | TikTok's internal content-type code; always `0` for a video hit |
| `group_id` | Same value as `aweme_id` — TikTok's internal grouping key |
| `desc` | Video caption/description text |
| `desc_language` | Detected caption language code, if TikTok supplies one |
| `is_description_translatable` | Whether TikTok offers a "translate caption" control for this video |
| `content_type` | Always `"video"` |
| `create_time` | Unix timestamp the video was posted |
| `create_time_utc` | ISO-8601 UTC timestamp the video was posted |
| `region` | Author's region if TikTok reports one, else the `region` input value |
| `url` / `share_url` | Canonical TikTok video URL (both keys carry the same value) |
| `is_ads` / `is_ad` | Whether TikTok flags the item as an ad (both keys carry the same value) |
| `is_top` | Always `0` — pinned-result flag is not exposed on this endpoint |
| `is_paid_partnership` | Always `false` — branded-content flag is not exposed on this endpoint |
| `is_eligible_for_commission` | Always `false` — TikTok Shop commission flag is not exposed on this endpoint |
| `shop_product_url` | Always `null` — TikTok Shop product link is not exposed on this endpoint |
| `aigc_info` | AI-content label object (see below) |
| `author` | Author profile object (see below) |
| `music` / `added_sound_music_info` | Sound/music object (both keys carry the same object) |
| `statistics` | Engagement counts object (see below) |
| `status` | Video status-flags object (see below) |
| `video` | Video technical/media object (see below) |
| `text_extra` | Array of hashtag/mention text spans |
| `cha_list` | Array of hashtag objects attached to the video |
| `aweme_acl` | Duet/stitch/share/comment permission flags |
| `search_query` | Which input query this record came from (Actor bookkeeping, not a TikTok field) |
| `scraped_at` | ISO timestamp the record was scraped (Actor bookkeeping, not a TikTok field) |

TikTok's native mobile app returns a much larger object per video (165+ top-level keys) than any browser-reachable endpoint can expose — that fuller object is served by an app-only, natively-signed endpoint with no public browser equivalent. This Actor returns every field TikTok's public web search page actually carries, and nothing padded out with guessed or fabricated values.

#### Video identity, timing and hashtags

`aweme_id`, `desc`, `desc_language`, `create_time`, `create_time_utc`, `region`, `url`, `search_query`, `scraped_at`, and the hashtag/mention arrays:

| Field Name | Description |
| --- | --- |
| `text_extra[].hashtag_name` / `hashtag_id` | Hashtag text and ID for each `#tag` span in the caption |
| `text_extra[].start` / `end` | Character offsets of the span inside `desc` |
| `text_extra[].user_id` | Tagged user's ID, for an `@mention` span |
| `text_extra[].is_commerce` | Whether TikTok flags the span as commercial |
| `cha_list[].cid` / `cha_name` / `desc` | ID, name and description of each hashtag attached to the video |

#### Engagement, status and content flags

| Field Name | Description |
| --- | --- |
| `statistics.digg_count` | Like count |
| `statistics.comment_count` | Comment count |
| `statistics.share_count` | Share count |
| `statistics.play_count` | View/play count |
| `statistics.collect_count` | Save/bookmark count |
| `statistics.repost_count` | Repost count |
| `statistics.download_count` / `forward_count` / `lose_count` / `lose_comment_count` / `whatsapp_share_count` | Always `null` — not exposed on this endpoint |
| `status.in_reviewing` | Whether the video is still under TikTok's moderation review |
| `status.private_status` | `1` if the video is marked private |
| `status.allow_comment` / `allow_share` | Whether commenting/sharing is enabled |
| `aweme_acl.duet_enabled` / `stitch_enabled` / `share_enabled` / `download_enabled` / `comment_enabled` | Per-action permission flags |
| `aigc_info.aigc_label_type` | TikTok's AI-content moderation label code, when present |

#### Author, video and music details

| Field Name | Description |
| --- | --- |
| `author.unique_id` | Author's `@username` |
| `author.nickname` | Author's display name |
| `author.uid` / `sec_uid` | Author's numeric and secure user IDs |
| `author.follower_count` / `following_count` / `aweme_count` | Follower, following and video counts |
| `author.total_favorited` | Total likes received across the author's account |
| `author.verification_type` | `1` if the author is verified, else `0` |
| `author.secret` | `1` if the author's account is private |
| `author.avatar_larger` / `avatar_medium` / `avatar_thumb` | Avatar image URLs at each size |
| `video.height` / `width` / `ratio` | Video resolution and quality tier |
| `video.duration` | Video length in milliseconds |
| `video.play_addr` / `download_addr` | Playback and download URLs |
| `video.cover` / `origin_cover` / `dynamic_cover` | Cover-image URLs |
| `music.title` / `author` / `album` | Sound title, artist and album |
| `music.is_original` | Whether the sound is an original creation |
| `music.play_url` / `cover_thumb` | Sound playback and cover-art URLs |

#### Add-on: Need additional TikTok data?

Need full profile timelines instead of a keyword search, or posts from a hashtag page? **TikTok Posts Scraper** covers profiles, hashtags, keyword search and pasted URLs in one Actor. Need a spoken-word transcript of the videos this Actor finds? **Best TikTok AI Transcript Extractor** turns TikTok videos into timestamped text with the same proxy-escalation approach used here.

### Why not build this yourself?

TikTok's web search page requires three request-signing headers (`X-Bogus`, `X-Gnarly`, `X-Dynosaur`) that TikTok's own obfuscated JavaScript computes per request from a live browser fingerprint — a replayed signed URL comes back HTTP 200 with an empty body rather than an error, so there's no static formula to reverse-engineer once and reuse. Building and maintaining a scraper that generates these correctly means running a real anti-detect browser, keeping up with TikTok's frontend changes, and absorbing proxy costs once TikTok starts challenging requests. This Actor already does all three: Camoufox drives the real search page, the proxy tier escalates automatically only when TikTok actually blocks a request, and the output schema stays the same even after a TikTok frontend change.

### How to use TikTok Keyword Search Scraper

This Actor runs on the Apify platform — no separate signup or SDK installation is required to try it.

1. Open TikTok Keyword Search Scraper on its Apify Store listing.
2. Fill in `queries` (required) with one or more keywords or full TikTok search URLs.
3. Optionally set `datePosted`, `sortBy`, `region`, or `maxResults` to narrow the results.
4. Click **Start**.
5. Watch results stream into the **Output** tab in real time, or download the finished dataset as JSON or CSV.

#### How to scale to bulk video extraction

`queries` is an array — paste any number of keywords or TikTok search URLs into it in one run, one per line, mixed freely; each is scraped independently and results from all of them land in the same dataset, tagged by `search_query`. There is no separate "bulk mode" to enable — a one-keyword run and a hundred-keyword run use the same input field, and `concurrency` (default `2`, max `5`) controls how many of them run in parallel.

### What can you do with TikTok video data?

- 📈 A trend researcher tracking a niche uses `statistics.play_count` and `statistics.digg_count` across a keyword's results to spot which video formats are currently outperforming the rest.
- 🏷️ A brand-safety analyst screening sponsored content uses `is_ads` and `aweme_acl.duet_enabled` to flag ad-labeled videos that also allow duets before a partnership review.
- 🔎 A social listening team monitoring a competitor's keyword uses `author.unique_id`, `create_time_utc` and `desc` to build a timeline of who's posting about a topic and when.
- 🎯 A growth marketer scouting sounds uses `music.title` and `music.author` across top-viewed results for a keyword to find trending audio before it saturates.
- 🤖 An AI engineer builds a RAG pipeline over TikTok trend data by indexing `desc` and `text_extra[].hashtag_name` as searchable context, with `statistics.play_count` as a relevance signal an agent can reason over directly.

### How does TikTok Keyword Search Scraper handle rate limits and blocking?

Every query starts on a direct connection with no proxy. If the search page won't load after `maxMountRetries` reload attempts, the Actor escalates itself one tier at a time — Direct → Datacenter proxy → Residential proxy — logging every switch, and stays on Residential for the rest of the run once it's reached. If a user configures their own proxy, that choice is used as-is and the automatic chain never overrides it. Pagination has its own budget: `maxScrollRetries` consecutive scrolls with no new videos ends a query's pagination, and `maxScanned` caps how many raw results a single query scans through as a runaway-crawl safety net. If four consecutive queries never load at all, the Actor logs the diagnosis once and stops attempting further queries rather than repeating the same failure — everything already collected stays saved either way, and a single record that fails to save never stops the rest of the run.

⚠️ If a Residential proxy is also blocked after 3 fresh-session rotations, the Actor gives up on that one query and moves to the next — the run itself keeps going, and every video already collected for that query has already been saved.

### Input

| Parameter | Required | Type | Description | Example Value |
| --- | --- | --- | --- | --- |
| `queries` | Yes | array | One or more keywords/phrases, or full TikTok search URLs, mixed freely. Each is scraped independently. | `["funny cats", "cooking recipes"]` |
| `datePosted` | No | string | Keep only videos published within this window: `today`, `this-week`, `this-month`, `this-year`. Applied client-side. | `"this-month"` |
| `sortBy` | No | string | Result order: `relevance` (default), `date`, or `views`. Applied client-side after collection. | `"views"` |
| `region` | No | string | 2-letter country code sent as an Accept-Language locale hint. Empty by default. | `"US"` |
| `maxResults` | No | integer | Maximum videos to fetch per query (`0`–`100000` in the schema; default `10`). | `50` |
| `proxyConfiguration` | No | object | Proxy settings. Default is no proxy — the automatic Direct → Datacenter → Residential chain handles most runs. | `{"useApifyProxy": false}` |
| `maxMountRetries` | No | integer | Page-load retries before escalating the proxy (`1`–`20`, default `5`). | `5` |
| `maxScrollRetries` | No | integer | Consecutive empty scrolls allowed before a query is considered exhausted (`1`–`20`, default `4`). | `4` |
| `maxScanned` | No | integer | Safety cap on raw rows scanned per query (`100`–`200000`, default `20000`). | `20000` |
| `concurrency` | No | integer | Parallel browser tabs, one per query (`1`–`5`, default `2`). | `2` |

⚠️ `maxResults` accepts up to `100000` in the input schema, but the Actor's own validation clamps every query to at most `1000` results — a higher value logs a warning and is silently capped, not rejected.

#### Example input

```json
{
    "queries": ["funny cats", "cooking recipes"],
    "datePosted": "this-month",
    "sortBy": "views",
    "region": "US",
    "maxResults": 50,
    "proxyConfiguration": { "useApifyProxy": false },
    "maxMountRetries": 5,
    "maxScrollRetries": 4,
    "maxScanned": 20000,
    "concurrency": 2
}
```

### Output

Results are typed, normalized JSON with a consistent schema across every run, pushed to the dataset the moment each video is found. Download them as JSON, CSV, or Excel from the Output tab, or use the built-in Overview table view for a query/author/stats/link column layout.

#### Example output

```json
{
    "aweme_id": "7680270017941835039",
    "aweme_type": 0,
    "group_id": "7680270017941835039",
    "desc": "we really cooked this up #FunnyCats #CatsOfTikTok",
    "desc_language": "en",
    "is_description_translatable": false,
    "content_type": "video",
    "create_time": 1788193182,
    "create_time_utc": "2026-08-31T16:19:42.000Z",
    "region": "US",
    "url": "https://www.tiktok.com/@catorahub/video/7680270017941835039",
    "share_url": "https://www.tiktok.com/@catorahub/video/7680270017941835039",
    "is_ads": false,
    "is_ad": false,
    "is_top": 0,
    "is_paid_partnership": false,
    "is_eligible_for_commission": false,
    "shop_product_url": null,
    "aigc_info": { "aigc_label_type": 0, "created_by_ai": null },
    "author": {
        "uid": "7663584039571899406",
        "sec_uid": "MS4wLjABAAAAexample",
        "unique_id": "catorahub",
        "nickname": "catora",
        "avatar_larger": { "url_list": ["https://p16-sign.tiktokcdn-us.com/avatar_larger.jpeg"], "url_prefix": null },
        "avatar_medium": { "url_list": ["https://p16-sign.tiktokcdn-us.com/avatar_medium.jpeg"], "url_prefix": null },
        "avatar_thumb": { "url_list": ["https://p16-sign.tiktokcdn-us.com/avatar_thumb.jpeg"], "url_prefix": null },
        "verification_type": 0,
        "follower_count": 29603,
        "following_count": 412,
        "total_favorited": 1882440,
        "aweme_count": 318,
        "secret": 0
    },
    "music": {
        "id": 7680270211014053000,
        "id_str": "7680270211014053000",
        "title": "original sound - catorahub",
        "author": "catora",
        "album": null,
        "is_original": true,
        "duration": 39,
        "play_url": { "url_list": ["https://sf16-ies-music.tiktokcdn.com/track.mp3"], "url_prefix": null },
        "cover_thumb": { "url_list": ["https://p16-sign.tiktokcdn-us.com/music_cover.jpeg"], "url_prefix": null }
    },
    "added_sound_music_info": {
        "id": 7680270211014053000,
        "id_str": "7680270211014053000",
        "title": "original sound - catorahub",
        "author": "catora",
        "album": null,
        "is_original": true,
        "duration": 39,
        "play_url": { "url_list": ["https://sf16-ies-music.tiktokcdn.com/track.mp3"], "url_prefix": null },
        "cover_thumb": { "url_list": ["https://p16-sign.tiktokcdn-us.com/music_cover.jpeg"], "url_prefix": null }
    },
    "statistics": {
        "aweme_id": "7680270017941835039",
        "digg_count": 150332,
        "comment_count": 1874,
        "share_count": 38544,
        "play_count": 1263171,
        "collect_count": 14024,
        "repost_count": 612,
        "download_count": null,
        "forward_count": null,
        "lose_count": null,
        "lose_comment_count": null,
        "whatsapp_share_count": null
    },
    "status": {
        "aweme_id": "7680270017941835039",
        "is_delete": false,
        "is_prohibited": false,
        "in_reviewing": false,
        "private_status": 0,
        "allow_comment": true,
        "allow_share": true,
        "self_see": false,
        "reviewed": 1
    },
    "video": {
        "height": 1280,
        "width": 720,
        "duration": 39126,
        "ratio": "720p",
        "play_addr": { "url_list": ["https://v16.tiktokcdn.com/play/video.mp4"], "url_prefix": null },
        "download_addr": { "url_list": ["https://v16.tiktokcdn.com/download/video.mp4"], "url_prefix": null },
        "cover": { "url_list": ["https://p16-sign.tiktokcdn-us.com/cover.jpeg"], "url_prefix": null },
        "origin_cover": { "url_list": ["https://p16-sign.tiktokcdn-us.com/origin_cover.jpeg"], "url_prefix": null },
        "dynamic_cover": { "url_list": ["https://p16-sign.tiktokcdn-us.com/dynamic_cover.jpeg"], "url_prefix": null },
        "has_watermark": null
    },
    "text_extra": [
        { "start": 20, "end": 30, "type": 1, "hashtag_id": "17001", "hashtag_name": "funnycats", "user_id": "", "is_commerce": false },
        { "start": 31, "end": 46, "type": 1, "hashtag_id": "17002", "hashtag_name": "catsoftiktok", "user_id": "", "is_commerce": false }
    ],
    "cha_list": [
        { "cid": "17001", "cha_name": "funnycats", "desc": "" },
        { "cid": "17002", "cha_name": "catsoftiktok", "desc": "" }
    ],
    "aweme_acl": {
        "duet_enabled": true,
        "stitch_enabled": true,
        "share_enabled": true,
        "download_enabled": true,
        "comment_enabled": true
    },
    "search_query": "funny cats",
    "scraped_at": "2026-09-01T12:00:00.000000Z"
}
```

### How does it work?

TikTok Keyword Search Scraper drives Camoufox, an anti-detect Firefox browser, to load TikTok's own search page directly rather than calling any API endpoint by hand. TikTok's web search requires request-signing headers its own JavaScript generates from a live browser fingerprint, so this Actor reads the JSON straight out of the real network responses that page fires — no header is ever hand-crafted or replayed. Pagination scrolls the page's actual inner results container, not just the outer window, since scrolling the window alone never triggers TikTok's lazy-loaded pages. Every request starts on a direct connection and only escalates to a Datacenter, then a Residential proxy if TikTok actually blocks it. Only what a visitor could see on TikTok's public search page is ever returned, and the output schema stays the same shape run to run, regardless of how TikTok reshuffles its own search page's layout.

### Integrations

TikTok Keyword Search Scraper is callable from any environment that can make an HTTP request, including from AI agent frameworks and no-code automation tools.

#### Calling TikTok Keyword Search Scraper programmatically

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_API_TOKEN>")
run = client.actor("<YOUR_USERNAME>/tiktok-keyword-search-scraper").call(
    run_input={
        "queries": ["funny cats"],
        "maxResults": 50,
    }
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["desc"], item["statistics"]["play_count"])
```

Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request.

#### MCP integration for AI agents

Apify publishes a general-purpose MCP server that exposes any Actor on the platform — including this one — as a callable tool for MCP-aware clients such as Claude Desktop and Cursor. Point it at this Actor by name (`tiktok-keyword-search-scraper`) using your Apify API token, and an agent can call it the same way it calls a local tool, no custom integration code required.

#### No-code tools (n8n, Make, LangChain)

In n8n, use the HTTP Request node pointed at this Actor's run-sync API endpoint with your `queries` payload as the request body. In Make, the Apify app's "Run an Actor" module takes the same input fields shown above and returns the dataset items as a bundle. In LangChain, the `ApifyWrapper` can call this Actor by ID and load the resulting dataset directly as documents for a chain or agent.

### Is it legal to scrape TikTok videos?

Scraping publicly available data — the videos, captions and profile fields any visitor can see without logging in — is generally lawful in most jurisdictions, and TikTok Keyword Search Scraper returns only that kind of data. Because the output includes author profile fields (username, nickname, avatar, follower count), it can count as personal data under regimes like GDPR and CCPA if you store or process it — you're responsible for having a lawful basis for that use, and for TikTok's own Terms of Service governing how scraped data may be used. Consult legal counsel if your use case involves bulk storage of personal data.

### Frequently asked questions

#### What TikTok video fields does TikTok Keyword Search Scraper return?

It returns `desc` (caption), `statistics` (likes/plays/comments/shares), `author` (username, nickname, followers), `video` (dimensions, duration, playback URLs), and `text_extra`/`cha_list` (hashtags), along with status and permission flags — see the full field table above.

#### Does TikTok Keyword Search Scraper require a TikTok account or login?

No. It searches TikTok's public web search page with no TikTok account, login, or API key — only publicly visible results are returned.

#### How many videos can I extract in one run?

Each query is capped at 1,000 results by the Actor's own validation (regardless of a higher `maxResults` value), and you can run as many queries as you like in a single `queries` array in one run.

#### What happens if a query returns zero results?

The run finishes normally with 0 records for that query and a log line explaining why — most often TikTok challenging a fresh session. The Actor already retries and escalates its proxy tier automatically on a block; if a query still comes back empty, retrying with a Residential proxy configured explicitly resolves most remaining cases.

#### Can I scrape multiple TikTok search queries at once?

Yes. `queries` is a bulk array field — add as many keywords or TikTok search URLs as you like, and they're scraped independently (up to `concurrency` at a time) with results streamed into one shared dataset.

#### Does TikTok Keyword Search Scraper work with Claude, ChatGPT, and other AI agent tools?

Yes. It's reachable through Apify's general-purpose MCP server for MCP-aware clients, and callable as a plain HTTP endpoint by any agent framework that can make a request and read JSON.

#### How does this differ from scraping TikTok search results by hand?

Beyond the browser and pagination handling, this Actor enforces `sortBy` and `datePosted` correctly by applying them client-side against the actual data — TikTok's anonymous search request carries no server-side sort or date parameter at all, so a hand-rolled scraper that trusts a URL parameter for either would silently get unfiltered, unsorted results back.

#### Does TikTok Keyword Search Scraper return data in a format LLMs can use directly?

Yes. Every record is typed, normalized JSON with consistent field names across runs — no HTML parsing or CSS selectors involved. Pass it directly to an LLM, index it into a vector store, or feed it to an agent tool.

#### What happens when TikTok changes its layout or anti-bot system?

The Actor is maintained, and the output schema is designed to stay stable across TikTok frontend changes — the parsing logic targets TikTok's underlying API response shape, not page markup that changes with every redesign.

#### Can I use TikTok Keyword Search Scraper without managing proxies or browser infrastructure?

Yes. The Actor runs its own anti-detect browser and handles proxy tier escalation (Direct → Datacenter → Residential) automatically; you only need to configure a proxy yourself if you want to force a specific tier from the start.

#### Which fields work best for AI training data and RAG indexing?

For RAG, index `desc` and `text_extra[].hashtag_name` as searchable text context. For structured training data, `statistics.play_count`, `statistics.digg_count`, and `create_time` are the most consistently populated numeric fields across records. All fields return as typed primitives (strings, integers, booleans) rather than raw HTML.

### Related scrapers

| Scraper Name | What it extracts |
| --- | --- |
| TikTok Posts Scraper | TikTok posts from profiles, hashtags, keyword search, or pasted URLs, with the same proxy-escalation and real-time output design |
| Best TikTok AI Transcript Extractor | AI-generated transcripts (SRT/VTT, timestamped segments) plus 40+ creator/video metadata fields from TikTok videos |
| Instagram Transcript API & Profile Scraper - AI Video to Text | AI transcripts from Instagram Reels/videos plus bulk public profile posts, for teams tracking both platforms |
| Spotify Play Count Scraper | Exact play/stream counts for Spotify artists, albums and tracks |

### Your feedback

Found a bug or a field that's missing? Let us know — reach out at dev.scraperengine@gmail.com or through the Issues tab on the Actor's Apify Console page, and we'll take a look.

# Actor input Schema

## `queries` (type: `array`):

One or more keywords/phrases to search for — or paste full TikTok search URLs (https://www.tiktok.com/search?q=...), mixed freely. Each query is scraped independently and results stream into the dataset as they're found.

## `datePosted` (type: `string`):

Only keep videos published within this window. Applied client-side (TikTok's anonymous search API accepts no server-side date filter).

## `sortBy` (type: `string`):

How to order results. Applied client-side after collection (TikTok's anonymous search API accepts no server-side sort parameter).

## `region` (type: `string`):

2-letter country code (e.g. US, GB, DE) — sent as an Accept-Language locale hint only. Left empty by default so the automatic proxy fallback isn't constrained to one country's IP pool; for a real change of exit-IP geography, pick a country inside 'Proxy configuration' below instead.

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

Maximum number of videos to fetch per query (0 = nothing, useful for a dry run).

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

🟢 Default = no proxy (direct connection), which works for most runs. If TikTok starts rejecting requests, the Actor automatically escalates itself: Direct ➜ 🖥️ Datacenter proxy ➜ 🏠 Residential proxy (retried up to 3×), then stays on residential for the rest of the run. Every switch is logged. Override this only to force a specific proxy from the start.

## `maxMountRetries` (type: `integer`):

How many times to reload the search page before treating it as blocked and escalating the proxy. Raise this if runs on a slow connection give up too early.

## `maxScrollRetries` (type: `integer`):

How many consecutive scrolls with no new videos to allow before assuming a query has no more results.

## `maxScanned` (type: `integer`):

Safety cap on how many raw videos a single query may scan through while paginating, even if 'Maximum Results' hasn't been reached yet (protects against a runaway crawl).

## `concurrency` (type: `integer`):

How many queries to scrape in parallel browser tabs. Keep this low — TikTok's anti-bot risk system is more likely to challenge a run that looks like a burst of parallel traffic.

## Actor input object example

```json
{
  "queries": [
    "funny cats"
  ],
  "sortBy": "relevance",
  "region": "US",
  "maxResults": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "maxMountRetries": 5,
  "maxScrollRetries": 4,
  "maxScanned": 20000,
  "concurrency": 2
}
```

# Actor output Schema

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

Every scraped video, from every query, in the order it was collected.

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

Quick-glance columns across every query, with the source query clearly labelled per row.

## `csv` (type: `string`):

All results exported as CSV.

# 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 = {
    "queries": [
        "funny cats"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scraper-engine/tiktok-keyword-search-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 = {
    "queries": ["funny cats"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("scraper-engine/tiktok-keyword-search-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 '{
  "queries": [
    "funny cats"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call scraper-engine/tiktok-keyword-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scraper-engine/tiktok-keyword-search-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/QKxvcbsddu5D1iBv1/builds/C8jjp5hI7U64eM1hg/openapi.json
