Skip to content

Commit ede6fb8

Browse files
committed
feat: support video download for zhihu.com #1203
1 parent 7580afe commit ede6fb8

4 files changed

Lines changed: 137 additions & 0 deletions

File tree

app/register.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,5 @@ import (
3939
_ "github.com/iawia002/lux/extractors/yinyuetai"
4040
_ "github.com/iawia002/lux/extractors/youku"
4141
_ "github.com/iawia002/lux/extractors/youtube"
42+
_ "github.com/iawia002/lux/extractors/zhihu"
4243
)

extractors/zhihu/types.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package zhihu
2+
3+
// minimum field
4+
type video struct {
5+
PlayList struct {
6+
FHD resolution `json:"FHD"`
7+
HD resolution `json:"HD"`
8+
SD resolution `json:"SD"`
9+
} `json:"playlist_v2"`
10+
}
11+
12+
// minimum field
13+
type resolution struct {
14+
Size int64 `json:"size"`
15+
Format string `json:"format"`
16+
PlayURL string `json:"play_url"`
17+
}

extractors/zhihu/zhihu.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package zhihu
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/iawia002/lux/extractors"
7+
"github.com/iawia002/lux/request"
8+
"github.com/iawia002/lux/utils"
9+
"github.com/pkg/errors"
10+
"strings"
11+
)
12+
13+
const (
14+
videoURL = "www.zhihu.com/zvideo"
15+
api = "https://lens.zhihu.com/api/v4/videos/"
16+
)
17+
18+
func init() {
19+
extractors.Register("zhihu", New())
20+
}
21+
22+
type extractor struct{}
23+
24+
func New() extractors.Extractor {
25+
return &extractor{}
26+
}
27+
28+
func (e *extractor) Extract(url string, option extractors.Options) ([]*extractors.Data, error) {
29+
if !strings.Contains(url, videoURL) {
30+
return nil, errors.WithStack(extractors.ErrURLParseFailed)
31+
}
32+
33+
var err error
34+
html, err := request.Get(url, url, nil)
35+
if err != nil {
36+
return nil, errors.WithStack(err)
37+
}
38+
39+
videoID := utils.MatchOneOf(html, `"videoId":"(\d+)"`)
40+
titleMatch := utils.MatchOneOf(html, `<title.*?>(.*?)</title>`)
41+
42+
if len(videoID) <= 1 {
43+
return nil, errors.New("zhihu video id extract failed")
44+
}
45+
46+
title := "Unknown"
47+
if len(titleMatch) > 1 {
48+
title = titleMatch[1]
49+
}
50+
51+
resp, err := request.GetByte(fmt.Sprintf("%s%s", api, videoID[1]), url, nil)
52+
if err != nil {
53+
return nil, errors.WithStack(err)
54+
}
55+
var data video
56+
if err = json.Unmarshal(resp, &data); err != nil {
57+
return nil, errors.WithStack(err)
58+
}
59+
60+
streams := make(map[string]*extractors.Stream)
61+
resolutions := map[string]resolution{
62+
"FHD": data.PlayList.FHD,
63+
"HD": data.PlayList.HD,
64+
"SD": data.PlayList.SD,
65+
}
66+
67+
for k, v := range resolutions {
68+
stream := &extractors.Stream{
69+
Parts: []*extractors.Part{
70+
{
71+
URL: v.PlayURL,
72+
Size: v.Size,
73+
Ext: v.Format,
74+
},
75+
},
76+
Size: v.Size,
77+
}
78+
streams[k] = stream
79+
}
80+
81+
return []*extractors.Data{
82+
{
83+
Site: "知乎 zhihu.com",
84+
Title: title,
85+
Streams: streams,
86+
Type: extractors.DataTypeVideo,
87+
URL: url,
88+
},
89+
}, nil
90+
}

extractors/zhihu/zhihu_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package zhihu
2+
3+
import (
4+
"github.com/iawia002/lux/extractors"
5+
"github.com/iawia002/lux/test"
6+
"testing"
7+
)
8+
9+
func TestDownload(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
args test.Args
13+
}{
14+
{
15+
name: "video test",
16+
args: test.Args{
17+
URL: "https://www.zhihu.com/zvideo/1620162752064061440",
18+
Title: `Cursor, GPT-4 驱动的强大代码编辑器 - 知乎`,
19+
},
20+
},
21+
}
22+
for _, tt := range tests {
23+
t.Run(tt.name, func(t *testing.T) {
24+
data, err := New().Extract(tt.args.URL, extractors.Options{})
25+
test.CheckError(t, err)
26+
test.Check(t, tt.args, data[0])
27+
})
28+
}
29+
}

0 commit comments

Comments
 (0)