What the TikTok Profile scraper collects
Extracts public statistics from TikTok profile pages: exact follower, following, like and video counts, the bio and its link, verification, organisation and TikTok Shop seller flags, account creation date and the settings a creator publishes — 41 columns per account. The default transport is plain HTTPS, with a browser fallback when TikTok actually challenges. Output is JSON or CSV with a .meta.json sidecar.
Single profile
Input: A handle, an @handle, or a full profile URLOne row with the account's exact counts from statsV2, bio, bio link, verification, seller flags, creation date and published settings.
Many profiles in parallel
Input: A comma-separated list of handles or URLsOne row per account, fetched concurrently; handles TikTok has no account for are listed in the sidecar's handles_unavailable rather than reported as a block.
Without a local browser
Input: A handle, via the 2Captcha Scraper APIThe same row shape fetched through 2Captcha's Scraper API, for running where no browser and no direct connection are wanted.
From zero to TikTok Profile data in 3 steps
Install
Clone the repository and install one browser engine. Python 3.9+.
$ git clone https://github.com/2scraper/tiktok-profile-scraper $ cd tiktok-profile-scraper $ python3 -m venv venv $ ./venv/bin/pip install -r requirements.txt -r requirements-playwright.txt $ ./venv/bin/python playwright_scraper.py --url nasa
Run a scraper
Pick what you need and run it. Every flag is documented in the README.
$ ./venv/bin/python playwright_scraper.py --url nasa$ ./venv/bin/python playwright_scraper.py --url nasa,khaby.lame,zachking,tiktok --concurrency 4 --format both$ ./venv/bin/python scraper_api_client.py --url nasaGet structured data
Rows land in JSON or CSV (
--format), with a.meta.jsonsidecar recording status, pages fetched and why the run stopped.
TikTok Profile data fields
41 columns per row in a predictable schema. The most useful ones:
| Field | What it holds |
|---|---|
user_id | Permanent numeric account id, the join key (also in sku) |
username | The @handle, which the account can change |
sec_uid | TikTok's own per-account token |
follower_count | Exact follower count, from statsV2 |
following_count | Accounts this one follows |
likes_count | Total likes received |
video_count | Number of videos |
likes_per_video | likes_count / video_count; null with no videos |
stats_source | statsV2, stats or absent — which object the counts came from |
verified | Verification badge |
is_seller | TikTok Shop seller flag |
bio | Profile bio text |
bio_link | The link in the bio, where set |
avatar_id | Stable content hash of the avatar; changes only when the picture does |
created_at | Account creation date, ISO-8601 UTC |
private_account | Whether the account is private |
[
{
"source": "tiktok.com",
"scraped_at": "2026-09-22T14:51:09Z",
"url": "https://www.tiktok.com/@nasa",
"sku": "7664638705177150477",
"title": "NASA",
"username": "nasa",
"user_id": "7664638705177150477",
"sec_uid": "MS4wLjABAAAAU9BRVzC8oCaegVnia8IbqWhPb_-dbU7s00Y3wS1_Nx8g5RUaYvyXrpejgjdxTwd6",
"follower_count": 1785780,
"following_count": 23,
"likes_count": 9419647,
"video_count": 46,
"friend_count": 17,
"digg_count": 0,
"likes_per_video": 204774.93,
"stats_source": "statsV2",
"verified": true,
"private_account": false,
"is_organization": true,
"is_seller": false,
"commerce_user": false,
"secret": false,
"bio": "Making the seemingly impossible, possible.✨",
"bio_link": null,
"avatar_url": "https://p16-common-sign.tiktokcdn-eu.com/tos-maliva-avt-0068/e5143212a59c09a008bf50f487…",
"avatar_id": "e5143212a59c09a008bf50f487d54d1f",
"avatar_expires_at": "2026-09-24T14:00:00+00:00",
"language": "en",
"created_at": "2026-07-20T15:55:49+00:00",
"nickname_modified_at": "2026-07-20T15:58:00+00:00",
"comment_setting": 0,
"duet_setting": 0,
"stitch_setting": 0,
"download_setting": 0,
"following_visibility": 1,
"open_favorite": false,
"show_playlist_tab": true,
"status": "ok",
"status_code": 0,
"page": 1
},
{
"source": "tiktok.com",
"scraped_at": "2026-09-22T14:51:09Z",
"url": "https://www.tiktok.com/@khaby.lame",
"sku": "127905465618821121",
"title": "Khabane lame",
"username": "khaby.lame",
"user_id": "127905465618821121",
"sec_uid": "MS4wLjABAAAAwAg0rSzO65WQfz4RzQgGv2Xdv108BgPXhRrrmNVIHQZ9PO8-flwwRtEppYTS0OjA",
"follower_count": 162986056,
"following_count": 81,
"likes_count": 2680889555,
"video_count": 1353,
"friend_count": 77,
"digg_count": 0,
"likes_per_video": 1981440.91,
"stats_source": "statsV2",
"verified": true,
"private_account": false,
"is_organization": false,
"is_seller": false,
"commerce_user": false,
"secret": false,
"bio": "Se vuoi ridere sei nel posto giusto😎 If u wanna laugh u r in the right place😎",
"bio_link": null,
"avatar_url": "https://p16-common-sign.tiktokcdn-eu.com/tos-maliva-avt-0068/08987e23b94057953fd4f17386…",
"avatar_id": "08987e23b94057953fd4f1738694bf5f",
"avatar_expires_at": "2026-09-24T14:00:00+00:00",
"language": "en",
"created_at": "2016-08-10T22:02:34+00:00",
"nickname_modified_at": null,
"comment_setting": 0,
"duet_setting": 0,
"stitch_setting": 0,
"download_setting": 0,
"following_visibility": 1,
"open_favorite": false,
"show_playlist_tab": false,
"status": "ok",
"status_code": 0,
"page": 1
}
]First 2 of 3 rows from a real run — sample_output.json. Long values shortened here.
Handles what breaks naive TikTok Profile scrapers
Exact counts, not rounded ones
Every profile carries a stats object rounded to three significant figures (up and down — @tiktok was off by 43,287) and an exact statsV2. Every row reads statsV2, and stats_source plus the sidecar make a run of rounded numbers visible.
Plain HTTP first, browser when challenged
Profiles are server-rendered, so plain HTTPS took 1.54 s against the browser's 3.38 s for identical rows. --transport auto starts a browser the moment TikTok's WAF interstitial appears, which a browser cleared 3 of 3 times.
Stable avatar tracking
The avatar URL is re-signed on every request and 403s without its signature. avatar_id, the stable content hash, is what diff_runs.py tracks, and avatar_expires_at says when the link dies.
"User banned" is not a block
TikTok answers a nonexistent handle with HTTP 200 and "user banned". The run lists it in handles_unavailable, exits 4 if nothing else was found, and never reports it as a block.
Scraping infrastructure included
The same tested core runs every 2scraper repository, so the hard parts work the same way on every site.
CAPTCHA handling
Detects the challenge a page really serves and, with a 2Captcha key, solves it only when it blocks the data — a capped number of solves per page.
Proxy rotation
Single proxy or a pool from a file, rotated on blocks with a fresh browser per exit. Credentials stay out of argv and logs.
Fingerprints
Optional complete device identity from the 2Captcha Fingerprint API — user agent, client hints, locale and timezone that agree with each other.
Scraping Browser API
Point --cdp-endpoint at a managed Chromium with its own exit country — no browser infrastructure to run yourself.
Honest run metadata
Every run writes a .meta.json sidecar and distinct exit codes: complete, blocked, empty and partial are never confused, and a failed run never overwrites good data.
Diff two runs
diff_runs.py compares two complete TikTok Profile snapshots by id — new, removed and changed rows for monitoring.
What teams do with TikTok Profile data
Influencer vetting
Compare exact follower, like and video counts, likes per video and verification across a list of creators.
Audience growth tracking
Run the same handles daily and diff exact counts by the permanent user_id, even if a handle changes.
TikTok Shop seller discovery
Flag accounts that are TikTok Shop sellers or commerce users, as a starting point for tiktok-shop-scraper.
Brand and organisation monitoring
Track organisation accounts' bios, bio links and published comment, duet and stitch settings over time.
Free to run. Pay only for what the site requires.
Runs with no key, proxy or account
A TikTok profile page is server-rendered and served to anyone: measured 2026-09-22 from a bare datacentre address, plain curl and headless and headful Chromium were all served, and the daily canary runs with no secrets. A residential proxy was measured worse than none on this route (2 of 9 exits served to a plain HTTP client). 2Captcha's products buy volume from many addresses, a specific exit country and no browser infrastructure of your own — not access.
Optional 2Captcha services — captcha solving, the Scraping Browser API, residential proxies and fingerprints — work behind one key.
Good to know
- A nonexistent handle and a banned account get the same answer from TikTok, and this repo does not claim to tell them apart.
- The signed avatar_url expires about 48 hours after the run.
- Selenium cannot use an authenticated CDP endpoint or proxy.
2scraper vs. hosted scraper APIs
| 2scraper | Typical hosted API | |
|---|---|---|
| Code | Open source, MIT — read and patch it | Closed |
| Cost | Free; optional services billed only when used | Subscription or per request / per result |
| Runs on | Your machine, server or CI | The vendor's infrastructure |
| Output | JSON/CSV files + run metadata | API responses |
| When the site changes | Fix it yourself or open an issue / PR | Wait for the vendor |
TikTok Profile scraper FAQ
Do I need a TikTok account, key or proxy?
No. For fewer than a few thousand accounts from one address, the README says you need none of the paid products.
Why do my counts differ from other tools?
Most read TikTok's rounded stats object. This repo reads the exact statsV2 object; the README shows rounding errors of up to tens of thousands of followers on large accounts.
Why do two runs an hour apart differ?
The counts move: nine refetches of one account within an hour returned nine different follower counts. diff_runs.py reports those as real changes.
Does it scrape an account's videos?
No. The profile's video-grid API answered HTTP 200 with an empty body to every variant tried, so this repo reports that as blocked; videos are covered by tiktok-video-scraper from the embed route and video pages.
What does --locale change?
Only the page chrome. ?lang=ja and ?lang=ar returned byte-identical account data.
Is scraping TikTok profiles legal?
It reads public profile data any visitor is served. It is public data; follow TikTok's terms and the law where you operate.