# YouTube 代理下载

> 服务器下载 YouTube 遇到 403 时，经代理链接下载最高 1080p 的媒体

来源: https://platform.snapany.com/zh/docs/youtube-proxy-download

[`POST /openapi/v1/youtube/proxy-download`](https://platform.snapany.com/zh/docs/api#POST/youtube/proxy-download) — 视频每 10 分钟消耗 4 积分。

传入 YouTube 链接，返回与[单个帖子提取](https://platform.snapany.com/zh/docs/extract-post)相同的结构，并为 1080p 及以下的每个档位附上**代理链接**。在云服务器、VPS、Serverless 函数上直接下载代理链接即可，无需维护住宅代理、cookies 或 PO token。

## 为什么服务器下载 YouTube 会返回 403 Forbidden

调 `/extract/post` 拿到 `googlevideo.com` 直链后，从服务器下载却报 `HTTP Error 403: Forbidden`。我们的实测结果：

- **机房 IP 被拒绝。** 同一条直链，从家庭宽带下载正常（`206`），从亚洲、欧洲多个地区的机房服务器下载返回 `403`。直链并不绑定提取时的 IP，YouTube 看的是下载时的 IP。
- **部分链接只允许美国 IP 下载。** 带 `gcr=us` 参数的链接，美国以外的 IP 下载一律返回 `403`。

所以只在提取环节想办法（换工具、配 cookies 或 PO token）解决不了问题：只要下载发生在机房 IP 上，照样返回 `403`。

## 工作原理

1. 你把 YouTube 链接发给 `POST /openapi/v1/youtube/proxy-download`。
2. 我们提取视频，返回常规的提取结果，并为 1080p 及以下的每个档位加上代理链接。
3. 你下载代理链接。我们的下载网络从 YouTube 取回文件并流式转发给你，YouTube 看不到你服务器的 IP。

响应里的直链（`video_url`、`audio_url`、`resource_url`）全部保留：1080p 以上的档位只有直链；如果你的服务器能正常下载直链（比如在家庭宽带上），也可以继续用直链。

## 请求与响应

```bash
curl -X POST https://api.snapany.com/openapi/v1/youtube/proxy-download \
  -H "Authorization: Bearer sk_snapany_xxx" \
  -H "Accept-Language: zh" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}'
```

支持 `youtube.com` 链接（普通视频、Shorts、直播回放，以及 `m.` 与 `music.` 子域）和 `youtu.be` 链接。其他链接——包括 `youtube-nocookie.com` 嵌入链接和跳转到 YouTube 的短链（如 `t.co`）——返回 HTTP `400`、错误码 `unsupported_site`，不扣费。

响应示例（有删减，ID 与链接均为占位符）：

```json
{
  "site": "youtube",
  "title": "Scenic tour of the Karst mountains",
  "medias": [
    {
      "media_type": "video",
      "resource_url": "https://rr1---sn-example.googlevideo.com/videoplayback?itag=18&expire=1700000000&sig=EXAMPLE",
      "resource_proxy_url": "https://proxy.example.com/proxy?payload=EXAMPLE_360P_RESOURCE",
      "preview_url": "https://i.ytimg.com/vi/EXAMPLE0001/maxresdefault.jpg",
      "duration": 754,
      "variants": [
        {
          "quality": 2160,
          "quality_label": "4K",
          "fps": 30,
          "video_url": "https://rr1---sn-example.googlevideo.com/videoplayback?itag=313&expire=1700000000&sig=EXAMPLE",
          "video_ext": "webm",
          "video_codec": "vp9",
          "video_filesize": 687194112,
          "audio_url": "https://rr1---sn-example.googlevideo.com/videoplayback?itag=251&expire=1700000000&sig=EXAMPLE",
          "audio_ext": "weba",
          "audio_codec": "opus",
          "audio_filesize": 11354112
        },
        {
          "quality": 1080,
          "quality_label": "1080p",
          "fps": 30,
          "video_url": "https://rr1---sn-example.googlevideo.com/videoplayback?itag=137&expire=1700000000&sig=EXAMPLE",
          "video_proxy_url": "https://proxy.example.com/proxy?payload=EXAMPLE_1080P_VIDEO",
          "video_ext": "mp4",
          "video_codec": "h264",
          "video_filesize": 181403648,
          "audio_url": "https://rr1---sn-example.googlevideo.com/videoplayback?itag=140&expire=1700000000&sig=EXAMPLE",
          "audio_proxy_url": "https://proxy.example.com/proxy?payload=EXAMPLE_1080P_AUDIO",
          "audio_ext": "m4a",
          "audio_codec": "aac",
          "audio_filesize": 12202891
        },
        {
          "quality": 360,
          "quality_label": "360p",
          "fps": 30,
          "video_url": "https://rr1---sn-example.googlevideo.com/videoplayback?itag=18&expire=1700000000&sig=EXAMPLE",
          "video_proxy_url": "https://proxy.example.com/proxy?payload=EXAMPLE_360P_VIDEO",
          "video_ext": "mp4",
          "video_codec": "h264",
          "video_filesize": 50847744
        }
      ]
    },
    {
      "media_type": "audio",
      "resource_url": "https://rr1---sn-example.googlevideo.com/videoplayback?itag=140&expire=1700000000&sig=EXAMPLE",
      "resource_proxy_url": "https://proxy.example.com/proxy?payload=EXAMPLE_AUDIO_RESOURCE",
      "duration": 754,
      "variants": [
        {
          "audio_url": "https://rr1---sn-example.googlevideo.com/videoplayback?itag=140&expire=1700000000&sig=EXAMPLE",
          "audio_proxy_url": "https://proxy.example.com/proxy?payload=EXAMPLE_AUDIO_EN",
          "audio_ext": "m4a",
          "audio_codec": "aac",
          "audio_filesize": 12202891,
          "language_tag": "en",
          "language_name": "English"
        }
      ]
    }
  ],
  "id": "EXAMPLE0001",
  "post_url": "https://www.youtube.com/watch?v=EXAMPLE0001",
  "created_at": "2025-06-02T12:00:00.000Z",
  "author": { "username": "@travelchannel", "display_name": "Travel Channel" }
}
```

在单个帖子提取的响应之外多出的字段：

| 字段                 | 位置            | 何时出现                                                 |
| -------------------- | --------------- | -------------------------------------------------------- |
| `video_proxy_url`    | 档位（variant） | 该档位在 1080p 及以下                                    |
| `audio_proxy_url`    | 档位（variant） | 1080p 及以下且音频分离的视频档位；音频媒体的每个语言档位 |
| `resource_proxy_url` | 视频 / 音频媒体 | `resource_url` 对应的文件在 1080p 及以下                 |

超过 3 GB 的文件不带代理链接。响应里没有 `preview_proxy_url`，也没有过期时间字段——有效期规则见下文。

完整 schema 见 [API 参考](https://platform.snapany.com/zh/docs/api#POST/youtube/proxy-download)。

## 用 `video_proxy_url` 下载

选有 `video_proxy_url` 的最高档位。如果它同时有 `audio_proxy_url`，说明音视频是分离的流：分别下载后用 ffmpeg 合并；只有 `video_proxy_url` 时它就是完整文件。代理链接不需要 API key，也不需要额外的请求头。

1080p 及以下的档位通常是 MP4（H.264）视频加 M4A（AAC）音频，`-c copy` 流复制即可合成标准 MP4，无需重编码。只想要一个现成文件时，用视频媒体的 `resource_proxy_url`（通常清晰度较低、自带音轨）；只要音频时，用音频媒体的 `resource_proxy_url`。

**curl**（配合 jq）：

```bash
# 1. 获取代理链接
curl -s -X POST https://api.snapany.com/openapi/v1/youtube/proxy-download \
  -H "Authorization: Bearer sk_snapany_xxx" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}' > post.json

# 2. 选有代理链接的最高档位（1080p 及以下）
jq '[.medias[] | select(.media_type == "video") | .variants[]? | select(.video_proxy_url)] | max_by(.quality)' post.json > variant.json

# 3. 分别下载音视频流：-f 遇到 HTTP 错误直接失败，-C - 支持断点续传
#    （档位没有 audio_proxy_url 时视频文件已经完整，跳过音频那一行和第 4 步）
curl -fL -C - -o video.mp4 "$(jq -r .video_proxy_url variant.json)"
curl -fL -C - -o audio.m4a "$(jq -r .audio_proxy_url variant.json)"

# 4. 合并：流复制，不重编码
ffmpeg -i video.mp4 -i audio.m4a -c copy merged.mp4
```

**Python**：

```python
import subprocess
import requests

response = requests.post(
    "https://api.snapany.com/openapi/v1/youtube/proxy-download",
    headers={"Authorization": "Bearer sk_snapany_xxx"},
    json={"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"},
)
response.raise_for_status()
post = response.json()

# 有代理链接的最高档位（代理链接只在 1080p 及以下的档位出现）
video = next(media for media in post["medias"] if media["media_type"] == "video")
variant = max((v for v in video["variants"] if v.get("video_proxy_url")), key=lambda v: v["quality"])


def download(url, path):
    # 不需要 API key 或请求头：代理链接本身就是访问凭证
    with requests.get(url, stream=True, timeout=60) as r:
        r.raise_for_status()  # 如 404 payload_expired
        with open(path, "wb") as f:
            for chunk in r.iter_content(1 << 20):
                f.write(chunk)


download(variant["video_proxy_url"], "video.mp4")
if variant.get("audio_proxy_url"):
    download(variant["audio_proxy_url"], "audio.m4a")
    subprocess.run(["ffmpeg", "-i", "video.mp4", "-i", "audio.m4a", "-c", "copy", "merged.mp4"], check=True)
```

**Node.js**（18+）：

```javascript
import { spawnSync } from 'node:child_process'
import { createWriteStream } from 'node:fs'
import { Readable } from 'node:stream'
import { pipeline } from 'node:stream/promises'

const response = await fetch('https://api.snapany.com/openapi/v1/youtube/proxy-download', {
  method: 'POST',
  headers: { Authorization: 'Bearer sk_snapany_xxx', 'Content-Type': 'application/json' },
  body: JSON.stringify({ url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' }),
})
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`)
const post = await response.json()

// 有代理链接的最高档位（代理链接只在 1080p 及以下的档位出现）
const video = post.medias.find(media => media.media_type === 'video')
const variant = video.variants.filter(v => v.video_proxy_url).sort((a, b) => b.quality - a.quality)[0]

async function download(url, path) {
  const res = await fetch(url)
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`) // 如 404 payload_expired
  await pipeline(Readable.fromWeb(res.body), createWriteStream(path))
}

await download(variant.video_proxy_url, 'video.mp4')
if (variant.audio_proxy_url) {
  await download(variant.audio_proxy_url, 'audio.m4a')
  spawnSync('ffmpeg', ['-i', 'video.mp4', '-i', 'audio.m4a', '-c', 'copy', 'merged.mp4'], { stdio: 'inherit' })
}
```

大文件建议用 Range 请求下载（curl 的 `-C -`，或自己带 `Range: bytes=…` 请求头），中断后可以续传，不必从头再来。

## 有效期、限制与计费

**有效期**：代理链接自响应起**至少 30 分钟、最长 1 小时**有效。拿到响应就开始下载，按 30 分钟规划。

过期后代理链接返回 HTTP `404`：

```json
{ "code": "payload_expired", "message": "link has expired" }
```

重新调用接口拿新链接即可，重试同一条链接没有用。

有效期内代理链接返回 HTTP `403`，说明 YouTube 拒绝了这次请求：

```json
{ "code": "upstream_error", "message": "upstream refused the request", "retryable": true, "upstream_status": 403 }
```

这种情况很少见，重试一次通常就能成功。

**限制**：

- 代理链接最高 **1080p**。1080p 以上的档位（1440p、4K、8K）保留在响应里，但只有直链，见下方常见问题。
- 超过 **3 GB** 的文件不带代理链接。
- 仅支持 YouTube 链接。无法提取的链接——播放列表、频道、正在直播的视频、会员专享或已删除的视频等——返回 HTTP `400`（见[错误码](https://platform.snapany.com/zh/docs/errors)），不扣费。

**计费**：返回响应后按响应内容扣费。

| 响应内容                                                                      | 扣费                                              |
| ----------------------------------------------------------------------------- | ------------------------------------------------- |
| 有视频代理链接（档位的 `video_proxy_url`，或视频媒体的 `resource_proxy_url`） | 每 10 分钟视频 4 积分（不足 10 分钟按 10 分钟计） |
| 只有音频代理链接                                                              | 4 积分                                            |
| 没有代理链接                                                                  | 1 积分                                            |

示例：10 分钟以内的视频 4 积分，10–20 分钟 8 积分，1 小时 24 积分。时长取自 `duration` 字段，缺失时按 4 积分计。上面的响应示例（`duration: 754`，约 12.5 分钟）消耗 8 积分。失败的调用（不支持的链接、提取失败）不扣费。

## 常见问题

### 需要自己维护住宅代理吗？

用本接口就不需要。住宅代理常见价格是每 GB $3–4，一个 20 分钟的 1080p 视频（约 270–470 MB）光代理流量就要约 $0.81–1.88，还不算维护 cookies 和 PO token 的工作量。通过本接口下载同一个视频消耗 8 积分，按积分套餐折算约 $0.0053–0.0088。

### 遇到「Sign in to confirm you're not a bot」怎么办？

这个报错出现在提取阶段：YouTube 把请求判定为自动化访问时会要求登录，服务器 IP 上很常见。用本接口时提取在我们这边完成，你的服务器不需要 cookies、登录账号或 PO token。

### 能在 AWS Lambda、EC2、VPS 上用吗？

可以。你的服务器只和 SnapAny API 及我们的下载网络通信，不直连 YouTube，所以跑在 AWS Lambda、EC2、其他云还是 VPS 上都一样。注意运行环境自身的限制：Lambda 有最长运行时间和 `/tmp` 容量上限，长视频建议直接流式写入对象存储（如 S3），或用 Range 分段下载。

### 能通过代理下载 4K 吗？

不能，代理链接最高 1080p。1440p、4K、8K 档位保留在响应里，但只有直链。YouTube 这些清晰度只提供 VP9 或 AV1 编码，文件体积是 1080p 的 3–7 倍，通常还需要转码。这些直链从家庭宽带下载一般没有问题。如果你需要通过代理下载更高清晰度，请联系我们。
