How to Scrape TikTok: A Python Guide
- Plain requests half-works on TikTok profiles. I fetched public pages live in June 2026 and got HTTP 200 with parseable JSON every time, but the data is unreliable: @charlidamelio returned real stats (158.3M followers), while @nasa returned a junk anonymous payload (687 followers, 0 videos). Real output is pasted below.
- The static HTML carries profile stats only. The video feed (list of clips, captions, play counts) is not in the page; it loads from a separate signed API call that needs an
msTokenand device fingerprint. - TikTok's robots.txt sets
Disallow: /for every named AI and scraper bot (GPTBot, ClaudeBot, CCBot, Bytespider) and allows the wildcard agent only a short path list. - Watch for a real data quirk: TikTok's
heartCountis a signed 32-bit int, so accounts over ~2.1B likes wrap negative. I got-699115041for Charli, which is 3.60 billion once you add 2^32. - For the video feed and reliable runs at volume, a scraper API like ChocoData that renders pages and rotates residential IPs is the realistic route. Legal limits are unchanged by the tool.
TikTok is the social target people most often ask me to crack, and it behaves unlike Facebook or LinkedIn. Plain Python does not get slammed with a 403 at the door. It gets a 200 and a pile of JSON, which feels like a win until you read the numbers. I spent June 2026 testing what a logged-out requests scraper actually pulls from TikTok profiles. The result is genuinely mixed, and I have the real output below: some accounts return correct follower counts, some return obvious placeholder data, and the video feed is missing from the page entirely. This guide shows exactly what you can pull, where the walls are, and the realistic path for the data the static page withholds.
Can you legally scrape TikTok?
Scraping public TikTok data is not automatically illegal in the US, but it breaches TikTok’s Terms of Service and pulls you into data-protection law, so treat the legal picture as three independent layers. Winning on one says nothing about the others.
The first layer is computer-crime law. US courts have generally held that scraping data open to the public without a login does not violate the Computer Fraud and Abuse Act, the reasoning the Ninth Circuit used in hiQ Labs v. LinkedIn in 2022. That logic covers TikTok’s public profile surfaces too, which is why platforms lean on contract claims instead.
The second layer is TikTok’s own contract. TikTok’s Terms of Service prohibit collecting information from the platform using automated scripts, bots, spiders, or scrapers without permission. TikTok’s robots.txt backs this with a hard line for machines: every named AI and data bot (GPTBot, ClaudeBot, CCBot, Bytespider, PerplexityBot, and more) gets Disallow: /, and the wildcard User-agent: * is allowed only a short list of paths like /foryou, /discover, /tag, and /share. Profile paths (/@username) sit outside both the allow and disallow lists, so they fall to default-allow for the wildcard agent, but the Terms breach stands regardless of what robots.txt permits.
The third layer is data protection, and for TikTok it bites hard. Profiles are personal data: handles, real names, faces, bios, follower graphs. The moment you store information about an EU resident, GDPR applies, and a US scraper has no TikTok-granted basis to process it. TikTok’s track record here is not theoretical: in 2023 the UK’s ICO fined TikTok 12.7 million pounds over misuse of children’s data, and the Irish DPC fined it 345 million euros the same year. I cover the general framing in my is web scraping legal guide. For TikTok specifically, the defensible posture is narrow: collect only public, aggregate, non-personal fields (a creator’s follower count or video count, not a list of who commented), never log in a bot account, and get counsel before you store anything tied to identifiable people, especially minors.
What data is available on a public TikTok profile?
A public TikTok profile exposes a defined set of creator and engagement fields; private accounts, follower lists, DMs, and anything behind login are off limits. The table below splits what is publicly visible from what a plain HTTP request can actually fetch, because on TikTok those are two different questions. “In static HTML” means the field ships in the page’s embedded JSON; “via signed API only” means you need a separate authenticated call to get it.
| Surface | Field | Public? | Where it lives |
|---|---|---|---|
| Profile | Handle (uniqueId), user ID | Yes | Static HTML (when not degraded) |
| Profile | Nickname, bio, verified flag | Yes | Static HTML (when not degraded) |
| Profile | Follower, following, like, video counts | Yes | Static HTML (when not degraded) |
| Profile | Avatar image URL | Yes | Static HTML |
| Video feed | List of a creator’s videos | Yes | Signed API only (absent from HTML) |
| Video | Caption, play/like/comment counts | Yes | Signed API only |
| Video | Video/audio CDN URLs | Yes | Signed API only |
| Video | Comment text and authors | Personal data | Signed API only |
| Follower / following lists | Individual accounts | Personal data | Signed API only, gated |
| Private account | Anything | No | Login wall |
The line that matters for risk: aggregate profile metrics (a follower count, a video count) are the low-risk target. Comment threads and follower lists are personal data on real people, and that is where data-protection exposure concentrates. Build around the profile-level numbers, not the people.
Can you scrape a TikTok profile with Python requests? (Method 1: DIY)
Partly, and the honest answer is “unreliably.” I tested it in June 2026. Plain requests to a public profile returns HTTP 200 with a large JSON blob embedded in the page, under a <script id="__UNIVERSAL_DATA_FOR_REHYDRATION__"> tag. Parsing that blob gets you real stats for some accounts and placeholder junk for others, with no error to tell the two apart. Here is the exact code I ran:
import json
import re
import sys
import requests
UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36")
def scrape_profile(username):
url = f"https://www.tiktok.com/@{username}"
r = requests.get(url, headers={"User-Agent": UA}, timeout=20)
r.raise_for_status()
r.encoding = "utf-8" # force UTF-8 so emoji/accents decode
m = re.search(
r'<script id="__UNIVERSAL_DATA_FOR_REHYDRATION__"[^>]*>(.*?)</script>',
r.text, re.S)
if not m:
raise RuntimeError("rehydration blob missing - page was challenged")
detail = (json.loads(m.group(1))["__DEFAULT_SCOPE__"]
.get("webapp.user-detail", {}))
info = detail.get("userInfo", {})
user, stats = info.get("user", {}), info.get("stats", {})
return {
"handle": user.get("uniqueId"),
"nickname": user.get("nickname"),
"verified": user.get("verified"),
"followers": stats.get("followerCount"),
"following": stats.get("followingCount"),
"likes": stats.get("heartCount"),
"videos": stats.get("videoCount"),
"status_code": detail.get("statusCode"),
}
if __name__ == "__main__":
handle = sys.argv[1] if len(sys.argv) > 1 else "charlidamelio"
print(json.dumps(scrape_profile(handle), ensure_ascii=False, indent=2))
The real output, run June 2026 with requests 2.34.2 on Python 3.13.7:
=== @charlidamelio ===
{
"handle": "charlidamelio",
"nickname": "charli d?amelio",
"verified": true,
"followers": 158300000,
"following": 1394,
"likes": -699115041,
"videos": 3154,
"status_code": 0
}
=== @nasa ===
{
"handle": "nasa",
"nickname": "user3890627351453",
"verified": false,
"followers": 687,
"following": 0,
"likes": 0,
"videos": 0,
"status_code": 0
}
Read those two results side by side, because they are the whole story of scraping TikTok with plain requests. The @charlidamelio fetch is correct: 158.3M followers, verified, 3,154 videos. The @nasa fetch is junk in the same run, same code, same headers: nickname user3890627351453, 687 followers, 0 videos. TikTok decided per request whether to hand my logged-out client the real record or a degraded anonymous placeholder, and statusCode was 0 (“success”) for both, so there is no flag to branch on. A naive scraper would happily store “@nasa has 687 followers” and never know it was wrong.
Two quirks in that output are worth flagging so you do not chase phantom bugs:
nicknameshowscharli d?amelio. The real value is the name with a curly apostrophe; the?is my Windows console failing to print the character, not bad parse. The Python string holds the correct Unicode, which is why I setr.encoding = "utf-8"andensure_ascii=False. Write to a UTF-8 file or a database and it is fine.likesis-699115041, a negative like count. TikTok storesheartCountas a signed 32-bit integer, and Charli’s true total exceeds the 2,147,483,647 ceiling, so it wraps negative. Add 2^32 and you get the real figure:-699115041 + 4294967296 = 3595852255, about 3.60 billion likes. If you see a negative count, add 2^32; do not treat it as a parse error.
The bigger limit: this blob contains profile stats only. I checked all three saved pages, and the itemList (the array of the creator’s videos) was never populated; the only itemList mention was inside a share-description string. So there is no BeautifulSoup-the-video-grid trick here, because the videos are not in the HTML. If you are learning the stack, my requests guide and BeautifulSoup guide use targets that return their content in the page; TikTok hands you only the profile header.
Why does TikTok return junk data instead of blocking outright?
TikTok prefers soft blocks over hard ones, which is why you get a 200 with bad data rather than a clean 403. Understanding the layers tells you why the DIY path is unreliable and where a browser engine or scraper API becomes mandatory.
| Layer | What it does | What it blocks |
|---|---|---|
| Anonymous-payload gating | Serves real or placeholder stats per request based on msToken/IP signals | Reliable profile stats from bare clients |
| Signed video API | Feed needs msToken + X-Bogus/device params on a separate XHR | Any non-browser request for the video list |
| Captcha challenge | Returns a verify page to suspect traffic | Datacenter IPs, repetitive patterns |
| Rate limits | Throttles fast or high-volume traffic | Bulk scraping from one IP |
| Login wall | Gates follower lists and some deep data | Logged-out deep access |
The practical reading: my test cleared the page fetch (layer one) but landed on the anonymous-payload coin flip, which is why @nasa came back wrong. The video feed sits behind layer two, a separate API call signed with an msToken cookie and obfuscated device parameters that TikTok generates in-browser; reproducing that signing by hand is the part that breaks every few weeks. My saved profile HTML also contained 26 references to captcha, the scaffolding for layer three, which TikTok triggers when it dislikes your traffic. Push volume from a datacenter IP and layers three and four shut you down fast. Each layer is a separate engineering problem, which is why scraping TikTok yourself is a maintenance project, not a script. My scraping without getting blocked guide covers the headers, fingerprinting, and IP-rotation tactics these layers force on you.
A browser-automation approach with Playwright clears more layers, because a real browser executes the JavaScript that mints the msToken and signs the video-feed XHR, so you can read the requests the page itself makes. I did not staple a Playwright TikTok script with pasted output to this article, because doing it properly means driving a real browser session and intercepting signed network calls, and the moment I show numbers I did not capture cleanly I have crossed into fabrication. If you want Playwright working end to end with real output, that belongs in a dedicated browser-scraping guide on a target I can show start to finish.
How do you scrape TikTok video feeds at scale? (Method 2: scraper API)
Route the request through a scraper API that runs a real browser, mints the tokens, rotates residential IPs, and returns the data, then you parse the result. For TikTok this is the route to the video feed and to reliable profile stats, because the DIY path gives you neither dependably. A scraper API absorbs the token signing, the IP rotation, and the captcha handling that you would otherwise rebuild and maintain.
ChocoData is one such service, with a universal endpoint plus 453 dedicated endpoints across 235 sites. The request shape is a single GET with your target URL and an API key, with JavaScript rendering switched on so the page mints its tokens and loads the feed:
import requests
API_KEY = "YOUR_CHOCODATA_KEY"
target = "https://www.tiktok.com/@charlidamelio"
r = requests.get(
"https://api.chocodata.com/api/v1/universal",
params={
"api_key": API_KEY,
"url": target,
"render_js": "true",
},
timeout=60,
)
print("STATUS:", r.status_code)
# r.text holds the rendered HTML/JSON after the page's own scripts run,
# so the video feed that was absent from a plain fetch is now present.
# Parse the profile fields and the itemList once you confirm the live
# shape against ChocoData's docs.
I did not run this snippet, because it needs a paid key, so treat the parameter names as illustrative and confirm them against ChocoData’s own docs before you build on them. The principle holds across any reputable scraper API: you hand it the URL, it returns the rendered result from a rotating residential IP, and TikTok’s payload gating, token signing, and captcha challenges become the vendor’s problem instead of yours. ChocoData’s published pricing in mid-2026 starts at $19/mo (Vibe plan) with 1,000 free requests/mo and no card, billing 5 credits per request plus 10 for JavaScript rendering, and charging only 2xx responses, so failed fetches do not cost you.
One thing a scraper API leaves untouched: the legal layer. Routing through proxies grants you no permission under TikTok’s Terms, and it offers no GDPR cover if you collect personal data. Use this for public, aggregate data (follower counts, video counts, public captions and play counts), keep comment authors and follower lists out of your store, and get counsel before any collection touches identifiable EU residents or minors.
Should you build your own TikTok scraper or use a scraper API?
It depends on what you need. For a one-off check of a handful of public profile stats, the DIY script above is free and fine, as long as you verify the numbers and handle the junk-payload case. For the video feed, or for reliable runs at volume, a scraper API is the realistic route, because the feed is absent from the HTML and the per-request gating makes bare fetches untrustworthy. Here is the comparison:
| Factor | DIY (requests/Playwright) | Scraper API |
|---|---|---|
| Profile stats | HTTP 200, but real-or-junk per request | Rendered reliably |
| Video feed | Absent from HTML; needs signed API | Returned after JS render |
Token signing (msToken/X-Bogus) | You reverse-engineer and maintain it | Vendor handles it |
| Captcha challenges | You solve or get blocked | Vendor absorbs them |
| IP rotation at volume | You buy and rotate proxies | Built in |
| Maintenance when TikTok changes | You fix it every few weeks | Vendor maintains |
| Cost | ”Free” but high engineering time | Paid per successful request |
| Terms of Service breach | Yes | Yes |
| GDPR exposure on personal data | Yes | Yes |
The pattern I would actually follow: use the DIY script for quick, low-volume profile-stat lookups, with a sanity check that rejects obvious placeholder payloads (a “verified” creator with 0 videos is a tell). For the video feed or any production workload, reach for a scraper API so you are not maintaining TikTok’s token-signing scheme by hand. Either way, keep the scope to public, aggregate data, because no tool removes the Terms breach or the data-protection duty, and TikTok’s regulators have shown they enforce it.
For the fundamentals under all of this, including headers, rendering, and parsing, see the web scraping guide. The Python scraping guide covers the requests-and-parsing stack on targets that cooperate, the Scrapy guide scales it to many pages, and scraping without getting blocked goes deep on the anti-bot tactics targets like TikTok force on you.
FAQ
Scraping public TikTok data is not automatically a crime in the US, but it breaches TikTok's Terms of Service, which prohibit collecting data with automated scripts or bots. TikTok data is also personal data (handles, faces, bios), so GDPR applies the moment you store info on EU residents, and the UK ICO has already fined TikTok over children's data. Public visibility does not equal permission to collect and republish at scale.
Not the video feed from the profile HTML. In my June 2026 test, the static profile page carried only aggregate stats (followers, likes, video count); the actual list of videos was absent. TikTok loads the feed through a separate API call signed with an msToken and X-Bogus/device parameters, so reproducing it without a browser engine or a scraper API is brittle and breaks often.
TikTok served you a degraded anonymous payload instead of the real profile. In my test @nasa came back with nickname user3890627351453, 687 followers, and 0 videos, all wrong, while other handles in the same run returned correct numbers. TikTok decides per request whether to hand a logged-out client real data, based on signals like the msToken cookie and IP reputation. Without those, you get placeholder data with HTTP 200, no error.
Yes, with soft blocks more than hard ones. Plain requests rarely gets a 403 on a profile page; instead TikTok returns 200 with either real or junk data, and serves captcha challenges to traffic it dislikes (my saved HTML contained 26 captcha references). The video API is signed and rate-limited. Datacenter IPs and high request volume trigger challenges fast, which is why residential rotation and a browser engine become necessary at scale.