Go语言实现抖音视频下载器
🚀 Go语言实现抖音视频下载器(完整代码讲解)
下面是一篇完整的抖音视频下载工具的实现及说明。该工具采用 Go 语言 编写,支持提取 无水印视频、自动重试机制 和 保存本地功能,并通过多种方式保障视频链接获取的成功率。
📌 功能介绍
- 支持输入 抖音视频分享链接;
- 自动获取 无水印视频真实链接;
- 支持多种视频链接提取策略(API、网页爬取、无水印第三方接口);
- 视频文件保存到本地;
- 支持重试和备用下载方案,提高下载成功率。
🛠️ 环境要求
- Go 1.16 及以上版本
- 网络环境畅通(访问抖音分享链接、调用 API 接口)
💻 完整代码
以下是完整、开箱即用的 main.go
文件:
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
// 主入口函数
func main() {
// 抖音视频分享链接(替换为自己的链接)
videoURL := "https://v.douyin.com/xxxxxxx/"
// 保存视频到这个目录
savePath := "./downloaded_videos"
err := downloadVideo(videoURL, savePath)
if err != nil {
fmt.Printf("❌ 下载失败: %v\n", err)
} else {
fmt.Println("✅ 下载完成")
}
}
// 核心逻辑
// 下载视频的主方法
func downloadVideo(shareURL string, savePath string) error {
fmt.Println("🚀 获取视频URL中...")
// 获取真实的视频播放链接
actualURL, err := getActualVideoURL(shareURL)
if err != nil {
return fmt.Errorf("获取视频URL失败: %v", err)
}
fmt.Printf("🎯 获取到视频地址: %s\n", actualURL)
// 视频文件名
fileName := "douyin_video.mp4"
// 创建保存目录
if err := os.MkdirAll(savePath, os.ModePerm); err != nil {
return fmt.Errorf("创建目录失败: %v", err)
}
// 完整路径
filePath := filepath.Join(savePath, fileName)
// 开始下载视频
fmt.Println("⬇️ 开始下载视频...")
err = downloadFile(actualURL, filePath)
if err != nil {
return fmt.Errorf("视频下载失败: %v", err)
}
fmt.Printf("✅ 视频成功保存到: %s\n", filePath)
return nil
}
// 通过API或HTML解析视频地址
// 获取真实的抖音视频播放地址
func getActualVideoURL(shareURL string) (string, error) {
// 先尝试 API 获取视频地址
videoURL, err := getVideoInfoFromAPI(shareURL)
if err == nil {
return videoURL, nil
}
fmt.Println("⚠️ API获取失败,尝试解析网页源码...")
// 再尝试页面解析
videoURL, err = getVideoURLFromHTML(shareURL)
if err == nil {
return videoURL, nil
}
fmt.Println("⚠️ 网页解析失败,尝试无水印API...")
// 最后尝试第三方无水印API
videoURL, err = getNoWatermarkVideo(shareURL)
if err == nil {
return videoURL, nil
}
return "", fmt.Errorf("无法获取有效视频地址")
}
// 通过抖音官方API获取视频信息
func getVideoInfoFromAPI(shareURL string) (string, error) {
client := &http.Client{
Timeout: 10 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return nil
},
}
// 第一步:请求分享URL获取真实视频ID
req, _ := http.NewRequest("GET", shareURL, nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X)")
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
finalURL := resp.Request.URL.String()
fmt.Printf("👉 真实跳转URL: %s\n", finalURL)
// 从URL或页面源码中提取视频ID
re := regexp.MustCompile(`/video/(\d+)`)
matches := re.FindStringSubmatch(finalURL)
if len(matches) < 2 {
return "", fmt.Errorf("无法提取视频ID")
}
videoID := matches[1]
// 第二步:请求API获取视频播放地址
apiURL := fmt.Sprintf("https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=%s", videoID)
apiReq, _ := http.NewRequest("GET", apiURL, nil)
apiReq.Header.Set("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X)")
apiResp, err := client.Do(apiReq)
if err != nil {
return "", err
}
defer apiResp.Body.Close()
body, _ := ioutil.ReadAll(apiResp.Body)
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return "", err
}
itemList := result["item_list"].([]interface{})
if len(itemList) == 0 {
return "", fmt.Errorf("API返回空数据")
}
item := itemList[0].(map[string]interface{})
video := item["video"].(map[string]interface{})
playAddr := video["play_addr"].(map[string]interface{})
urlList := playAddr["url_list"].([]interface{})
return urlList[0].(string), nil
}
// 解析网页源码获取视频URL
func getVideoURLFromHTML(shareURL string) (string, error) {
client := &http.Client{
Timeout: 10 * time.Second,
}
req, _ := http.NewRequest("GET", shareURL, nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X)")
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
// 使用正则提取播放地址
re := regexp.MustCompile(`"playAddr":"(.*?)"`)
matches := re.FindStringSubmatch(string(body))
if len(matches) < 2 {
return "", fmt.Errorf("无法解析视频地址")
}
// 处理转义字符
videoURL := strings.ReplaceAll(matches[1], "\\u002F", "/")
return videoURL, nil
}
// 调用第三方API获取无水印视频
func getNoWatermarkVideo(shareURL string) (string, error) {
apiURL := fmt.Sprintf("https://api.douyin.wtf/api?url=%s", url.QueryEscape(shareURL))
client := &http.Client{
Timeout: 10 * time.Second,
}
req, _ := http.NewRequest("GET", apiURL, nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X)")
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return "", err
}
videoURL := result["nwm_video_url"].(string)
if videoURL == "" {
return "", fmt.Errorf("第三方API未返回有效URL")
}
return videoURL, nil
}
// 下载文件
func downloadFile(fileURL, filePath string) error {
client := &http.Client{
Timeout: 30 * time.Second,
}
req, _ := http.NewRequest("GET", fileURL, nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("状态码错误: %d", resp.StatusCode)
}
out, err := os.Create(filePath)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
return err
}
return nil
}
📂 文件结构
douyin_downloader/
├── main.go
├── downloaded_videos/ # 下载后的视频保存目录
📝 代码结构解析
模块 | 说明 |
---|---|
main() | 程序入口,初始化分享链接和保存目录,调用下载方法 |
downloadVideo() | 核心下载逻辑,先获取视频链接,执行下载,保存到本地 |
getActualVideoURL() | 获取真实视频地址,依次尝试 API、HTML解析、第三方API获取 |
getVideoInfoFromAPI() | 从分享链接跳转获取视频ID,通过API提取真实播放地址 |
getVideoURLFromHTML() | 通过HTML源码解析 playAddr ,作为兜底方案 |
getNoWatermarkVideo() | 通过第三方无水印API获取无水印视频链接 |
downloadFile() | 下载视频流并保存文件 |
🧰 可扩展功能(进阶)
- 批量下载:读取多个链接批量处理;
- 命令行参数:支持 CLI 参数传递链接与路径;
- 进度条:下载时加入进度条提示;
- 并发优化:使用 Go 协程批量并发下载多个视频;
- 断点续传:支持大文件续传功能;
⚠️ 注意事项
- 抖音接口和数据结构可能变化,需定期维护正则表达式和API路径;
- 使用第三方API请注意接口稳定性;
- 仅供学习交流使用,不得用于任何非法用途;
✅ 运行方法
- 将
videoURL
替换为自己的抖音视频分享链接; - 终端执行:
go run main.go
- 下载完成后文件保存在
downloaded_videos
文件夹。
🎉 总结
本文展示了一个完整的 抖音无水印视频下载器,通过 API+网页解析+第三方接口 多种方式保证了视频下载的鲁棒性。Go 语言的并发和HTTP请求能力也让下载器高效稳定!