Use TikTok Explore Feed Trending Post Scraper with Python
Collect trending posts from TikTok's Explore feed across one or more content categories, with engagement stats, author snapshot, sound and video details. Can also try to discover category IDs.
1. Choose the data you need
Review the published README for supported collection modes, examples, and limits. The recipe below uses the current published input definition.
2. Prepare a source-based input
This JSON contains published prefills and required-field defaults. Review every value for your task before running it; it is not a promise of successful collection.
{
"autoDiscoverCategories": false,
"categories": [
"All"
],
"countries": [],
"includePostDetails": false,
"includeTranscripts": false,
"maxItemsPerCategory": 3,
"minComments": 0,
"minLikes": 0,
"minShares": 0,
"minViews": 0,
"onlyNew": false,
"postedWithin": "any",
"rehostVideo": false
}
Download input.example.json and save your reviewed copy as input.json.
| Field | Type | Required | Description |
|---|---|---|---|
categories | array | required | Categories to collect. Accepts names, numeric IDs, or legacy {id,name} objects. All=120; topics 100-119: Anime & Comics, Shows, Beauty Care, Games, Comedy, Daily Life, Family, Relationship, Drama, Outfit, Lipsync, Food, Sports, Animals, Society, Cars, Education, Fitness & Health, Technology, Singing & Dancing. 123 is accepted as a legacy alias for All. 121/122 (empty upstream) and 124+ (invalid upstream) are skipped with a warning. |
maxItemsPerCategory | integer | optional | Hard cap on emitted posts per category. The cap is applied before any page is pushed, so a 30-item page never overshoots. The default is 3 because media rehosting is on by default and adds one browser page plus several HTTP media fetches per emitted post. |
maxItems | integer | optional | Legacy alias for maxItemsPerCategory. It is used when maxItemsPerCategory is absent or exactly the default 3; an explicitly non-default maxItemsPerCategory wins. |
categoryId | integer | optional | Legacy shorthand for one category. It replaces the categories list. 100-120 are accepted; 123 maps to 120; other values are skipped with a warning. |
autoDiscoverCategories | boolean | optional | When true, an empty or All-only selection expands to all 21 verified Explore categories. If specific categories are selected, the missing categories are appended. |
countries | array | optional | Optional ISO 3166-1 alpha-2 markets. Empty means the actor's current egress and no proxy. Each selected country uses a per-country Apify proxy (AUTO first, residential only after a proven block). The feed follows the egress IP, not a query parameter. |
country | string | optional | Single-country shorthand. Ignored when countries is non-empty. |
postedWithin | string | optional | Client-side filter on createTime relative to the run start. |
minViews | integer | optional | Drop posts with fewer playCount views. Missing stats fail a positive threshold. |
minLikes | integer | optional | Drop posts with fewer likes. Missing stats fail a positive threshold. |
minComments | integer | optional | Drop posts with fewer comments. Missing stats fail a positive threshold. |
minShares | integer | optional | Drop posts with fewer shares. Missing stats fail a positive threshold. |
onlyNew | boolean | optional | Emit only posts that were not present in the named state store. State is read and written only when this is true. |
stateKey | string | optional | Optional key-value store key for onlyNew. When empty, a deterministic key is derived from categories, countries and filters. |
includeTranscripts | boolean | optional | Fetch subtitle WebVTT URLs and add subtitles[].text. Subtitle metadata is always returned when available; text is opt-in and fail-soft. |
includePostDetails | boolean | optional | Fetch the public post detail page for each selected post (bounded to the emitted maxItems) and merge locationCreated plus poi/imagePost when the feed omitted them. Detail requests are fail-soft: a 403, stub page or parse failure keeps the original post. Adds one page request per emitted row. |
rehostVideo | boolean | optional | Open each emitted post with Playwright to capture the real video/mp4 response, then HTTP-fetch the signed cover, originCover, dynamicCover, zoomCover, avatar, music, slideshow image and subtitle files. Every captured payload is stored in the run's default key-value store and the original signed URL is kept as a <field>Original value plus in mediaSources. video.playUrl and video.downloadUrl use tiktok-video-{postId}.mp4; other media use typed tiktok-{kind}-{postId}-... keys. Rehosting is fail-soft per field and never drops a post. It adds one browser page plus several media fetches per emitted record and increases run time and KV storage use; keep maxItemsPerCategory small. |
3. Start an Actor run
Set APIFY_TOKEN in your environment. Python 3.10 or newer is sufficient; this recipe uses no third-party packages.
Execution may incur charges. Review current pricing on Apify. The request asks for a 120-second timeout and a charge limit; these settings are subject to Apify’s pricing and do not guarantee an all-inclusive spending cap.
import json
import os
import urllib.request
# Save the schema-derived example as input.json; review and edit it first.
with open("input.json", encoding="utf-8") as stream:
actor_input = json.load(stream)
token = os.environ["APIFY_TOKEN"]
request = urllib.request.Request(
"https://api.apify.com/v2/acts/BvUQENdTgrozjghWJ/runs?timeout=120&maxTotalChargeUsd=0.50",
data=json.dumps(actor_input).encode("utf-8"),
headers={"Authorization": "Bearer " + token, "Content-Type": "application/json"},
method="POST",
)
# This starts a potentially billable run. Do not automatically retry this POST.
with urllib.request.urlopen(request, timeout=40) as response:
run = json.load(response)["data"]
print("Run ID:", run["id"])
print("Status:", run["status"])
# Inspect and download results in Apify Console once the run succeeds.
4. Inspect the actual results
Open the run in Apify Console and wait for a successful completion. Use the published output schema to locate results and the dataset schema to understand declared views. Missing or optional record fields must not be treated as guaranteed values.
Questions before you run
Which inputs are required?
The published required fields are categories
What does this guide verify?
The fields, prefills, and schema links are rendered from the published build. This is a usage recipe, not a paid live extraction test. Source availability and collection results can vary.
What if the run request times out?
Check Apify Console before retrying. The first request may already have created a run. Do not automatically repeat a run-creation POST.
Generated from source build 75caFFr2NTE3Pf7ul and its README and schemas. Source changes regenerate this guide; no third-party editorial claims are inferred.