こんにちは、ショート動画の技術構造を趣味で解析しているエンジニアです。
先日、マーケティング調査プロジェクトで「TikTok」に投稿されたクリエイティブな動画コンテンツをローカルに保存する必要が出てきました。「え、TikTokの動画ってダウンロードできるの?あの署名検証が厳しいやつ?」と思われるかもしれませんが、実は適切なアプローチを取れば、技術的には十分解析可能です。
ただし、最初に断っておきます。本記事で紹介する手法は、あくまで個人の学習・研究・デザイン参考目的での利用を前提としています。TikTokの利用規約や著作権法、クリエイターの意向は必ずご自身で確認し、適法な範囲でご活用ください。他者の作品を無断で再配布・商用利用することは絶対に避けましょう。
TikTok動画の「見えない壁」を技術的に分解する
YouTubeやInstagram Reelsと違い、TikTokの動画配信にはいくつかの「意図的な障壁」があります。
-
X-Bogus / X-Ladon / _signature の多重検証
APIリクエストには複数の署名パラメータが含まれており、単純なcurlでは403を返されることが多いです。 -
ウォーターマークの埋め込み
公式ダウンロード機能では動画にTikTokロゴが焼き付けられます。「水印なし」で取得するには別のエンドポイントを探る必要があります。 -
Device Fingerprintingの強化
最近のTikTokは、User-Agentだけでなく、Canvas fingerprintingやTLS JA3ハッシュまでチェックするケースがあります。 -
CDNの地域最適化と有効期限
動画URLは地域ごとのCDNに分散され、トークン付きリンクには数時間の有効期限が設定されています。
これらの課題を「手動で」突破するのは現実的ではありません。そこで、私が実際に運用している「最小依存・保守性重視」の解析アプローチを紹介します。
核心部分:動画メタデータの取得ロジック(Python例)
まず、TikTokの動画ページからvideo_idを抽出し、API経由でダウンロード用URLを取得する基本的な流れです。requestsと正規表現を組み合わせた軽量実装です。
python
import requests
import re
import json
from urllib.parse import urlparse, parse_qs
def extract_tiktok_video_info(video_url: str) - dict:
"""TikTok動画URLからメタデータとダウンロードリンクを抽出"""
1. URLからvideo_idを抽出
対応形式: tiktok.com/@user/video/1234567890
match = re.search(r'/video/(\d+)', video_url)
if not match:
short link (vm.tiktok.com) の展開処理
resp = requests.head(video_url, allow_redirects=True)
match = re.search(r'/video/(\d+)', resp.url)
if not match:
raise ValueError("video_idを抽出できませんでした")
video_id = match.group(1)
2. 埋め込みページからデータを取得(署名回避のため)
embed_url = f'https://www.tiktok.com/embed/v2/{video_id}'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'https://www.tiktok.com/'
}
resp = requests.get(embed_url, headers=headers)
resp.raise_for_status()
3. JSONデータを抽出
embedページには<script id="__UNIVERSAL_DATA_FOR_REHYDRATION__"にデータが埋め込まれている
data_match = re.search(r'__UNIVERSAL_DATA_FOR_REHYDRATION__.?({.?})</script', resp.text, re.S)
if not data_match:
raise ValueError("埋め込みデータを抽出できませんでした")
universal_data = json.loads(data_match.group(1))
4. 動画情報を探索(構造は変更される可能性があるため柔軟に)
video_data = _traverse_video_data(universal_data)
return video_data
def _traverse_video_data(obj):
"""再帰的にvideo/infoフィールドを探索する簡易実装"""
if isinstance(obj, dict):
動画情報のパターンマッチ
if 'video' in obj and isinstance(obj['video'], dict):
video = obj['video']
if 'downloadAddr' in video or 'playAddr' in video:
return {
'video_url_no_watermark': video.get('downloadAddr'),
'video_url_watermark': video.get('playAddr'),
'cover': video.get('cover'),
'duration': video.get('duration'),
'width': video.get('width'),
'height': video.get('height'),
'author': obj.get('author', {}).get('uniqueId'),
'desc': obj.get('desc', '')[:100]
}
for v in obj.values():
result = _traverse_video_data(v)
if result:
return result
elif isinstance(obj, list):
for item in obj:
result = _traverse_video_data(item)
if result:
return result
return None
💡 ポイント: TikTokは頻繁にフロントエンドの構造を変更するため、__UNIVERSAL_DATA_FOR_REHYDRATION__のような実装詳細に依存しすぎないよう、複数の抽出方法を併用するのが吉です。また、downloadAddrが水印なし、playAddrが水印付きであることが多いですが、仕様変更には注意が必要です。
署名パラメータを「回避」するのではなく「再現」する
一部のエンドポイントでは、X-Bogusや_signatureといった署名パラメータが必須です。これらを「バイパス」しようとするのではなく、ブラウザの挙動を再現して正しく生成するアプローチの方が長期的に保守性が高いです。
python
簡易例: Playwrightでブラウザ挙動を再現し、署名付きリクエストを取得
from playwright.sync_api import sync_playwright
def fetch_with_signature(video_url: str) - dict:
"""Playwrightでブラウザを操作し、署名付きレスポンスを取得"""
with sync_playwright() as p:
ChromiumでTikTokページを開く
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
viewport={'width': 1920, 'height': 1080}
)
page = context.new_page()
Network interceptionでAPIレスポンスをキャプチャ
api_responses = []
def handle_response(response):
if '/api/item/detail/' in response.url:
api_responses.append(response.json())
page.on('response', handle_response)
page.goto(video_url, wait_until='networkidle')
browser.close()
if api_responses:
return api_responses[0] 最初のAPIレスポンスを返す
raise ValueError("APIレスポンスを取得できませんでした")
※ 実際のプロダクションでは、Playwrightの起動コストを考慮し、セッションの再利用やキャッシュ機構を併用することをお勧めします。また、ヘッドレスブラウザの検出回避には追加の工夫が必要です。
「毎回スクリプトを動かすのは面倒」→ Webツール化の選択
上記のような処理を毎回Pythonスクリプトで実行するのは、マーケターやデザイナーの皆様にはハードルが高いですよね。そこで、私はこのロジックをWebインターフェースにラップし、URLを貼り付けるだけでダウンロードできるようにしました。
今回ご紹介している TikTok動画ダウンローダー は、まさにそのような「技術的バックエンド + 直感的UI」を組み合わせたツールです。
技術スタックの裏側(少しだけ公開)
- フロントエンド: Next.js + Tailwind CSS。URL入力・解析状態・解像度選択をコンポーネント単位で管理。TikTokのembedプレビューも非同期で取得。
- バックエンド: Python(FastAPI) + asyncio + httpx。動画解析を非同期で処理し、同時リクエストへの対応を強化。
- 署名生成モジュール: 頻繁に変更される署名ロジックを独立モジュール化。新しいパターンが検出された場合、自動でフォールバック手法に切り替え。
- キャッシュ戦略: 同じvideo_idのメタデータはRedisに30分間キャッシュ。ユーザー体験の向上とTikTokサーバーへの負荷軽減を両立。
- セキュリティ: 入力値のサニタイズ、CORS制限、レートリミット(1IP/分間5リクエスト)により悪用を防止。
- プライバシー: 動画データの一時保存はメモリ内のみ。ダウンロード完了後即破棄。アクセスログにも動画URLを記録しません。
特に「サーバー側に動画を保存しない」設計は重要で、ストレージコストと法的リスクの両方を抑えつつ、ユーザーに「自分の責任でダウンロード」してもらう姿勢を明確にしています。
実際の使い流れ(技術者目線で深掘り)
-
URLの正規化とバリデーション
TikTokのURLにはhttps://www.tiktok.com/@user/video/1234567890、https://vm.tiktok.com/xxxx/、https://vt.tiktok.com/xxxx/など、複数の形式があります。ツール内部ではurllib.parseでパスを解析し、video_idを抽出して統一処理しています。 -
ウォーターマーク有無の選択ロジック
downloadAddr(水印なし)→playAddr(水印あり)→bitrateInfo(複数解像度)の順で優先度を設定。ユーザーが「画質重視」か「水印なし重視」かを選択できるオプションも用意。 -
フォールバック機構の多層化
embed API→Playwrightブラウザ再現→サードパーティAPIミラーの順で試行。これにより「TikTokが仕様変更しても動かし続ける」という目標に近づいています。 -
メタデータの付与とファイル命名
ダウンロードファイルには、投稿者名・動画説明文(先頭30文字)・投稿日時を自動で付与。[@username] 2024-01-15_awesome_dance.mp4のような形式で出力され、後からの整理が格段に楽になります。
開発中にハマったポイント(実体験・失敗談)
① User-Agentを変えただけでは403が止まらない
最初は「スマホのUser-Agentにすればいいや」と思っていたのですが、TikTokはTLSフィンガープリントやHTTP/2の設定までチェックしている節があります。対策として、ツールではhttpxのhttp2=Trueオプションを活用し、さらにcurl-impersonateのようなライブラリの導入も検討中。
② CDNトークンの有効期限が「思っていたより短い」
TikTokのダウンロードURLには?expires=1234567890&token=abcd...のようなパラメータが含まれており、有効期限が1〜2時間のことがあります。対策として、メタデータ取得→ダウンロード開始を可能な限り短時間で完結させる設計に。ユーザーには「解析完了後はお早めにダウンロードください」と優しく案内しています。
③ 日本語説明文の絵文字・改行処理
TikTokの動画説明文には絵文字や改行、ハッシュタグが頻繁に含まれます。ファイル名にそのまま使うとOSによってはエラーになります。本ツールでは、re.sub(r'[<:"/\\|?]', '_', text)で危険文字を置換し、さらに長すぎる説明文は...でトリミングするサニタイズ処理を実装。Qiita読者の皆様なら、この「地味だけど重要」な配慮、共感していただけるかと。
法的・倫理的なラインについて(ここはしっかり書きます)
技術的に「できる」ことと、「してよい」ことは別問題です。本ツールの利用にあたっては、以下の点を強く意識しています。
✅ 推奨される使い方
- デザインのインスピレーション収集のための個人アーカイブ
- 自分が作成したコンテンツのバックアップ
- 学術研究・市場調査での一時的な参考利用(再配布なし)
❌ 避けるべき使い方
- ダウンロードした動画をYouTubeやInstagramに無断で再アップロード
- クリエイターの作品を商用目的で無断利用
- 大量ダウンロードによるTikTokサーバーへの負荷攻撃(レートリミット違反)
ツールページにも明記していますが、ユーザー自身の行動に対する法的責任はユーザー本人にあります。技術者は「できること」を提供するだけでなく、「どう使うべきか」のガイドラインも併せて提示する責任があると考えています。
Qiita読者への特別ヒント:自作ツールに「機能追加」するなら
もし本記事を読んで「自分でも作ってみよう」と思われた方へ、追加で実装すると喜ばれる機能を3つご紹介します。
- ユーザー投稿の一括取得
研究用途では「特定のクリエイターの動画をまとめて取得したい」というニーズがあります。TikTokのAPI(またはスクレイピング)を活用し、ユーザーの投稿一覧から動画のみをフィルタリング→キューに投入→完了順にダウンロード、というフローを実装すると、プロユースに一気に近づきます。
javascript
// 簡易例:ユーザー投稿一覧から動画URLを抽出
async function fetchUserVideos(username, maxCount = 30) {
const videos = [];
let cursor = 0;
while (videos.length < maxCount) {
const api = https://www.tiktok.com/api/post/item_list/?uniqueId=${username}&count=30&cursor=${cursor};
const resp = await fetch(api, {headers: {'User-Agent': 'Mozilla/5.0'}});
const data = await resp.json();
for (const item of data.itemList || []) {
if (item.video?.downloadAddr) {
videos.push({
id: item.id,
url: item.video.downloadAddr,
desc: item.desc
});
}
}
if (!data.hasMore) break;
cursor = data.cursor;
await new Promise(r = setTimeout(r, 1000)); // レートリミット配慮
}
return videos;
}
-
メタデータのJSON/CSV出力
動画説明文・投稿者・再生回数・いいね数・投稿日時などを構造化データで出力できるようにすると、分析用途での活用が格段に便利になります。 -
ブラウザ拡張機能との連携
TikTok閲覧中に「この動画保存したい」と思った瞬間に、拡張機能からツールにURLを送信。UXが劇的に向上します。chrome.runtime.sendMessageとFastAPIのエンドポイントを組み合わせるだけなので、実装コストも低め。
おわりに:技術は「クリエイティビティの尊重」と共に
TikTok動画のダウンロードというテーマは、一見「便利ツール」の域を出ないように見えるかもしれません。しかし、裏側を覗くと、署名アルゴリズム・CDN解析・非同期処理・UI/UX設計・法遵守…と、フルスタックエンジニアの腕が試される要素が詰まっています。
今回ご紹介した TikTok動画ダウンローダー は、そうした技術的課題を「誰でも使える形」に落とし込んだ一つの答えです。もちろん完璧ではなく、TikTokの仕様変更には随時対応が必要ですが、オープンな技術スタックで構築しているため、コミュニティからのフィードバックも歓迎しています。
📌 最後に一言
技術の可能性を追求する一方で、クリエイターへのリスペクトを忘れずに。
「インスピレーションを得る」と「権利を守る」のバランスを取ることこそ、プロフェッショナルの腕の見せ所だと信じています。
何か質問や技術的な議論があれば、コメント欄で気軽にどうぞ。
それでは、よいTikTokライフを!🎵✨
参考リンク