跳转至

抖音视频批量下载方案

环境与工具

工具 用途 安装方式
Python 3.11+ 主控脚本语言 系统预装
websockets (Python) 与 Chrome CDP 通信 pip install websockets -i https://pypi.tuna.tsinghua.edu.cn/simple
Chrome 浏览器 提供登录态和视频页面渲染 系统预装,需以调试模式启动
ffmpeg 合并分离的音视频流 BtbN/FFmpeg-Builds 下载解压

用清华 pip 镜像可大幅提速。


Chrome 启动方式(关键)

必须以远程调试端口启动,否则无法通过 CDP 连接:

Stop-Process -Name chrome -Force -ErrorAction SilentlyContinue
Start-Sleep 2
Start-Process "C:\Program Files\Google\Chrome\Application\chrome.exe" `
  -ArgumentList "--remote-debugging-port=9222", `
                "--user-data-dir=$env:LOCALAPPDATA\Google\Chrome\User Data", `
                "about:blank"

要点:

  • 必须先用 Stop-Process 杀掉所有 Chrome 进程再启动,否则调试端口不会生效。
  • --user-data-dir 必须指向用户的真实 Chrome profile,这样登录态(包括社媒助手等扩展)才能加载。
  • 验证端口是否就绪:Invoke-WebRequest http://127.0.0.1:9222/json/version

下载流程

1. 获取视频列表

从抖音收藏夹提取的所有视频链接,保存到 douyin-link.md,一行一个 URL,格式如:

https://www.douyin.com/video/7630049695850646827
https://www.douyin.com/video/7658605034971598107

2. 核心原理

抖音 PC 端视频使用 DASH(Dynamic Adaptive Streaming over HTTP)技术,将视频分为纯视频流纯音频流两个独立文件,存放在 douyinvod.com CDN 上。这两个流的 URL 带有签名 token,需要在浏览器会话中实时捕获。

3. Python 脚本流程

每段视频分三步完成:

Step 1:通过 CDP 导航到视频页面并捕获音视频流 URL

import asyncio, json, urllib.request, websockets

async def get_video_urls(video_page_url, ws_url):
    async with websockets.connect(ws_url, max_size=2**30) as ws:
        # 启用 Network 监控
        await ws.send(json.dumps({"id":1, "method":"Network.enable"}))
        await ws.recv()
        # 导航到目标视频
        await ws.send(json.dumps({"id":2, "method":"Page.navigate",
            "params":{"url": video_page_url}}))

        captured = []
        start = asyncio.get_event_loop().time()
        while asyncio.get_event_loop().time() - start < 12:
            msg = await asyncio.wait_for(ws.recv(), timeout=2)
            data = json.loads(msg)
            if "method" in data and data["method"] == "Network.responseReceived":
                resp = data["params"]["response"]
                url = resp.get("url", "")
                if "douyinvod.com" in url and "video" in resp.get("mimeType", ""):
                    captured.append(url)
            if len(captured) >= 2:
                break
        return captured
  • 通过 Page.navigate 导航,利用 Chrome 的登录态。
  • 监听 Network.responseReceived 事件,过滤出 douyinvod.com 且 MIME 类型为 video/* 的请求。
  • 通常每个视频有 2 个流:一个带 audio 标识(音频流),一个带 video 标识(视频流)。部分短视频只有 1 个流(音视频已合并)。

Step 2:用 urllib 下载音视频流

def download(url, path, retries=3):
    for i in range(retries):
        try:
            req = urllib.request.Request(url)
            req.add_header("Referer", "https://www.douyin.com/")
            req.add_header("User-Agent", "Mozilla/5.0 ...")
            with urllib.request.urlopen(req, timeout=60) as resp:
                content = resp.read()
            with open(path, "wb") as f:
                f.write(content)
            return True
        except Exception as e:
            if i < retries - 1:
                time.sleep(2)
    return False

要点:

  • 必须带上 Referer: https://www.douyin.com/,否则 CDN 会拒绝请求。
  • User-Agent 必须模拟浏览器。
  • 捕获的 URL token 有效期很短(几分钟),必须马上使用。
  • 加 3 次重试 + IncompleteRead 容错,避免网络波动导致失败。

Step 3:用 ffmpeg 合并音视频流

ffmpeg -i video1_video.mp4 -i video1_audio.mp4 `
  -c copy -map 0:v:0 -map 1:a:0 -shortest video1.mp4 -y

参数说明:

  • -c copy:不重新编码,直接拷贝流(速度快)。
  • -map 0:v:0:取第一个输入的视频轨。
  • -map 1:a:0:取第二个输入的音频轨。
  • -shortest:以较短的流为准截断(避免音视频不同步)。
  • -y:覆盖已有文件。

获取 CDP WebSocket URL

def get_ws_url():
    with urllib.request.urlopen("http://127.0.0.1:9222/json") as resp:
        pages = json.loads(resp.read())
    for p in pages:
        if "douyin.com" in p.get("url", ""):
            return p["webSocketDebuggerUrl"]
    return pages[0]["webSocketDebuggerUrl"]

验证 CDP 连接

Invoke-WebRequest "http://127.0.0.1:9222/json/version" -UseBasicParsing

成功返回 JSON,包含 webSocketDebuggerUrl 字段即表示 CDP 可用。


完整的单视频下载脚本示例

文件:dl_douyin.py

import asyncio, json, urllib.request, os, time, websockets

CHROME_CDP = "http://127.0.0.1:9222"
OUT_DIR = r"D:\product\news\douyin-videos"
os.makedirs(OUT_DIR, exist_ok=True)

# ... 上述 get_ws_url / download / get_video_urls 函数 ...

async def process_video(url, prefix, ws_url):
    """下载单个视频的音视频流"""
    streams = await get_video_urls(url, ws_url)
    audio_url = next((s for s in streams if "audio" in s), None)
    video_url = next((s for s in streams if "audio" not in s), None)

    if audio_url and video_url:
        # 分离的音视频流:分别下载后合并
        audio_path = os.path.join(OUT_DIR, f"{prefix}_audio.mp4")
        video_path = os.path.join(OUT_DIR, f"{prefix}_video.mp4")
        download(audio_url, audio_path)
        download(video_url, video_path)
        # 用 ffmpeg 合并
        out_path = os.path.join(OUT_DIR, f"{prefix}.mp4")
        os.system(f'ffmpeg -i "{video_path}" -i "{audio_path}" -c copy -map 0:v:0 -map 1:a:0 -shortest "{out_path}" -y')
        os.remove(audio_path)
        os.remove(video_path)
    elif video_url:
        # 已合并的视频流:直接下载
        download(video_url, os.path.join(OUT_DIR, f"{prefix}.mp4"))

async def main():
    ws_url = get_ws_url()
    await process_video("https://www.douyin.com/video/7630049695850646827", "video1", ws_url)

常见问题

问题 原因 解决
CDP 连接被拒 ECONNREFUSED Chrome 未以调试模式启动,或 Chrome 未完全关闭就重启 Stop-Process -Name chrome -Force 后再启动
IncompleteRead 网络波动导致下载中断 加重试逻辑(3次),每次间隔2秒
Fresh cookies are needed yt-dlp 提取不到有效 cookies 放弃 yt-dlp,改用 CDP + urllib 直接下载
只有一个流(无音频) 短视频可能音视频未分离 检查 ffprobe 输出,如果已含音轨则直接使用
token 过期 CDN URL 有效期短 必须在捕获后 立即 下载,不要提前批量收集