/
douyin.go
executable file
·157 lines (131 loc) · 4.14 KB
/
douyin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package parser
import (
"bytes"
"errors"
"fmt"
"math/rand"
"net/url"
"strings"
"github.com/PuerkitoBio/goquery"
"github.com/tidwall/gjson"
"github.com/go-resty/resty/v2"
)
type douYin struct{}
func (d douYin) parseVideoID(videoId string) (*VideoParseInfo, error) {
reqUrl := fmt.Sprintf("https://www.iesdouyin.com/share/video/%s", videoId)
client := resty.New()
res, err := client.R().
SetHeader(HttpHeaderUserAgent, "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 Edg/122.0.0.0").
Get(reqUrl)
if err != nil {
return nil, err
}
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(res.Body()))
if err != nil {
return nil, err
}
returnData := doc.Find("#RENDER_DATA").Text()
decodeData, err := url.QueryUnescape(returnData)
if err != nil {
return nil, err
}
data := gjson.Get(decodeData, "app.videoInfoRes.item_list.0")
if !data.Exists() {
return nil, fmt.Errorf(
"get video info fail: %s - %s",
gjson.GetBytes(res.Body(), "filter_list.0.filter_reason"),
gjson.GetBytes(res.Body(), "filter_list.0.notice"),
)
}
// 获取图集图片地址
imagesObjArr := data.Get("images").Array()
images := make([]string, 0, len(imagesObjArr))
for _, imageItem := range data.Get("images").Array() {
imageUrl := imageItem.Get("url_list.0").String()
if len(imageUrl) > 0 {
images = append(images, imageUrl)
}
}
// 获取视频播放地址
videoUrl := data.Get("video.play_addr.url_list.0").String()
videoUrl = strings.ReplaceAll(videoUrl, "playwm", "play")
// 如果图集地址不为空时,因为没有视频,上面抖音返回的视频地址无法访问,置空处理
if len(images) > 0 {
videoUrl = ""
}
videoInfo := &VideoParseInfo{
Title: data.Get("desc").String(),
VideoUrl: videoUrl,
MusicUrl: "",
CoverUrl: data.Get("video.cover.url_list.0").String(),
Images: images,
}
videoInfo.Author.Uid = data.Get("author.sec_uid").String()
videoInfo.Author.Name = data.Get("author.nickname").String()
videoInfo.Author.Avatar = data.Get("author.avatar_thumb.url_list.0").String()
// 视频地址非空时,获取302重定向之后的视频地址
// 图集时,视频地址为空,不处理
if len(videoInfo.VideoUrl) > 0 {
d.getRedirectUrl(videoInfo)
}
return videoInfo, nil
}
func (d douYin) parseShareUrl(shareUrl string) (*VideoParseInfo, error) {
client := resty.New()
// disable redirects in the HTTP client, get params before redirects
client.SetRedirectPolicy(resty.NoRedirectPolicy())
res, err := client.R().
SetHeader(HttpHeaderUserAgent, DefaultUserAgent).
Get(shareUrl)
// 非 resty.ErrAutoRedirectDisabled 错误时,返回错误
if !errors.Is(err, resty.ErrAutoRedirectDisabled) {
return nil, err
}
locationRes, err := res.RawResponse.Location()
if err != nil {
return nil, err
}
videoId, err := d.parseVideoIdFromPath(locationRes.Path)
if err != nil {
return nil, err
}
if len(videoId) <= 0 {
return nil, errors.New("parse video id from share url fail")
}
// 西瓜视频解析方式不一样
if strings.Contains(locationRes.Host, "ixigua.com") {
return xiGua{}.parseVideoID(videoId)
}
return d.parseVideoID(videoId)
}
func (d douYin) parseVideoIdFromPath(urlPath string) (string, error) {
if len(urlPath) <= 0 {
return "", errors.New("url path is empty")
}
urlPath = strings.Trim(urlPath, "/")
urlSplit := strings.Split(urlPath, "/")
// 获取最后一个元素
if len(urlSplit) > 0 {
return urlSplit[len(urlSplit)-1], nil
}
return "", errors.New("parse video id from path fail")
}
func (d douYin) getRedirectUrl(videoInfo *VideoParseInfo) {
client := resty.New()
client.SetRedirectPolicy(resty.NoRedirectPolicy())
res2, _ := client.R().
SetHeader(HttpHeaderUserAgent, DefaultUserAgent).
Get(videoInfo.VideoUrl)
locationRes, _ := res2.RawResponse.Location()
if locationRes != nil {
(*videoInfo).VideoUrl = locationRes.String()
}
}
func (d douYin) randSeq(n int) string {
letters := []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}