TikTok 简易爬虫实现
tiktok web 页面,为各种爬虫准备了一份数据,就是其页面源码中,一个 id 为 SIGI_STATE
的 script 里的 json 数据。实际上,tiktok web 页面使用 sigi 框架,并且配合 SSR 将 sigi 应用的 state 保存在了 dom 里,相当于 vue 的 data。这个 state 里包含了用户的相关信息,用户发布的视频等等信息。
所以需要做的就是拉取 web 页面,解析出这个 json, 并且获取感兴趣的字段。
第一步,访问 tk 页面。tk 是限制了访问区域的,比如国内以及想干的大部分 ip 都不能够访问。所以第一步就是需要有一台能够访问 tk 的机器。
第二步,在这台机器上使用 curl 访问 tk 主页
第三步,从 html 页面中解析出 json
第四步,从 JSON 中提取感兴趣的字段
代码实现
一:跳板机。因为 tk 对访问的区域敏感,所以准备了多个区域的多台机器备用。查询时,可以选择发起访问的区域
// region: ip var TkDestIpMap = map[string][string]{}
使用 ssh 工具,连接到指定区域,并执行命令。这里写一个简易的 ssh 工具
// 现在太菜,后面补
连接上之后,就可以执行命令了。但是因为命令比较多,而且使用 shell 编写也比较麻烦,所以使用 golang 编写,再打包成可执行命令,然后只需要触发一下就可以了。
二:获取 HTML 并提取感兴趣的字段
编写一个 golang 命令行工具
package main import ( "bytes" "encoding/json" "errors" "flag" "fmt" "os/exec" "regexp" "strings" ) var tkId = flag.String("t", "", "tk user id") // 使用一下命令,将此 go 程序编译成可执行程序(这里编译后的可执行程序名为 fetch。 使用方式为 ./fetch -t <tk user id>) // build: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./fetch ./parseTkState.go func main() { flag.Parse() sw := bytes.Buffer{} command := exec.Command("curl", "-s", "https://www.tiktok.com/@"+*tkId) command.Stdout = &sw err := command.Run() if err != nil { _ = fmt.Errorf("curl tk failed %v", err) return } // 使用 curl 获取到的 tk 主页 html html := sw.String() script, err := extraJsonInScript(html) if err != nil { _ = fmt.Errorf("extra json failed %v", err) return } // 将结果输出到 stdout,便于调用者获取 fmt.Print(script) } // 从 html 中解析出含有用户信息的 json var scriptsReg = regexp.MustCompile(`<script\s+.*?>(.*?)</script>`) func extraJsonInScript(html string) (string, error) { ret := "" rets := scriptsReg.FindAllStringSubmatch(html, -1) for _, v := range rets { isStateScript := strings.Contains(v[0], `id="SIGI_STATE"`) if len(v) > 1 && isStateScript && json.Valid([]byte(v[1])) { state := TKState{} jsonStr := strings.Trim(v[1], " ") err := json.Unmarshal([]byte(jsonStr), &state) if err != nil { continue } userMap := state.UserModule.Users if userMap == nil || len(userMap) == 0 { return "", errors.New("用户不存在或账号已注销") } // 返回什么内容,由 TKState 决定 stateStr, err := json.Marshal(state) if err != nil { continue } ret = string(stateStr) break } } return ret, nil } // 省略这个结构体的内容。具体内容可以手动把 tk 主页的 json 拉出来看,并且使用工具转换成结构体即可 type TKState struct { }
有了 fetch 这个可执行程序,调用方就很简单了。
sh := NewSSHHelper(sshConf) cmd := fmt.Sprintf("./fetch -t %s", url.PathEscape(tiktokId)) Logger.Infof("show tk user cmd: %s", cmd) sshRet, err := sh.RunCMD(cmd) if err != nil { l.Logger.Errorf("curl failed, %v", err) } return sshRet
但是因为访问 tk 主页是个网络请求行为,所以不得不考虑超时问题。以下是处理超时的逻辑:
sh := NewSSHHelper(sshConf) // 接收 fetch 命令结构的 channel retChan := make(chan string, 1) // 异步执行,让 main 进入 select 流程 go func() { cmd := fmt.Sprintf("./fetch -t %s", url.PathEscape(tiktokId)) Logger.Infof("show tk user cmd: %s", cmd) sshRet, err := sh.RunCMD(cmd) if err != nil { l.Logger.Errorf("curl failed, %v", err) // 出错了写入空值,后面会判断 retChan <- "" } retChan <- sshRet }() // 经典的 golang 超时控制结构 select { case <-time.After(45 * time.Second): return "", status.New(codes.DeadlineExceeded, "超时").Err() case sshRet := <-retChan: if sshRet == "" { return "", status.New(codes.Unknown, "解析失败").Err() } else { return sshRet, nil } }
至此,一个简易的 tk 爬虫便能够跑起来了。
但是需要注意的是,fetch 程序是运行在能够访问 tk 的机器上的,而 fetch 程序的调用者,需要通过 ssh 连接到这台机器上去触发。并且,因为有很多区域,每个区域都有一个主机,所以 fetch 程序的部署也是一个繁琐的事情。
一开始写了一个 shell 脚本,循环所有的机器列表,一个个通过 scp 把编译好的 fetch 程序部署上去。虽然也能用,但是由于机器数量巨大,机器分布在全球,访问时间长短不一,脚本又不能并行,所以就执行得很慢。
后经同事指点,了解了 ansible 这个工具。那是真好用。
所以现在的部署脚本就是:
# build CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./fetch ./parseTkState.go echo 'build done' sleep 2 # deploy ansible tk -i ./ansible.ini -m copy -a "src=./fetch dest=~/" echo 'copy done' ansible tk -i ./ansible.ini -m file -a "path=/root/fetch mode=0755" echo 'deploy done'
ansible.ini 就是配置机器列表,大致长这样:
[tk] hostname1 ansible_password=yyy hostname2 ... [tk:vars] ansible_connection=ssh ansible_user=root ansible_password=xxx
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构