How to Scrape TikTok Videos (2026)
- A single Python
requests.getto a TikTok video URL returned HTTP 200 with the whole video object embedded in a__UNIVERSAL_DATA_FOR_REHYDRATION__script tag when I tested in July 2026. No browser and no proxy for one public video. - Each video object carries
playCount,diggCount(likes),commentCount,shareCount, themusicblock, hashtags, and two CDN links:playAddr(usually clean) anddownloadAddr(watermarked). - One video is free from the page HTML. A creator's full catalog comes from
/api/post/item_list/, which is signed withX-BogusandmsTokenand needs residential IPs. That is the point where a raw script stalls. - For volume, a scraper API takes a video URL or a username and returns parsed JSON plus a no-watermark URL, with the signing and proxies handled server side.
I scraped my first TikTok video the blunt way: one requests.get against a video URL, no browser and no proxy. The page came back 200, and the entire video object - play count, likes, the music, and a playable CDN link - was sitting inside a single script tag in the HTML. That is the core of how to scrape TikTok videos, and most of this guide is about that one tag: how far it gets you, what each field means, and the exact point where TikTok makes you work for the rest.
Below is the code I ran in July 2026, the JSON keys the data came back in, and how to scale from one video to a creator’s whole catalog without getting blocked.
What is the fastest way to scrape TikTok videos?
The fastest way to scrape TikTok videos is to read the JSON that TikTok already server-renders into every public video page, with no hidden endpoint needed for the first pull. When you request a video URL with a browser User-Agent, TikTok returns the rendered HTML with a script tag named __UNIVERSAL_DATA_FOR_REHYDRATION__. The browser uses that JSON to hydrate the page, and a scraper can parse the same JSON to get the video’s stats, music, hashtags, and download URLs directly.
That covers one video at a time. Four routes exist for scraping TikTok videos, and they scale differently:
- Page JSON (a plain HTTP request): free, no signing, good for a single video or the first batch on a profile.
- Internal feed endpoint (
/api/post/item_list/): a creator’s full catalog, but every call is signed. - Browser automation (Playwright): lets the real page mint the signature for you, at a heavy resource cost.
- Scraper API: you send a URL or username and get parsed JSON back, with signing and proxies handled server side.
The next section starts with the free route, because for a handful of videos it is genuinely all you need.
How do you scrape a single TikTok video with Python?
You scrape a single TikTok video with Python by fetching its public page and parsing the video object out of the embedded JSON. A video URL looks like https://www.tiktok.com/@nasa/video/7300000000000000000, and the page ships with the same __UNIVERSAL_DATA_FOR_REHYDRATION__ tag a profile page carries. Install the one dependency, then fetch and parse.
pip install requests
import requests, re, json
UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36")
# Swap in a real video URL
url = "https://www.tiktok.com/@nasa/video/7300000000000000000"
r = requests.get(url, headers={"User-Agent": UA}, timeout=25)
print(r.status_code) # -> 200
m = re.search(
r'<script id="__UNIVERSAL_DATA_FOR_REHYDRATION__"[^>]*>(.*?)</script>',
r.text, re.S,
)
data = json.loads(m.group(1))
item = data["__DEFAULT_SCOPE__"]["webapp.video-detail"]["itemInfo"]["itemStruct"]
print(item["desc"]) # caption
print(item["stats"]["playCount"], "plays")
print(item["stats"]["diggCount"], "likes")
print(item["stats"]["commentCount"], "comments")
print(item["music"]["title"], "-", item["music"]["authorName"])
print(item["video"]["playAddr"][:60], "...") # clean CDN stream
The single video page nests its data one level deeper than a profile does. Where a profile keeps user data under webapp.user-detail, a video page keeps the post under webapp.video-detail, and the actual video object is itemInfo.itemStruct. From there, stats holds the counts, music holds the sound, video holds the CDN URLs, and textExtra holds the hashtags. That one itemStruct is the whole video.
One caveat worth setting up front, because it surprises people. The video.playAddr link is bound to the IP and headers of the request that fetched it, and it expires within minutes. A URL you pull on a server will often return a 403 when you paste it into a browser later, so download the file in the same session that fetched the page rather than storing the link for a nightly job. With one video parsed, the next question is how to get every video a creator has posted, which the page JSON does not give you.
How do you scrape all of a creator’s videos at scale?
You scrape all of a creator’s videos by paging the internal item-list endpoint with the secUid from their profile, not by requesting one video page at a time. A profile page carries the secUid and only the first batch of videos inside its rehydrated JSON. Everything past that comes from https://www.tiktok.com/api/post/item_list/, which returns an itemList array, a cursor for the next page, and a hasMore boolean that tells you when to stop.
The loop is the whole pattern. You read itemList, check hasMore, and feed the cursor value back into the next request until hasMore flips to false.
def fetch_videos(sec_uid, signed_url_builder, pages=5):
cursor_value = "0"
videos = []
for _ in range(pages):
params = {"secUid": sec_uid, "count": 35, "cursor": cursor_value}
# TikTok rejects this URL unless it is signed (see below)
url = signed_url_builder("/api/post/item_list/", params)
data = requests.get(url, timeout=25).json()
videos.extend(data.get("itemList", [])) # each item is a full video object
cursor_value = data.get("cursor") # carry forward
if not data.get("hasMore", False): # False means last page
break
return videos
Two variables drive that function: cursor_value, which you read out of each response and pass into the next call, and the hasMore flag, which ends the loop. Each item in itemList is the same video object shape you parsed from a single video page, so once this loop runs you have every video’s stats and CDN URLs in one pass.
The catch hides behind signed_url_builder. A raw requests.get to /api/post/item_list/ does not behave like the profile fetch: the endpoint is signed, and an unsigned call returns HTTP 200 with an empty body. TikTok expects a valid X-Bogus parameter and an msToken, both generated by an obfuscated virtual machine that runs in the browser, and Scrapfly’s teardown documents how that signature is derived from the signed URL, the msToken, and the User-Agent.
Reproducing that signature in Python means running TikTok’s signer in a JS runtime or driving a real browser, which is what the open-source TikTok-Api library does under the hood. With the feed mapped, the useful next step is knowing what each field in a video object actually means.
What data does a TikTok video object contain?
A TikTok video object contains the engagement stats, the music, the hashtags, the author, and two CDN download URLs, all nested under one item in the JSON. These are the fields a video scraper actually needs, keyed as TikTok’s web page returns them.
| Field | What it holds |
|---|---|
id | The video id (also called aweme_id) |
desc | The caption text |
createTime | Upload time as a Unix timestamp |
stats.playCount | Plays / views |
stats.diggCount | Likes (hearts) |
stats.commentCount | Comments |
stats.shareCount | Shares |
stats.collectCount | Saves / favorites |
music.title / music.authorName / music.id | The sound behind the video |
textExtra[].hashtagName | Each hashtag on the post |
video.playAddr | The stream URL, usually without the burned-in watermark |
video.downloadAddr | The download URL, with the TikTok watermark |
The playAddr versus downloadAddr split is the one most people miss. playAddr is usually the clean stream with no watermark overlay, while downloadAddr is the watermarked file, so read playAddr when a clean video is the goal. The music block can come back sparse or empty for some tracks depending on licensing, so treat the sound fields as best-effort and null-check them rather than assuming every video reports a music.id.
If you also use the official TikTok Research API, it names the same numbers differently. Its Query Videos endpoint returns view_count, like_count, comment_count, share_count, music_id, and hashtag_names, which is a useful schema baseline even though its access rules are far narrower than the public page. Knowing the fields is only half the job, though, because getting them consistently means not getting blocked.
How do you scrape TikTok videos without getting blocked?
You avoid getting blocked when scraping TikTok videos by fixing three signals in order: the User-Agent, the IP reputation, and the request rate. The signature requirement on the feed endpoint is a separate problem, but blocking is what stops most scrapers first, and the User-Agent is the cheapest signal to fix.
The single most useful test I ran was the User-Agent. The two requests below differ by one header and nothing else:
| Request | User-Agent | Status | Body |
|---|---|---|---|
GET /@nasa/video/... | Python default (python-requests/2.x) | 403 | 0 bytes |
GET /@nasa/video/... | Chrome 126 desktop string | 200 | full page |
A blank 403 is the signature of a missing or non-browser User-Agent, so set a real one first. After that, the levers that moved results in my testing, in rough order of impact:
- Send a real browser User-Agent. A datacenter-looking client gets
403before TikTok serves any HTML. - Use residential or mobile IPs. TikTok flags datacenter and known-proxy ranges, so residential IPs that present as ordinary home connections last longer against repeated requests.
- Slow down and randomize. A steady cadence of one request every few seconds per IP survives where parallel bursts get throttled. Random delays of 2 to 5 seconds are a safe start.
- Respect what is fenced off. TikTok’s robots.txt sets a flat
Disallow: /for a long list of named crawlers and disallows/search?for the catch-all agent, so it states which surfaces the platform considers off-limits even though it carries no technical enforcement on its own.
Doing all of this yourself means renting a residential proxy pool, rotating it, faking realistic headers, and, for the feed endpoint, generating the X-Bogus signature on every call. That becomes a standing maintenance project past a few thousand videos, which is why most teams move the blocking and signing to an API. That route is the last method, and it is the shortest code in this guide.
How do you scrape TikTok videos without managing proxies or signatures?
You scrape TikTok videos without managing proxies or signatures by sending a target to a scraper API and getting parsed JSON back, with the IP rotation, browser fingerprint, and X-Bogus signing handled on the server side. You send one request with your API key and get structured video data, with no 403 to debug and no signer to maintain.
With ChocoData, a creator’s full video feed is a single call. The TikTok endpoints live under the tiktok path, and you pass your key as api_key:
curl "https://chocodata.com/api/v1/tiktok/video?username=nasa&api_key=$CHOCO_API_KEY"
Pass a single post’s url instead of username to pull one video rather than the whole feed. The Python version is the same one request, and it returns the same stats, music, and video fields you would have parsed by hand, already cleaned:
import os, requests
resp = requests.get(
"https://chocodata.com/api/v1/tiktok/video",
params={"username": "nasa", "api_key": os.environ["CHOCO_API_KEY"]},
timeout=30,
)
feed = resp.json()
first = feed["itemList"][0]
print(first["stats"]["playCount"])
print(first["video"]["playAddr"]) # no-watermark CDN URL
The trade is straightforward. For a one-off pull of a few videos, the raw requests script at the top of this guide is genuinely all you need, and it is free. For continuous collection across many creators and hashtags, the signature breaks on TikTok’s schedule and datacenter IPs get blocked, so offloading both is usually cheaper than your own time once you price it in. Here is how the four routes compare on the decisions that pick one:
| Route | Auth | Signing / proxies | Scales to | Best for |
|---|---|---|---|---|
Page JSON (requests) | None (browser UA) | Not needed (single video) | One video or first page | A handful of public videos |
| Feed endpoint yourself | None | You build and maintain it | A creator’s catalog | Full control, if you own the upkeep |
| Browser automation (Playwright) | None | Handled by the browser | Feeds, slowly | Signed data without reversing X-Bogus |
| Scraper API (ChocoData) | API key | Handled server side | Continuous, high volume | Production pipelines |
Before you collect at volume, the legal footing matters as much as the code. US courts have repeatedly protected scraping of public, logged-out data, and the Ninth Circuit in hiQ v. LinkedIn held that the CFAA does not bar access to public pages. Video content carries copyright, creator data can be personal data under GDPR, and TikTok’s Terms of Service are a separate question, so I cover the full picture in is scraping TikTok legal. For the language-agnostic build and the profile-first workflow, see how to scrape TikTok with Python, and for a ranked comparison of managed options, best TikTok scrapers in 2026.
FAQ
Can you scrape TikTok videos without an API key?
Yes, for public video and profile pages. TikTok server-renders the video's data into a __UNIVERSAL_DATA_FOR_REHYDRATION__ script tag that a plain requests.get with a browser User-Agent can fetch and json.loads can parse. The limit is volume: a creator's full video feed loads from the signed /api/post/item_list/ endpoint, so a keyless script handles single videos and stalls past the first page.
How do you download a TikTok video without a watermark?
Read the video.playAddr URL from the video object, which usually points at the clean stream on a tiktokcdn.com host, rather than video.downloadAddr, which is the watermarked file. Two caveats from my testing: those CDN URLs are tied to the requesting IP and headers, and they expire quickly, so a link pulled on a server often will not play in a browser later.
How do you get the play count and like count of a TikTok video?
Both live in the stats block of the video object: playCount is plays or views and diggCount is likes (hearts), alongside commentCount, shareCount, and collectCount for saves. The official TikTok Research API names the same numbers differently, as view_count, like_count, comment_count, and share_count.
Why does my TikTok video scraper return an empty response?
An empty body or empty array almost always means you called a signed internal endpoint (like /api/post/item_list/) without a valid X-Bogus signature and msToken, or you sent the request from a datacenter IP that TikTok verify-walls. A single public video page returns full HTML with a browser User-Agent, while the feed endpoints behind it need the signature and a residential IP.
Is it legal to scrape TikTok videos?
Scraping public, logged-out TikTok video pages sits on the safer side of recent US case law, but video content and creator data can carry copyright and personal-data obligations, and TikTok's Terms of Service restrict automated access. This is general information, not legal advice. I work through the specifics in the legal guide linked below.