1. 2scraper
  2. Social media & video
  3. TikTok Video
Open source · MITRuns with no key, proxy or account

TikTok Video Scraper

TikTok videos with exact engagement, sound, hashtags, subtitle tracks and media URLs — no key, proxy or account.

  • Plain HTTP
  • Playwright
  • Selenium
  • Puppeteer
  • Scraping Browser API
  • JSON
  • CSV
tiktok-video-scraper
# Account's recent videos
$ ./venv/bin/python playwright_scraper.py --url nasa,zachking --format both
✓ 11 sample rows · 57 columns in sample_output.json
0keys, proxies or accounts needed
10–12recent videos per account the route serves
2server-rendered routes: embed and video page
4transports: plain HTTPS + 3 browser engines
Available TikTok Video scrapers

What the TikTok Video scraper collects

Reads an account's recent videos or any named video from TikTok's server-rendered embed and video pages. Each row carries the caption, creation date, exact likes, comments, shares and saves, the sound, hashtags and mentions, caption tracks, direct media URLs and photo-post stills, as JSON or CSV.

Account's recent videos

Input: A handle, @handle or profile URL (comma-separated for several)

Reads the account's embed window, then each video's own page for the full row — engagement, sound, hashtags, subtitles.

Named videos

Input: Video URLs or bare 19-digit video ids

One full row per video; every video has its own address, so they can be fetched in parallel.

Fast embed-only rows

Input: A handle

Skips the per-video pages: caption, rounded play count and media URLs only, about 6× faster, with data_source marking every row as embed.

Quick start

From zero to TikTok Video data in 3 steps

  1. Install

    Clone the repository and install one browser engine. Python 3.9+.

    $ git clone https://github.com/2scraper/tiktok-video-scraper && cd tiktok-video-scraper
    $ python3 -m venv venv
    $ ./venv/bin/pip install -r requirements.txt -r requirements-playwright.txt
    $ ./venv/bin/python playwright_scraper.py --url nasa
  2. 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,zachking --format both
  3. Get structured data

    Rows land in JSON or CSV (--format), with a .meta.json sidecar recording status, pages fetched and why the run stopped.

Output

TikTok Video data fields

57 columns per row in a predictable schema. The most useful ones:

FieldWhat it holds
video_idTikTok's 19-digit video id
descriptionThe video caption
created_atCreation time (UTC)
author_usernameThe creator's handle
play_countPlay count, rounded by TikTok
digg_countExact like count
comment_countExact comment count
share_countExact share count
collect_countExact save count
music_titleThe sound's title
hashtagsHashtags from TikTok's own entity data
mentionsMentioned accounts by real handle, not display text
subtitle_urlDownloadable WebVTT caption track
download_addrDirect media URL (signed, expires)
content_typevideo or photo (photo-mode carousel)
data_sourceWhich route(s) produced the row
[
  {
    "source": "tiktok.com",
    "scraped_at": "2026-09-22T15:16:46Z",
    "url": "https://www.tiktok.com/@nasa/video/7665075736742530317",
    "sku": "7665075736742530317",
    "title": "Something big just landed on TikTok.",
    "video_id": "7665075736742530317",
    "description": "Something big just landed on TikTok.",
    "created_at": "2026-07-21T20:09:02+00:00",
    "author_username": "nasa",
    "author_id": "7664638705177150477",
    "author_nickname": "NASA",
    "author_sec_uid": "MS4wLjABAAAAU9BRVzC8oCaegVnia8IbqWhPb_-dbU7s00Y3wS1_Nx8g5RUaYvyXrpejgjdxTwd6",
    "author_verified": true,
    "play_count": 1400000,
    "digg_count": 91500,
    "comment_count": 2756,
    "share_count": 3078,
    "collect_count": 6767,
    "repost_count": 0,
    "stats_source": "statsV2",
    "content_type": "video",
    "image_count": null,
    "image_urls": null,
    "image_title": null,
    "duration_seconds": 25,
    "width": 720,
    "height": 1280,
    "ratio": "720p",
    "definition": "720p",
    "video_format": "mp4",
    "video_size_bytes": 3356735,
    "bitrate": 1066942,
    "play_addr": "https://v16-webapp-prime.tiktok.com/video/tos/alisg/tos-alisg-ve-37c799-sg/okRjOkUA6WdC…",
    "download_addr": "https://v16-webapp-prime.tiktok.com/video/tos/alisg/tos-alisg-ve-37c799-sg/osBro6wCGRle…",
    "cover_url": "https://p16-common-sign.tiktokcdn-eu.com/tos-useast5-p-0068-tx/oAUhkWfCZC7g6ALj2HZgOeEL…",
    "dynamic_cover_url": "https://p16-common-sign.tiktokcdn-eu.com/tos-useast5-p-0068-tx/ogCUKkOrAAd2Hj0YAgL4vokG…",
    "media_expires_at": "2026-09-24T15:17:12+00:00",
    "music_id": "7665075815390645006",
    "music_title": "original sound",
    "music_author": "NASA"
  },
  {
    "source": "tiktok.com",
    "scraped_at": "2026-09-22T15:16:46Z",
    "url": "https://www.tiktok.com/@nasa/video/7686893699099413773",
    "sku": "7686893699099413773",
    "title": "Another big week in the sky and beyond at NASA 🚀   🔭 Roman gets a major mission-life bo…",
    "video_id": "7686893699099413773",
    "description": "Another big week in the sky and beyond at NASA 🚀   🔭 Roman gets a major mission-life bo…",
    "created_at": "2026-09-18T16:00:00+00:00",
    "author_username": "nasa",
    "author_id": "7664638705177150477",
    "author_nickname": "NASA",
    "author_sec_uid": "MS4wLjABAAAAU9BRVzC8oCaegVnia8IbqWhPb_-dbU7s00Y3wS1_Nx8g5RUaYvyXrpejgjdxTwd6",
    "author_verified": true,
    "play_count": 130500,
    "digg_count": 9698,
    "comment_count": 316,
    "share_count": 348,
    "collect_count": 781,
    "repost_count": 0,
    "stats_source": "statsV2",
    "content_type": "video",
    "image_count": null,
    "image_urls": null,
    "image_title": null,
    "duration_seconds": 92,
    "width": 720,
    "height": 1280,
    "ratio": "720p",
    "definition": "720p",
    "video_format": "mp4",
    "video_size_bytes": 10727129,
    "bitrate": 926049,
    "play_addr": "https://v16-webapp-prime.tiktok.com/video/tos/alisg/tos-alisg-ve-37c799-sg/oc5dqgYAEIsc…",
    "download_addr": "https://v16-webapp-prime.tiktok.com/video/tos/alisg/tos-alisg-ve-37c799-sg/o8abmgkGVCqm…",
    "cover_url": "https://p16-common-sign.tiktokcdn-eu.com/tos-useast5-p-0068-tx/o4BjAVnVuiCAcCqAE1iB7Sf3…",
    "dynamic_cover_url": "https://p19-common-sign.tiktokcdn-eu.com/tos-useast5-p-0068-tx/o4BjAVnVuiCAcCqAE1iB7Sf3…",
    "media_expires_at": "2026-09-24T15:18:20+00:00",
    "music_id": "7686893746084547342",
    "music_title": "original sound - NASA",
    "music_author": "NASA"
  }
]

First 2 of 11 rows from a real run — sample_output.json. Long values shortened here.

Built for TikTok Video

Handles what breaks naive TikTok Video scrapers

Mentions resolved to the right account

The text after an @ in a caption is a display name: "@sadie" pointed at the account sad_i_e while @sadie is a different real account. Hashtags and mentions come from TikTok's own entity data, never from slicing the caption.

Photo posts are not broken videos

Photo-mode carousels share the /video/ URL with zero duration and no play address. They come out as content_type photo with image_count, image_urls and their own image_title.

Empty answers are not empty accounts

TikTok's paginated feed answers HTTP 200 with an empty body. That gets its own state, counted as blocked (exit 3), instead of reporting an account with no videos.

WAF interstitial handled by a browser

TikTok's "Please wait..." WAF page is a JavaScript challenge, not a captcha. --transport auto recognises it and switches to a browser, which cleared it 3 of 3 times in testing.

Everything built in

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 Video snapshots by id — new, removed and changed rows for monitoring.

Use cases

What teams do with TikTok Video data

Creator and campaign analytics

Track exact likes, comments, shares and saves on an account's latest videos or a list of campaign videos.

Sound and trend research

See which sounds, hashtags and mentions recent videos use, read from TikTok's own structured data.

Transcript collection

Download machine-generated WebVTT caption tracks where a video carries one.

Media archiving

Capture direct video, cover and photo URLs with their expiry time, so you know how long each one stays valid.

Access & pricing

Free to run. Pay only for what the site requires.

Runs with no key, proxy or account

Both routes are server-rendered and were served to a bare datacentre address with no credentials (measured 2026-09-22); a daily canary re-tests that from a GitHub runner with no secrets. The paid products buy volume from many addresses, a specific exit country and no browser infrastructure of your own — not access. Through a residential pool a plain HTTP client met TikTok's WAF interstitial more often, and a browser cleared it, which is why --transport auto falls back to one.

Optional 2Captcha services — captcha solving, the Scraping Browser API, residential proxies and fingerprints — work behind one key.

Good to know

  • Only an account's 10-12 most recent videos are reachable; the back catalogue is not.
  • Follower counts are not included; they live in tiktok-profile-scraper.
  • Hashtag feeds and keyword search are not implemented.

2scraper vs. hosted scraper APIs

2scraperTypical hosted API
CodeOpen source, MIT — read and patch itClosed
CostFree; optional services billed only when usedSubscription or per request / per result
Runs onYour machine, server or CIThe vendor's infrastructure
OutputJSON/CSV files + run metadataAPI responses
When the site changesFix it yourself or open an issue / PRWait for the vendor
FAQ

TikTok Video scraper FAQ

How many videos per account can it get?

The 10 to 12 most recent that the embed route serves. The paginated back-catalogue feed answers with an empty body, so each run's sidecar marks the window as a sample of the account.

Why is play_count rounded but likes exact?

TikTok rounds the play count in both its stats objects, while likes, comments, shares and saves are exact.

Do I need a proxy or a key?

No. Both routes answered plain HTTPS from a datacentre address. Proxies and the Scraping Browser API are for volume and geography.

Can it scrape hashtag feeds or search?

No — those pages server-render no videos and the request that fills them is refused, so this repo refuses them with that reason. Profile stats are in tiktok-profile-scraper.

Do media URLs last?

No, they are signed and expire. media_expires_at says when, and diff_runs.py ignores URL changes so videos are not reported as changed on every run.

Is scraping TikTok legal?

It reads public pages TikTok serves to any visitor, with no login. Public data; follow the site's terms and the law.