本文以分享技术为主,禁止将本文内容使用任何形式的商业形式或违法活动,后果自负。
---
# 原理介绍
通过代码模拟浏览器打开抖音的分享页面,从而获取到用户的信息,而不需要理解各种抖音请求的算法,最低门槛开始数据获取之路。
简单看一下下面的图,显示出来的信息都可以获取到,比如关注数,粉丝数,点赞数,作品数,喜欢数。
# 分享从哪里来?
在右下角菜单"我"-->右上角菜单"三条杠"-->我的二维码, 如果是其他用户也可以在用户信息界面的右上角菜单找到.
到目前为止就得到了一个链接,我们将要开始模拟浏览器打开请求获取到这个链接的数据。
使用到的技术点:
编程语言:Golang(需要go mod)
模拟浏览器:chromedp
便捷部署:Docker
获取到这个数据是这样一个过程:
1. 打开分享页面的链接,会发生302跳转,从location的头部信息拿到跳转后的URL
2. 通过跳转后的URL中拿到sec_uid参数
3. 使用sec_uid参数拼接数据接口,请求数据
就是这样三步就可以拿到用户的基本信息了。
项目准备工作,以linux命令展示:
创建项目目录(douyin): mkdir douyin
初始化go mod信息: go mod init
创建main.go文件 : touch main.go,将完整代码写入到main.go
以下是完整的代码:
package main
import (
"bytes"
"context"
"fmt"
"io"
"log"
"net/http"
"strings"
"time"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/cdproto/fetch"
"github.com/chromedp/cdproto/network"
"github.com/chromedp/chromedp"
)
func main() {
// 设置路由,如果访问/,则调用index方法
http.HandleFunc("/index", index)
// 启动web服务,监听9090端口
err := http.ListenAndServe(":8000", nil)
if err != nil {
panic(err)
}
log.Println("server.started")
}
func index(w http.ResponseWriter, r *http.Request) {
url := r.URL.Query().Get("url")
log.Println("get.url:", url)
result := loadDouYin(url)
fmt.Fprintf(w, result)
}
func loadDouYin(firstUrl string) string {
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.DisableGPU,
chromedp.NoDefaultBrowserCheck,
chromedp.NoSandbox,
chromedp.Flag("headless", true),
chromedp.Flag("ignore-certificate-errors", true),
)
//创建chrome
allocCtx, _ := chromedp.NewExecAllocator(context.Background(), opts...)
br, brcancel := chromedp.NewContext(allocCtx)
//打开一个空白页面测试一下是否打开
err := chromedp.Run(br, chromedp.Navigate("about:blank"))
if err != nil {
panic("chrome open blank failed, " + err.Error())
}
defer brcancel()
//新建一个tab
tab, tabcancel := chromedp.NewContext(br)
defer tabcancel()
secondurl := ""
//监听请求的数据
chromedp.ListenTarget(tab, func(ev interface{}) {
switch evnt := ev.(type) {
case *fetch.EventRequestPaused:
go func(evt *fetch.EventRequestPaused) {
var requestid = evt.RequestID
nctx := chromedp.FromContext(tab)
lctx := cdp.WithExecutor(tab, nctx.Target)
defer func() {
//如果不执行这一步 请求在发起一个请求后停住 比如打开一个页面有三个请求,如果不执行这里 则会卡在第一个请求后
err = fetch.ContinueRequest(requestid).Do(lctx)
if err != nil {
log.Println(" continuerequest failed," + err.Error())
}
}()
log.Println("evt.ResponseStatusCode:", evt.Request.URL, evt.ResponseStatusCode)
if evt.ResponseStatusCode == 302 {
for _, v := range evt.ResponseHeaders {
//拿到302请求中的location头部信息, 需要跳转的URL
if v.Name == "location" {
secondurl = v.Value
break
}
}
return
} else if evt.ResponseStatusCode == 304 {
return
}
if secondurl != "" {
//拿到了302的链接
log.Println("get.302.url.success:", secondurl)
} else {
log.Println("get.302.url.failed")
}
}(evnt)
}
})
err = chromedp.Run(tab, network.Enable(), fetch.Enable().WithPatterns([]*fetch.RequestPattern{
{
//监听请求的response类型
ResourceType: network.ResourceTypeDocument,
RequestStage: "Response",
},
}), chromedp.Navigate(firstUrl))
// time.Sleep(3 * time.Second)
if secondurl != "" {
//https://www.iesdouyin.com/share/user/96875399323?u_code=15a973a6e&sec_uid=MS4wLjABAAAADufhDZPJqGpNo-4ay7ZQtVsZbvCxVPEesV4PUAE2wyc×tamp=1602391166&utm_source=copy&utm_campaign=client_share&utm_medium=android&share_app_name=douyin
strs1 := strings.Split(secondurl, "&")
for i := 0; i < len(strs1); i++ {
if strings.Contains(strs1[i], "sec_uid") {
strs := strings.Split(strs1[i], "=")
if len(strs) > 0 {
secUid := strs[1]
log.Println("get secUid :", secUid)
url := "https://www.iesdouyin.com/web/api/v2/user/info/?sec_uid=" + secUid
return doDYinfo(url)
}
}
}
}
return "nothing"
}
func doDYinfo(url string) string {
request, err := http.NewRequest("GET", url, nil)
if err != nil {
panic(err)
}
request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")
request.Header.Add("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")
request.Header.Add("Connection", "keep-alive")
//写入浏览器的user agent
request.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36")
// 超时时间:5秒
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Do(request)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var buffer [512]byte
result := bytes.NewBuffer(nil)
for {
n, err := resp.Body.Read(buffer[0:])
result.Write(buffer[0:n])
if err != nil && err == io.EOF {
break
} else if err != nil {
panic(err)
}
}
log.Println("req.result:", result.String())
return result.String()
}
这段代码的最终结果是用Golang自带的http server启动了一个http服务,端口号是8000,开放了一个/index路径的API,API只有一个参数,也就是分享页面的链接.返回的结果就是抖音接口的数据.
例如:
1. 启动程序.
[root@normal11 douyin]# go run main.go
2. 请求一个分享页面的用户基本数据
[root@normal11 faither]# curl localhost:8000/index?url=https://v.douyin.com/JP1BNGa/
{"status_code":0,"user_info":{"uid":"59424687354","aweme_count":10,"following_count":42,"verification_type":1,"original_musician":{"music_count":0,"music_used_count":0},"followers_detail":null,"platform_sync_info":null,"secret":0,"type_label":null,"nickname":"PVP","avatar_thumb":{"uri":"tos-cn-avt-0015/70e93c2224fc560d7b72d634c7a30600","url_list":["https://p3-dy-ipv6.byteimg.com/img/tos-cn-avt-0015/70e93c2224fc560d7b72d634c7a30600~c5_100x100.jpeg?from=4010531038","https://p29-dy.byteimg.com/img/tos-cn-avt-0015/70e93c2224fc560d7b72d634c7a30600~c5_100x100.jpeg?from=4010531038","https://p6-dy-ipv6.byteimg.com/img/tos-cn-avt-0015/70e93c2224fc560d7b72d634c7a30600~c5_100x100.jpeg?from=4010531038"]},"avatar_medium":{"uri":"tos-cn-avt-0015/70e93c2224fc560d7b72d634c7a30600","url_list":["https://p9-dy.byteimg.com/img/tos-cn-avt-0015/70e93c2224fc560d7b72d634c7a30600~c5_720x720.jpeg?from=4010531038","https://p29-dy.byteimg.com/img/tos-cn-avt-0015/70e93c2224fc560d7b72d634c7a30600~c5_720x720.jpeg?from=4010531038","https://p3-dy-ipv6.byteimg.com/img/tos-cn-avt-0015/70e93c2224fc560d7b72d634c7a30600~c5_720x720.jpeg?from=4010531038"]},"follower_count":9315,"custom_verify":"","region":"CN","is_gov_media_vip":false,"short_id":"10566482","signature":"","avatar_larger":{"uri":"tos-cn-avt-0015/70e93c2224fc560d7b72d634c7a30600","url_list":["https://p26-dy.byteimg.com/img/tos-cn-avt-0015/70e93c2224fc560d7b72d634c7a30600~c5_1080x1080.jpeg?from=4010531038","https://p29-dy.byteimg.com/img/tos-cn-avt-0015/70e93c2224fc560d7b72d634c7a30600~c5_1080x1080.jpeg?from=4010531038","https://p3-dy-ipv6.byteimg.com/img/tos-cn-avt-0015/70e93c2224fc560d7b72d634c7a30600~c5_1080x1080.jpeg?from=4010531038"]},"favoriting_count":1459,"total_favorited":"462000","unique_id":"","geofencing":null,"policy_version":null},"extra":{"now":1602950144000,"logid":"202010172355440101980230914C625738"}}
注意:程序需要chromedp,这个是开源的模拟chrome的工具.需要自行安装,不希望安装这个工具可以接着往下看:使用Docker部署
# Docker便捷部署
编写了一个Dockerfile,用于docker部署,这样本机就可以不用折腾chromedp相关的环境问题了。
Dockerfile的内容如下:
FROM centos:7.6.1810
# 安装中文字体和chromedp
RUN yum install -y wget && \
yum install -y wqy-microhei-fonts wqy-zenhei-fonts && \
wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm && \
yum install -y ./google-chrome-stable_current_*.rpm && \
google-chrome --version && \
rm -rf *.rpm
# 设置go mod proxy 国内代理和golang path
ENV GOPROXY=https://goproxy.io GOPATH=/gopath PATH="${PATH}:/usr/local/go/bin"
# 定义使用的Golang 版本
ARG GO_VERSION=1.13.6
# 安装 golang 1.13.6
RUN wget "https://dl.google.com/go/go$GO_VERSION.linux-amd64.tar.gz" && \
rm -rf /usr/local/go && \
tar -C /usr/local -xzf "go$GO_VERSION.linux-amd64.tar.gz" && \
rm -rf *.tar.gz && \
go version && go env;
WORKDIR $GOPATH
COPY . douyin
RUN cd douyin && go build -o app;
EXPOSE 8000
CMD ["douyin/app"]
自行打包成docker镜像后就可以使用了,命令如下:
1. cd到项目目录下,目录应该有四个文件
[root@normal11 douyin]# ls
Dockerfile go.mod go.sum main.go
2. 打包docker镜像
docker build -t douyin .
3. 启动docker容器
docker run --rm -p 8000:8000 douyin
4. 访问接口获取数据
linux:
curl localhost:8000/index?url=你要获取数据的分享页面链接
windows:
在浏览器打开localhost:8000/index?url=你要获取数据的分享页面链接