Scout源码解读③:TikTok页面内嵌JSON逆向,__UNIVERSAL_DATA解析实战
Scout源码解读③:TikTok页面内嵌JSON逆向,__UNIVERSAL_DATA解析实战
本文是「Scout源码解读」系列第三篇,带你逐行拆解 Scout 这款免费 lead generation(销售线索生成)工具的 TikTok 抓取模块,搞懂它如何不登录、不逆向后端接口,仅靠解析页面内嵌的 __UNIVERSAL_DATA_FOR_REHYDRATION__ JSON 数据,就能拿到主页 bio、粉丝数并提取邮箱的完整实战思路。
一、为什么不需要登录就能抓 TikTok?
很多同学以为抓 TikTok 必须登录或逆向加密接口,其实不然。
TikTok 的 Web 页面采用 SSR(服务端渲染):服务器会把当前用户的主页数据直接序列化进 HTML 里的一个 <script> 标签中,前端 JS 加载后再用这份数据"水合"(rehydration)出完整页面。这个标签长这样:
<script id="__UNIVERSAL_DATA_FOR_REHYDRATION__" type="application/json">
{ ...一大段用户数据... }
</script>
只要请求主页 HTML,这段 JSON 就躺在源码里。一次 HTTP GET + 一次正则 + 一次 JSON 解析,全部数据到手——这正是 Scout 的核心手法,整套逻辑集中在 app/scrapers/tiktok.py 这一个约 100 行的文件里。
二、整体流程:5 步流水线
先建立全局观,scrape_tiktok_profile() 的执行链路非常清晰:
| 步骤 | 做什么 | 对应位置 |
|---|---|---|
| 1 | 构造类浏览器请求,GET 主页 URL | tiktok.py#L39-L55 |
| 2 | 正则定位 __UNIVERSAL_DATA 标签内容 |
tiktok.py#L57-L64 |
| 3 | json.loads 反序列化为字典 |
tiktok.py#L66-L70 |
| 4 | 逐层导航取出 userInfo |
tiktok.py#L72-L79 |
| 5 | 组装统一的 profile 字典返回 | tiktok.py#L83-L95 |
这种"取数五步法"是 Scout 所有平台爬虫的共同骨架,读懂 TikTok 这一篇,其他平台也就通了。
三、第一层功夫:把请求伪装成真实浏览器
服务端渲染页面的另一个好处是:只要请求"像人",服务器就大方地把数据塞进 HTML。Scout 在两个地方做了伪装:
1. 复用的 HTTP 客户端(tiktok.py#L17-L22):
follow_redirects=True:TikTok 会把部分请求 302 到验证页,自动跟随避免丢数据;timeout=20:给慢网络留足余量;proxy=...:代理由 stealth.py 的get_httpx_proxy()统一注入,支持SCOUT_PROXY环境变量或代理列表文件轮换,不配置则直连。
2. 精心构造的请求头(tiktok.py#L25-L36):
{
'User-Agent': random_user_agent(),
'Accept': 'text/html,application/xhtml+xml,...',
'Accept-Language': 'en-US,en;q=0.9',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-Dest': 'document',
'Sec-Ch-Ua-Platform': '"Windows"',
}
几个细节值得抄作业:
- 🎲 User-Agent 随机化:
random_user_agent()从 stealth.py 内置的 10 个真实 Chrome / Safari / Firefox UA 池里随机挑一个,避免固定指纹被识别; Sec-Fetch-*三件套:这是 Chrome 系浏览器发起顶级导航时必带的头,缺了它们,反爬规则很容易判定你是脚本;Accept-Encoding: gzip, deflate:声明支持压缩,与真实浏览器行为一致。
💡 这套请求模板不是 TikTok 专属——Instagram、Twitch、Pinterest 模块都是同一思路,可以跨站点复用。
四、核心逆向:用正则锁死内嵌 JSON
拿到 HTML 后,关键一步是精确定位数据所在的 script 标签。代码在 tiktok.py#L57-L61:
match = re.search(
r'<script\s+id="__UNIVERSAL_DATA_FOR_REHYDRATION__"[^>]*>(.*?)</script>',
html,
re.DOTALL,
)
拆开看这个正则的三个设计点:
\s+而非写死空格:标签属性顺序、缩进可能变化,用空白符匹配更稳;[^>]*>:吞掉标签上可能出现的其他属性(如type="application/json"),只锚定id;(.*?)惰性匹配 +re.DOTALL:JSON 体可能跨多行,re.DOTALL让.能匹配换行;惰性量符保证只取到最近的</script>,不会吞掉后续标签。
没匹配到怎么办?代码直接记一条 warning 并返回 None,把"页面结构变了"和"用户不存在"统一成失败信号,上层界面会显示 not found,不会抛异常崩溃。
五、第二层功夫:JSON 里的"三层寻宝"
json.loads 之后,数据是一棵很深的树。Scout 用一行取值链直达目标(tiktok.py#L73-L76):
user_detail = data['__DEFAULT_SCOPE__']['webapp.user-detail']
user_info = user_detail['userInfo']
user = user_info.get('user', {})
stats = user_info.get('stats', {})
对应的真实结构大致如下:
{
"__DEFAULT_SCOPE__": {
"webapp.user-detail": {
"userInfo": {
"user": {
"uniqueId": "用户名",
"nickname": "主页显示名",
"signature": "bio 内容,可能含邮箱",
"verified": false
},
"stats": {
"followerCount": 12345,
"followingCount": 89,
"heartCount": 99999,
"videoCount": 42
}
}
}
}
}
注意两个防御性写法:
- 顶层用
[]硬取——__DEFAULT_SCOPE__是框架固定结构,缺失即说明页面完全不对,直接失败; - 叶子层用
.get(..., {})软取——user/stats可能缺键,软取避免KeyError中断; - 外层再包一层
try/except (KeyError, TypeError),兜住一切结构漂移。
六、从 JSON 到线索:profile 字典组装
最后一步是把散落的字段收敛成全项目统一的 profile 结构(tiktok.py#L83-L95):
| profile 字段 | 来源 | 说明 |
|---|---|---|
username |
user.uniqueId |
缺失时回退为入参用户名 |
full_name |
user.nickname |
主页显示名 |
bio |
user.signature |
个人简介 |
email |
对 bio 跑 extract_email() |
📧 核心卖点:从 bio 里正则出邮箱 |
follower_count 等 |
stats.* |
粉丝 / 关注 / 获赞 / 视频数 |
其中 extract_email() 定义在 utils.py#L16-L22,用一段经典邮箱正则从 bio 文本中提取第一个邮箱地址。TikTok 用户常把 xxx@gmail.com 写在 bio 里引流,这一字段直接决定了后续 SMTP 邮箱验证和 CSV 导出的价值。
抓取完成后,scout.py 中的 TikTok 交互流程会:
- 逐个输入用户名(带 2~5 秒随机延迟,模拟人工节奏);
- 每抓到一个就打印一张漂亮的 profile 卡片(粉丝数、bio、高亮邮箱);
- 批量完成后询问是否做线索增强(Enhancement),再导出 CSV。
七、实战上手:5 分钟跑通验证
跟着下面步骤,你可以亲自验证整个解析链路:
- 克隆仓库并安装依赖:
git clone https://gitcode.com/gh_mirrors/scout39/Scout
cd Scout
pip install -r requirements.txt
- 启动交互菜单:
python scout.py
- 选择
[2] TikTok,输入任意公开主页的用户名(不带 @),回车结束输入; - 想观察正则匹配、JSON 解析的日志细节,加
--verbose重新启动,内部logger.info/warning会逐条打印; - 如果当前 IP 触发验证(TikTok 对部分区域会返回 CAPTCHA,见 README.md 的 Limitations 一节),可在 proxies.example.txt 格式下配置代理文件,通过
SCOUT_PROXY_FILE环境变量启用轮换代理。
八、总结:这套"内嵌 JSON 逆向"范式能迁移到哪儿?
回顾本篇的三个关键招式:
- 🔍 找 SSR 水合点:页面源码里带
id="__..."的 JSON script 标签(__NEXT_DATA__、__NUXT__、__UNIVERSAL_DATA_FOR_REHYDRATION__都属于这类); - 🛡️ 类浏览器请求:UA 池 +
Sec-Fetch-*头 + 可选代理,一次 GET 换全量数据; - 🧩 防御式取值:正则惰性匹配 +
.get()软取 + 异常兜底,结构漂移时优雅失败而非崩溃。
掌握这个范式后,面对任何采用服务端渲染的网站,你都可以先用浏览器 DevTools 的 Elements 面板搜 __DATA / __STATE 类关键字,找到那份"白送"的 JSON,再用同样的三步法接入自己的采集流程——这正是 Scout 给所有爬虫新手上的最实用的一课。
