Guía de descarga de enlaces de video multiplataforma
Este documento presenta en detalle cómo descargar correctamente archivos de video después de obtener enlaces de video de plataformas como Douyin, TikTok, Bilibili, Xiaohongshu, Weibo, etc. Se centra en resolver problemas comunes como CORS, anti-hotlinking y verificación de encabezados de solicitud.
Índice
- Resumen de problemas comunes
- Douyin (Douyin)
- TikTok
- Bilibili (Bilibili)
- Xiaohongshu (Xiaohongshu)
- Weibo (Weibo)
- YouTube
- Solución de descarga en frontend
- Solución de descarga mediante proxy en backend
- Contáctanos
Resumen de problemas comunes
Después de obtener enlaces de video de distintas plataformas, al descargar directamente normalmente se encuentran los siguientes problemas:
| Tipo de problema | Descripción | Solución |
|---|---|---|
| Anti-hotlinking (Referer) | El servidor verifica el origen de la solicitud y rechaza orígenes no válidos | Establecer el encabezado Referer correcto |
| CORS | La política de seguridad del navegador bloquea solicitudes entre dominios | Proxy en backend / descarga del lado del servidor |
| Verificación de User-Agent | El servidor verifica la identificación del cliente | Simular UA de navegador |
| Verificación de Cookie | Algunos enlaces requieren sesión iniciada | Incluir Cookie válida |
| Caducidad del enlace | Los enlaces de video tienen tiempo de expiración | Usarlos a tiempo; si expiran, obtenerlos de nuevo |
| Restricción de IP | Algunos enlaces restringen el acceso por región | Usar un proxy de la región correspondiente |
Douyin (Douyin)
Características de los enlaces de video
Los enlaces de video de Douyin suelen presentarse en las siguientes formas:
1# 播放地址 (无水印)
2https://www.douyin.com/aweme/v1/play/?video_id=xxx&line=0&file_id=xxx
3
4# CDN 地址
5https://v26-web.douyinvod.com/xxx/xxx.mp4
6
7# 带水印下载地址
8https://aweme.snssdk.com/aweme/v1/playwm/?video_id=xxxRequisitos de descarga
| Encabezado de solicitud | ¿Obligatorio? | Valor |
|---|---|---|
Referer | Obligatorio | https://www.douyin.com/ |
User-Agent | Recomendado | UA del navegador |
Cookie | Opcional | Algunos enlaces en alta definición lo requieren |
Ejemplo de descarga en el servidor
1import httpx
2
3async def download_douyin_video(video_url: str, save_path: str) -> bool:
4 """
5 下载抖音视频
6
7 Args:
8 video_url: 视频播放地址
9 save_path: 保存路径
10
11 Returns:
12 是否下载成功
13 """
14 headers = {
15 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
16 "Referer": "https://www.douyin.com/", # 必须
17 "Accept": "*/*",
18 "Accept-Encoding": "identity", # 避免压缩,方便流式下载
19 "Range": "bytes=0-", # 支持断点续传
20 }
21
22 async with httpx.AsyncClient(follow_redirects=True, timeout=60) as client:
23 async with client.stream("GET", video_url, headers=headers) as response:
24 if response.status_code in [200, 206]:
25 with open(save_path, "wb") as f:
26 async for chunk in response.aiter_bytes(chunk_size=8192):
27 f.write(chunk)
28 return True
29 return FalseProblema de CORS en frontend
Los enlaces de video de Douyin no admiten CORS; las solicitudes directas desde el frontend serán bloqueadas por el navegador:
1// ❌ 错误方式 - 会被 CORS 拦截
2fetch('https://v26-web.douyinvod.com/xxx.mp4')
3 .then(res => res.blob()) // CORS error!
4
5// ✅ 正确方式 - 使用后端代理
6fetch('/api/proxy/video?url=' + encodeURIComponent(videoUrl))
7 .then(res => res.blob())
8 .then(blob => {
9 const url = URL.createObjectURL(blob);
10 const a = document.createElement('a');
11 a.href = url;
12 a.download = 'video.mp4';
13 a.click();
14 });Notas
- Caducidad del enlace: Los enlaces de video de Douyin suelen ser válidos durante 24 horas
- Enlaces sin marca de agua: Usar
play_addren lugar dedownload_addr - Enlaces en alta definición: Algunos videos 4K/originales son de gran tamaño; se recomienda la descarga por segmentos para evitar fallos al descargar de una sola vez
TikTok
Características de los enlaces de video
1# 播放地址
2https://v16-webapp-prime.tiktok.com/video/tos/xxx.mp4
3
4# 无水印地址
5https://www.tiktok.com/aweme/v1/play/?video_id=xxx
6
7# 带水印地址
8https://v16-webapp.tiktok.com/video/tos/xxx.mp4Requisitos de descarga
Enlaces de video de la web
| Encabezado de solicitud | ¿Obligatorio? | Valor |
|---|---|---|
Referer | Obligatorio | https://www.tiktok.com/ |
User-Agent | Obligatorio | UA del navegador |
Cookie | Obligatorio | Se requiere el campo tt_chain_token |
Importante: Los enlaces de video devueltos por TikTok Web deben incluir la Cookie
tt_chain_tokenpara poder descargarse; de lo contrario, se devolverá un error 403.
Enlaces de video de la app
| Encabezado de solicitud | ¿Obligatorio? | Valor |
|---|---|---|
User-Agent | Recomendado | Cualquier UA |
Cookie | No necesario | Los enlaces de la app no tienen esta restricción |
Recomendación: Si puedes obtener enlaces de la app, prioriza su uso; no es necesario tratar el problema de las Cookies.
Ejemplo de descarga en el servidor
Descarga web (requiere tt_chain_token)
1import httpx
2
3async def download_tiktok_video_web(
4 video_url: str,
5 save_path: str,
6 tt_chain_token: str # 必须提供
7) -> bool:
8 """
9 下载 TikTok Web 端视频
10
11 注意:
12 - Web 端链接必须携带 tt_chain_token Cookie
13 - tt_chain_token 可从浏览器或 API 响应中获取
14 """
15 headers = {
16 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
17 "Referer": "https://www.tiktok.com/",
18 "Accept": "video/webm,video/ogg,video/*;q=0.9,*/*;q=0.8",
19 "Accept-Language": "en-US,en;q=0.5",
20 "Cookie": f"tt_chain_token={tt_chain_token}", # 必须
21 "Sec-Fetch-Dest": "video",
22 "Sec-Fetch-Mode": "no-cors",
23 "Sec-Fetch-Site": "cross-site",
24 }
25
26 async with httpx.AsyncClient(follow_redirects=True, timeout=60) as client:
27 async with client.stream("GET", video_url, headers=headers) as response:
28 if response.status_code in [200, 206]:
29 with open(save_path, "wb") as f:
30 async for chunk in response.aiter_bytes(chunk_size=8192):
31 f.write(chunk)
32 return True
33 elif response.status_code == 403:
34 print("下载失败: tt_chain_token 无效或缺失")
35 return FalseDescarga de la app (sin restricción de Cookie)
1async def download_tiktok_video_app(video_url: str, save_path: str) -> bool:
2 """
3 下载 TikTok App 端视频
4
5 App 端链接无需 Cookie,直接下载即可
6 """
7 headers = {
8 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
9 "Accept": "*/*",
10 }
11
12 async with httpx.AsyncClient(follow_redirects=True, timeout=60) as client:
13 async with client.stream("GET", video_url, headers=headers) as response:
14 if response.status_code in [200, 206]:
15 with open(save_path, "wb") as f:
16 async for chunk in response.aiter_bytes(chunk_size=8192):
17 f.write(chunk)
18 return True
19 return FalseCómo obtener tt_chain_token
tt_chain_token es la Cookie que TikTok utiliza para la autenticación de descarga de videos; formas de obtenerla:
-
Obtenerla desde el navegador: Abre el sitio web de TikTok, herramientas de desarrollador F12 -> Application -> Cookies -> encuentra
tt_chain_token -
Obtenerla desde la respuesta de la API: En el encabezado
Set-Cookiede algunas respuestas de la API de TikTok puede incluirse este valor -
Usar la API de TikHub: Los datos devueltos por la API pueden ya incluir un token disponible
1# 示例:从响应头提取 tt_chain_token
2def extract_tt_chain_token(response_headers: dict) -> str:
3 """从响应头中提取 tt_chain_token"""
4 cookies = response_headers.get("set-cookie", "")
5 for cookie in cookies.split(";"):
6 if "tt_chain_token=" in cookie:
7 return cookie.split("tt_chain_token=")[1].split(";")[0]
8 return ""Restricciones regionales
Los videos de TikTok pueden tener restricciones regionales; algunos solo son accesibles en países específicos:
1# 使用代理下载特定地区视频
2proxies = {
3 "http://": "http://us-proxy:8080",
4 "https://": "http://us-proxy:8080",
5}
6
7async with httpx.AsyncClient(proxies=proxies) as client:
8 # ...Notas
- Restricción regional: Algunos videos requieren IP de EE. UU./Europa para poder acceder
- Caducidad del enlace: El período de validez del enlace es de aproximadamente 24 horas
- Sin marca de agua: Prioriza el uso del campo
downloadAddr
Bilibili (Bilibili)
Características de los enlaces de video
Los enlaces de video de Bilibili son bastante especiales: el audio y el video están separados:
1# 视频流 (DASH)
2https://upos-sz-mirrorali.bilivideo.com/xxx/xxx.m4s
3
4# 音频流 (DASH)
5https://upos-sz-mirrorali.bilivideo.com/xxx/xxx.m4s
6
7# FLV 格式 (旧版)
8https://upos-sz-mirrorali.bilivideo.com/xxx/xxx.flvRequisitos de descarga
| Encabezado de solicitud | ¿Obligatorio? | Valor |
|---|---|---|
Referer | Obligatorio | https://www.bilibili.com/ |
User-Agent | Recomendado | UA del navegador |
Cookie | HD, 4K obligatorio | SESSDATA, etc. |
Range | Recomendado | Descarga por segmentos |
Ejemplo de descarga en el servidor
1import httpx
2import subprocess
3from pathlib import Path
4
5async def download_bilibili_video(
6 video_url: str,
7 audio_url: str,
8 save_path: str,
9 cookie: str = None
10) -> bool:
11 """
12 下载 B站 视频 (需要合并音视频)
13
14 Args:
15 video_url: 视频流地址
16 audio_url: 音频流地址
17 save_path: 最终保存路径
18 cookie: 可选,高清视频需要 SESSDATA
19 """
20 headers = {
21 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
22 "Referer": "https://www.bilibili.com/", # 必须,否则 403
23 "Accept": "*/*",
24 "Accept-Encoding": "identity",
25 "Origin": "https://www.bilibili.com",
26 }
27
28 if cookie:
29 headers["Cookie"] = cookie
30
31 # 临时文件路径
32 video_temp = save_path + ".video.m4s"
33 audio_temp = save_path + ".audio.m4s"
34
35 async with httpx.AsyncClient(follow_redirects=True, timeout=120) as client:
36 # 下载视频流
37 async with client.stream("GET", video_url, headers=headers) as response:
38 if response.status_code not in [200, 206]:
39 return False
40 with open(video_temp, "wb") as f:
41 async for chunk in response.aiter_bytes(chunk_size=8192):
42 f.write(chunk)
43
44 # 下载音频流
45 async with client.stream("GET", audio_url, headers=headers) as response:
46 if response.status_code not in [200, 206]:
47 return False
48 with open(audio_temp, "wb") as f:
49 async for chunk in response.aiter_bytes(chunk_size=8192):
50 f.write(chunk)
51
52 # 使用 FFmpeg 合并音视频
53 try:
54 subprocess.run([
55 "ffmpeg", "-y",
56 "-i", video_temp,
57 "-i", audio_temp,
58 "-c:v", "copy",
59 "-c:a", "copy",
60 save_path
61 ], check=True, capture_output=True)
62
63 # 删除临时文件
64 Path(video_temp).unlink()
65 Path(audio_temp).unlink()
66 return True
67 except subprocess.CalledProcessError:
68 return FalseRelación entre calidad de imagen y Cookie
| Calidad | ¿Requiere inicio de sesión? | Requisito de Cookie |
|---|---|---|
| 360P/480P | No | Ninguno |
| 720P | Sí | SESSDATA |
| 1080P | Sí | SESSDATA + miembro premium |
| 4K/HDR | Sí | SESSDATA + miembro premium |
Notas
- Debe combinarse: El formato DASH separa audio y video; se debe usar FFmpeg para combinarlos
- Referer obligatorio: Sin el Referer correcto se devolverá 403
- Caducidad del enlace: Válido durante aproximadamente 2 horas
- Descarga por segmentos: Para archivos grandes se recomienda usar el encabezado Range para descargar por segmentos
Xiaohongshu (Xiaohongshu)
Características de los enlaces de video
1# 视频地址
2https://sns-video-bd.xhscdn.com/xxx.mp4
3https://sns-video-hw.xhscdn.com/xxx.mp4
4
5# 图片地址
6https://sns-img-bd.xhscdn.com/xxx.jpgRequisitos de descarga
| Encabezado de solicitud | ¿Obligatorio? | Valor |
|---|---|---|
Referer | Obligatorio | https://www.xiaohongshu.com/ |
User-Agent | Recomendado | UA del navegador |
Cookie | Opcional | Algunos contenidos lo requieren |
Ejemplo de descarga en el servidor
1import httpx
2
3async def download_xiaohongshu_video(video_url: str, save_path: str) -> bool:
4 """
5 下载小红书视频
6
7 小红书对 Referer 检查较严格
8 """
9 headers = {
10 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
11 "Referer": "https://www.xiaohongshu.com/", # 必须
12 "Accept": "*/*",
13 "Origin": "https://www.xiaohongshu.com",
14 "Sec-Fetch-Dest": "video",
15 "Sec-Fetch-Mode": "cors",
16 "Sec-Fetch-Site": "cross-site",
17 }
18
19 async with httpx.AsyncClient(follow_redirects=True, timeout=60) as client:
20 async with client.stream("GET", video_url, headers=headers) as response:
21 if response.status_code in [200, 206]:
22 with open(save_path, "wb") as f:
23 async for chunk in response.aiter_bytes(chunk_size=8192):
24 f.write(chunk)
25 return True
26 return FalseDescarga de imágenes
Las imágenes de Xiaohongshu también tienen anti-hotlinking:
1async def download_xiaohongshu_image(image_url: str, save_path: str) -> bool:
2 """下载小红书图片"""
3 headers = {
4 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
5 "Referer": "https://www.xiaohongshu.com/",
6 "Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
7 }
8
9 async with httpx.AsyncClient(follow_redirects=True) as client:
10 response = await client.get(image_url, headers=headers)
11 if response.status_code == 200:
12 with open(save_path, "wb") as f:
13 f.write(response.content)
14 return True
15 return FalseNotas
- Referer estricto: Debe llevar un Referer de
xiaohongshu.com - Nodos CDN:
bd(Baidu Cloud),hw(Huawei Cloud), etc., diferentes nodos - Sin marca de agua: Los enlaces devueltos por la API suelen estar sin marca de agua
Weibo (Weibo)
Características de los enlaces de video
Los enlaces de video de Weibo tienen varios formatos:
1# 标准视频
2https://f.video.weibocdn.com/xxx.mp4
3
4# 直播回放
5https://live.video.weibocdn.com/xxx.flv
6
7# 高清视频
8https://video.weibo.com/media/play?fid=xxxRequisitos de descarga
| Encabezado de solicitud | ¿Obligatorio? | Valor |
|---|---|---|
Referer | Obligatorio | https://weibo.com/ o https://m.weibo.cn/ |
User-Agent | Recomendado | UA del navegador |
Cookie | Algunos lo requieren | Necesario para videos privados |
Ejemplo de descarga en el servidor
1import httpx
2
3async def download_weibo_video(video_url: str, save_path: str) -> bool:
4 """
5 下载微博视频
6
7 微博支持多个 Referer 域名
8 """
9 headers = {
10 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
11 "Referer": "https://weibo.com/", # 或 https://m.weibo.cn/
12 "Accept": "*/*",
13 "Accept-Encoding": "identity",
14 }
15
16 async with httpx.AsyncClient(follow_redirects=True, timeout=60) as client:
17 async with client.stream("GET", video_url, headers=headers) as response:
18 if response.status_code in [200, 206]:
19 with open(save_path, "wb") as f:
20 async for chunk in response.aiter_bytes(chunk_size=8192):
21 f.write(chunk)
22 return True
23 return FalseSelección de múltiples calidades
Los videos de Weibo suelen ofrecer múltiples calidades:
1def select_best_quality(media_info: dict) -> str:
2 """选择最高清晰度的视频链接"""
3 # 清晰度优先级
4 quality_priority = ["mp4_720p_mp4", "mp4_hd_mp4", "mp4_ld_mp4"]
5
6 playback_list = media_info.get("playback_list", [])
7
8 for quality in quality_priority:
9 for item in playback_list:
10 if item.get("quality_label") == quality:
11 return item.get("play_info", {}).get("url")
12
13 # 降级使用 stream_url
14 return media_info.get("stream_url")Notas
- Dominio del Referer: Admite
weibo.comym.weibo.cn - Múltiples calidades: En los datos devueltos hay varias calidades disponibles
- Caducidad del enlace: Algunos enlaces tienen limitaciones de tiempo
YouTube
Características de los enlaces de video
Los enlaces de video de YouTube suelen presentarse en las siguientes formas:
1# 标准视频流地址
2https://rr1---sn-xxx.googlevideo.com/videoplayback?expire=xxx&ei=xxx&ip=xxx&id=xxx&itag=xxx&source=youtube&...
3
4# 自适应流地址 (DASH)
5https://manifest.googlevideo.com/api/manifest/dash/...
6
7# 直播流地址 (HLS)
8https://manifest.googlevideo.com/api/manifest/hls_variant/...Requisitos de descarga
Restricción estricta de IP: La dirección del flujo de video de YouTube tiene una verificación extremadamente estricta de la ubicación geográfica de la IP; solo se puede descargar con éxito usando una IP de la zona de Los Ángeles, California, Estados Unidos; de lo contrario, se devolverá un error 403 Forbidden.
| Encabezado de solicitud | ¿Obligatorio? | Valor |
|---|---|---|
User-Agent | Obligatorio | UA del navegador |
Referer | Recomendado | https://www.youtube.com/ |
Origin | Recomendado | https://www.youtube.com |
| IP proxy | Obligatorio | IP de Los Ángeles, California, EE. UU. |
Explicación de la restricción regional de IP
La dirección del flujo de video de YouTube se vincula a la IP del solicitante al generarse; al descargar, debe usarse una IP de la misma región:
1# 视频流 URL 中包含 IP 绑定参数
2https://rr1---sn-xxx.googlevideo.com/videoplayback?
3 expire=1234567890 # 过期时间
4 &ei=xxx # 加密标识
5 &ip=1.2.3.4 # 绑定的 IP 地址 (关键!)
6 &id=xxx
7 &itag=137 # 视频质量标识
8 &source=youtube
9 &...¿Por qué debe ser una IP de Los Ángeles?
- La mayoría de los nodos CDN de YouTube están ubicados en la costa oeste de EE. UU.
- Al generar la URL del flujo de video, se selecciona el CDN más cercano según la IP de la solicitud
- Si la región de la IP no coincide al descargar, se producirá un error 403
- Los Ángeles es la ubicación de los principales centros de datos de YouTube, por lo que ofrece la mejor compatibilidad
Ejemplo de descarga en el servidor
1import httpx
2
3async def download_youtube_video(
4 video_url: str,
5 save_path: str,
6 proxy: str = None # 必须是美国加州洛杉矶代理
7) -> bool:
8 """
9 下载 YouTube 视频
10
11 重要:
12 - 必须使用美国加利福尼亚州洛杉矶地区的代理 IP
13 - 其他地区 IP 会返回 403 Forbidden
14 - 视频流 URL 有时效性,通常 6 小时内有效
15 """
16 headers = {
17 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
18 "Referer": "https://www.youtube.com/",
19 "Origin": "https://www.youtube.com",
20 "Accept": "*/*",
21 "Accept-Language": "en-US,en;q=0.9",
22 "Accept-Encoding": "identity", # 避免压缩
23 "Range": "bytes=0-", # 支持断点续传
24 "Sec-Fetch-Dest": "video",
25 "Sec-Fetch-Mode": "no-cors",
26 "Sec-Fetch-Site": "cross-site",
27 }
28
29 # 代理配置 - 必须是洛杉矶 IP
30 proxies = None
31 if proxy:
32 proxies = {
33 "http://": proxy,
34 "https://": proxy,
35 }
36
37 async with httpx.AsyncClient(
38 follow_redirects=True,
39 timeout=120,
40 proxies=proxies
41 ) as client:
42 async with client.stream("GET", video_url, headers=headers) as response:
43 if response.status_code in [200, 206]:
44 with open(save_path, "wb") as f:
45 async for chunk in response.aiter_bytes(chunk_size=8192):
46 f.write(chunk)
47 return True
48 elif response.status_code == 403:
49 print("下载失败: IP 地区不符合要求,请使用美国加州洛杉矶的代理")
50 elif response.status_code == 410:
51 print("下载失败: 视频链接已过期,请重新获取")
52 return FalseTratamiento de separación de audio y video
Los videos en alta definición de YouTube (1080p+) usan formato DASH, y el audio y el video están separados:
1import subprocess
2from pathlib import Path
3
4async def download_youtube_video_with_audio(
5 video_url: str,
6 audio_url: str,
7 save_path: str,
8 proxy: str = None
9) -> bool:
10 """
11 下载 YouTube 视频并合并音频
12
13 YouTube 1080p 及以上清晰度的视频,音频和视频是分离的
14 需要分别下载后使用 FFmpeg 合并
15 """
16 video_temp = save_path + ".video.mp4"
17 audio_temp = save_path + ".audio.m4a"
18
19 # 下载视频流
20 success = await download_youtube_video(video_url, video_temp, proxy)
21 if not success:
22 return False
23
24 # 下载音频流
25 success = await download_youtube_video(audio_url, audio_temp, proxy)
26 if not success:
27 return False
28
29 # 使用 FFmpeg 合并
30 try:
31 subprocess.run([
32 "ffmpeg", "-y",
33 "-i", video_temp,
34 "-i", audio_temp,
35 "-c:v", "copy",
36 "-c:a", "aac",
37 save_path
38 ], check=True, capture_output=True)
39
40 # 删除临时文件
41 Path(video_temp).unlink()
42 Path(audio_temp).unlink()
43 return True
44 except subprocess.CalledProcessError as e:
45 print(f"FFmpeg 合并失败: {e}")
46 return FalseEjemplo de configuración de proxy
1# 推荐的代理配置 - 必须是洛杉矶地区
2LA_PROXIES = [
3 "http://user:pass@la-proxy1.example.com:8080", # 洛杉矶代理 1
4 "http://user:pass@la-proxy2.example.com:8080", # 洛杉矶代理 2
5 "socks5://user:pass@la-socks.example.com:1080", # SOCKS5 代理
6]
7
8# 验证代理 IP 是否在洛杉矶
9async def verify_proxy_location(proxy: str) -> bool:
10 """验证代理 IP 是否在洛杉矶地区"""
11 async with httpx.AsyncClient(proxies={"https://": proxy}) as client:
12 response = await client.get("https://ipapi.co/json/")
13 data = response.json()
14
15 city = data.get("city", "")
16 region = data.get("region", "")
17 country = data.get("country_code", "")
18
19 # 检查是否在加州洛杉矶
20 is_la = (
21 country == "US" and
22 region == "California" and
23 "Los Angeles" in city
24 )
25
26 if is_la:
27 print(f"✓ 代理 IP 位于: {city}, {region}, {country}")
28 else:
29 print(f"✗ 代理 IP 位于: {city}, {region}, {country} (不符合要求)")
30
31 return is_laTabla de referencia de identificadores de calidad (itag)
| itag | resolución | formato | ¿Incluye audio? |
|---|---|---|---|
| 18 | 360p | MP4 | Sí |
| 22 | 720p | MP4 | Sí |
| 137 | 1080p | MP4 | No (requiere combinar) |
| 248 | 1080p | WebM | No (requiere combinar) |
| 271 | 1440p | WebM | No (requiere combinar) |
| 313 | 2160p | WebM | No (requiere combinar) |
| 140 | - | audio M4A | Solo audio |
| 251 | - | audio WebM | Solo audio |
Notas
- Restricción de IP extremadamente estricta: Debe usarse una IP de Los Ángeles, California, EE. UU.; otras regiones (incluidos otros estados de EE. UU.) pueden fallar
- Caducidad del enlace: La URL del flujo de video suele ser válida durante 6 horas
- Separación de audio y video: Las calidades superiores a 720p requieren descargar por separado el audio y el video y luego combinarlos
- Limitación de velocidad: YouTube limita la velocidad de descarga; para archivos grandes se recomienda la descarga por segmentos
- Protección de derechos de autor: Algunos videos tienen protección DRM y no se pueden descargar directamente
Solución de descarga en frontend
Debido a las restricciones de CORS, el frontend no puede descargar directamente videos de la mayoría de las plataformas. Estas son las soluciones viables:
Opción 1: Proxy en backend (recomendado)
1// 前端请求后端代理接口
2async function downloadVideo(videoUrl, platform, filename) {
3 const proxyUrl = `/api/download/video?url=${encodeURIComponent(videoUrl)}&platform=${platform}`;
4
5 const response = await fetch(proxyUrl);
6 const blob = await response.blob();
7
8 // 创建下载链接
9 const url = URL.createObjectURL(blob);
10 const a = document.createElement('a');
11 a.href = url;
12 a.download = filename || 'video.mp4';
13 document.body.appendChild(a);
14 a.click();
15 document.body.removeChild(a);
16 URL.revokeObjectURL(url);
17}Opción 2: Abrir en una nueva ventana (disponible en algunas plataformas)
1// 部分平台链接可以直接在新窗口打开下载
2function openVideoInNewTab(videoUrl) {
3 window.open(videoUrl, '_blank');
4}Opción 3: Usar el atributo download (escenario del mismo origen)
1<!-- 仅适用于同源或允许 CORS 的资源 -->
2<a href="video.mp4" download="video.mp4">下载视频</a>Solución de descarga mediante proxy en backend
Ejemplo de proxy con FastAPI
1from fastapi import FastAPI, Query, Response
2from fastapi.responses import StreamingResponse
3import httpx
4
5app = FastAPI()
6
7# 各平台的请求头配置
8PLATFORM_HEADERS = {
9 "douyin": {
10 "Referer": "https://www.douyin.com/",
11 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
12 },
13 "tiktok": {
14 "Referer": "https://www.tiktok.com/",
15 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
16 },
17 "bilibili": {
18 "Referer": "https://www.bilibili.com/",
19 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
20 },
21 "xiaohongshu": {
22 "Referer": "https://www.xiaohongshu.com/",
23 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
24 },
25 "weibo": {
26 "Referer": "https://weibo.com/",
27 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
28 },
29}
30
31
32@app.get("/api/download/video")
33async def proxy_video(
34 url: str = Query(..., description="视频 URL"),
35 platform: str = Query(..., description="平台标识"),
36):
37 """
38 视频代理下载接口
39
40 解决前端跨域和防盗链问题
41 """
42 headers = PLATFORM_HEADERS.get(platform, {})
43
44 async def video_stream():
45 async with httpx.AsyncClient(follow_redirects=True, timeout=120) as client:
46 async with client.stream("GET", url, headers=headers) as response:
47 async for chunk in response.aiter_bytes(chunk_size=8192):
48 yield chunk
49
50 return StreamingResponse(
51 video_stream(),
52 media_type="video/mp4",
53 headers={
54 "Content-Disposition": "attachment; filename=video.mp4",
55 "Access-Control-Allow-Origin": "*", # 允许跨域
56 }
57 )
58
59
60@app.get("/api/download/image")
61async def proxy_image(
62 url: str = Query(..., description="图片 URL"),
63 platform: str = Query(..., description="平台标识"),
64):
65 """图片代理下载接口"""
66 headers = PLATFORM_HEADERS.get(platform, {})
67
68 async with httpx.AsyncClient(follow_redirects=True) as client:
69 response = await client.get(url, headers=headers)
70
71 return Response(
72 content=response.content,
73 media_type=response.headers.get("content-type", "image/jpeg"),
74 headers={
75 "Access-Control-Allow-Origin": "*",
76 }
77 )Ejemplo de descarga con progreso
1import httpx
2from tqdm import tqdm
3
4async def download_with_progress(url: str, save_path: str, headers: dict) -> bool:
5 """带进度条的下载"""
6 async with httpx.AsyncClient(follow_redirects=True, timeout=120) as client:
7 async with client.stream("GET", url, headers=headers) as response:
8 total = int(response.headers.get("content-length", 0))
9
10 with open(save_path, "wb") as f:
11 with tqdm(total=total, unit="B", unit_scale=True, desc="下载中") as pbar:
12 async for chunk in response.aiter_bytes(chunk_size=8192):
13 f.write(chunk)
14 pbar.update(len(chunk))
15
16 return True
17 return FalseResumen
Tabla rápida de puntos clave de descarga por plataforma
| Plataforma | Referer | Requisito de Cookie | Tratamiento especial | Caducidad del enlace |
|---|---|---|---|---|
| Douyin | https://www.douyin.com/ | Opcional | Ninguno | 24 horas |
| TikTok Web | https://www.tiktok.com/ | Obligatorio tt_chain_token | Requiere proxy regional | 1-6 horas |
| TikTok App | No necesario | No necesario | Uso recomendado | Más larga |
| Bilibili | https://www.bilibili.com/ | Para HD es obligatorio SESSDATA | La separación de audio y video requiere combinar | 2 horas |
| Xiaohongshu | https://www.xiaohongshu.com/ | Opcional | Ninguno | Más larga |
https://weibo.com/ | Algunos lo requieren | Múltiples calidades | Más larga | |
| YouTube | https://www.youtube.com/ | No necesario | IP de Los Ángeles obligatoria + separación de audio y video | 6 horas |
Solución de problemas comunes
| Código de error | Posible causa | Solución |
|---|---|---|
| 403 Forbidden | Referer incorrecto o ausente | Verificar el encabezado Referer |
| 403 Forbidden | TikTok sin tt_chain_token | Añadir Cookie o usar enlace de la app |
| 403 Forbidden | La IP de YouTube no coincide con la región | Usar un proxy de Los Ángeles, California, EE. UU. |
| 404 Not Found | Enlace caducado | Obtener el enlace de nuevo |
| 410 Gone | Enlace de YouTube caducado | Obtener de nuevo la URL del flujo de video |
| CORS Error | Restricción de CORS del navegador | Usar proxy en backend |
| Timeout | Problemas de red o archivo demasiado grande | Aumentar el tiempo de espera, descargar por segmentos |
Mejores prácticas
- Establecer siempre Referer: Es un requisito necesario para la mayoría de las plataformas
- Usar proxy en backend: La mejor solución para resolver problemas de CORS en frontend
- Gestionar la caducidad del enlace: Usar el enlace a tiempo; si expira, obtenerlo de nuevo
- Admitir reanudación de descargas: Para archivos grandes, usar el encabezado Range para descargar por segmentos
- Reintentos ante errores: Implementar un mecanismo de reintento cuando la red sea inestable
Contáctanos
Si después de seguir las instrucciones de este documento aún no puedes descargar el video con éxito, contacta al servicio de atención al cliente para obtener ayuda:
- Documentación oficial: https://docs.tikhub.io/
- Comunidad de Discord: https://discord.gg/Pu2uKkFu6u
- Atención al cliente en línea (Chaport): Visita la ventana de atención al cliente en la esquina inferior derecha del sitio oficial
Al contactar al servicio de atención al cliente, proporciona la siguiente información para que podamos ayudarte más rápidamente:
- Nombre de la plataforma: Douyin/TikTok/Bilibili/Xiaohongshu/Weibo/YouTube
- Enlace del video: URL original de la página del video
- Mensaje de error: código de error completo y descripción del error
- Configuración de encabezados de solicitud: información de Referer, Cookie, etc. que estás usando
- Información del proxy: si usas proxy, proporciona la región del proxy