DEV Community

Valentina Skakun for HasData

Posted on

Downloading TikTok Videos with Python (Part 2)

This is Part 2 of the series.

Part 1 covered extracting profile data and video lists (via HasData + TikTok endpoints). Here, we’ll use those results to download videos.

Table of Contents

Requirements

You’ll need:

  • Python 3.8+
  • requests
  • HasData API key (if you want to refresh links)
pip install requests
Enter fullscreen mode Exit fullscreen mode

Basic downloader

Simple, memory-safe way to download each video file in chunks.

import json
import requests
import os
import time

DOWNLOAD_FOLDER = "tiktok_videos"
os.makedirs(DOWNLOAD_FOLDER, exist_ok=True)

HEADERS = {
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
    "accept": "*/*",
    "referer": "https://www.tiktok.com",
}

def download_video(url, filepath):
    try:
        with requests.get(url, headers=HEADERS, stream=True, timeout=30) as r:
            r.raise_for_status()
            with open(filepath, "wb") as f:
                for chunk in r.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
        print(f"[OK] {filepath}")
        return True
    except requests.HTTPError as e:
        print(f"[HTTP {e.response.status_code}] {filepath}")
        return False
    except Exception as e:
        print(f"[ERROR] {filepath}: {e}")
        return False


# Load video list from Part 1
with open("videos_clean_full.json", "r", encoding="utf-8") as f:
    videos = json.load(f)

for video in videos:
    vid = video.get("id")
    url = video.get("downloadAddr")
    if not url:
        print(f"[WARN] No link for {vid}")
        continue

    path = os.path.join(DOWNLOAD_FOLDER, f"{vid}.mp4")
    if os.path.exists(path):
        print(f"[SKIP] {vid}")
        continue

    download_video(url, path)
    time.sleep(0.5)
Enter fullscreen mode Exit fullscreen mode

This handles streaming, writes to disk in chunks, and skips existing files.

Handling expired links

TikTok download links expire fast. If you reuse old links, you’ll get 403 Forbidden. When that happens, request a fresh link using HasData.

from bs4 import BeautifulSoup
import json

def refresh_download_url(video_page_url, api_key, country="DE"):
    api_url = "https://api.hasdata.com/scrape/web"
    payload = {
        "url": video_page_url,
        "proxyType": "residential",
        "proxyCountry": country,
        "blockResources": False,
        "wait": 1000,
        "jsRendering": True
    }
    headers = {
        "Content-Type": "application/json",
        "x-api-key": api_key
    }

    try:
        r = requests.post(api_url, headers=headers, json=payload, timeout=30)
        r.raise_for_status()
        data = r.json()
        html = data.get("content")
        if not html:
            return None

        soup = BeautifulSoup(html, "html.parser")
        script = soup.find("script", id="__UNIVERSAL_DATA_FOR_REHYDRATION__")
        if not script or not script.string:
            return None

        raw = json.loads(script.string)
        items = raw.get("__DEFAULT_SCOPE__", {}).get("webapp.video-detail", {}).get("itemModule", {})
        for v in items.values():
            d = v.get("video", {}).get("downloadAddr")
            if d:
                return d
    except Exception as e:
        print(f"[ERROR] refresh: {e}")
    return None
Enter fullscreen mode Exit fullscreen mode

Retry failed downloads and refresh URLs on 403.

MAX_RETRIES = 3
HASDATA_API_KEY = "YOUR-API-KEY"

for v in videos:
    vid = v.get("id")
    url = v.get("downloadAddr")
    page = f"https://www.tiktok.com/@funny_funny66066/video/{vid}"
    path = os.path.join(DOWNLOAD_FOLDER, f"{vid}.mp4")

    attempts = 0
    while attempts < MAX_RETRIES:
        ok = download_video(url, path)
        if ok:
            break
        attempts += 1
        print(f"[RETRY] {vid} ({attempts}) — refreshing URL")
        url = refresh_download_url(page, HASDATA_API_KEY) or url
        time.sleep(1)

    if not ok:
        print(f"[FAIL] {vid}")
Enter fullscreen mode Exit fullscreen mode

Notes

More useful resources:

Top comments (0)