README_tiktok_stats.md 26 KB

TikTok 视频数据抓取 —— 纯接口(API)版

目标:https://www.tiktok.com/t/ZTy8r7T2a/ → @marcgagilang/video/7552202593888603410 抓取项:点赞 / 评论数 / 分享 / 收藏 / 播放 + 全量评论内容

✅ 不使用无障碍权限。全程 HTTP 请求,无 auto/UiSelector/点击/滑动。


一、云机实测结论(2026-09-23,云机 106.119.179.124:23098,Android 14)

1.1 网络环境(关键前提)

现象 结论
adb shell 里 curl https://www.tiktok.com ❌ Recv failure: Connection reset by peer
有 tun0 网卡(172.19.0.1/30) 云机跑 NekoBox(包 moe.nb4a)VPN
127.0.0.1:2080 在监听 NekoBox 的 mixed 代理(HTTP + SOCKS5)
curl -x http://127.0.0.1:2080 https://www.tiktok.com ✅ 200

原因:adb shell 的 curl 走 eth0(被过滤,TLS 握手被 reset),App 走 tun0。 对脚本的影响:Auto.js 走系统 VPN,一般直连即可;若连不上,在 CONFIG 里填 proxyHost: "127.0.0.1" + proxyPort: 2080。

1.2 计数指标(公开 SSR,无需签名、无需登录)

页面 结果
https://m.tiktok.com/v/<id>.html ✅ 首选。SSR 完整,含收藏数
https://www.tiktok.com/embed/v2/<id> ✅ 备用(无收藏数)
https://www.tiktok.com/video/<id> ❌ 常返回 SPA 空壳,无 SSR 数据

实测值:点赞 2,400,000 / 评论 18,900 / 分享 1,200,000 / 收藏 246,728 / 播放 14,800,000

注意:itemStruct 之后第一个 stats 块是话题的 {"videoCount":...}, 脚本会遍历所有 "stats":{...} 并取第一个含 diggCount/playCount 的(实测需跳过 5 个)。

1.3 评论接口(官方全部被拦 → 调用代码已于 2026-09-25 移除)

接口 结果
www.tiktok.com/api/comment/list/ ❌ HTTP 200 但 Content-Length: 0(缺 A-Bogus/X-Bogus)
同上 + msToken 二次握手 ❌ 仍为空(服务端下发了 x-ms-token 也没用)
api16/api19/.../comment/list/ (app_name=musically) ❌ 403(Argus 风控)
同上 (app_name=tiktok_web) ❌ 2146 Please upgrade your TikTok app

2146 与 cookie / UA / version_code(试过 180800 / 270000 / 618002 / 2024505030 等) / 设备参数全部无关,是死路。登录态 Cookie 对评论接口没有任何帮助。

可用方案:www.tikwm.com 镜像接口(已实测)

顶级评论  https://www.tikwm.com/api/comment/list?url=<视频URL>&count=50&cursor=N
子回复    https://www.tikwm.com/api/comment/reply?video_id=<ID>&comment_id=<CID>&count=50&cursor=N
→ {code:0, data:{comments:[...], total:18752, cursor:50, hasMore:true}}

1.4 tikwm 的 6 个坑(2026-09-24/25 复测,★ 决定成败)

# 坑 实测证据 后果
1 hasMore 会偶发「假 false」 cursor=400 返回 hasMore:false(仅 41 条),但 cursor=450/500 依然 true 且有数据 拿它当终止条件 → 第 9 页就收工,只拿到 426 条
2 只按「并发」限流,不按「时间」限流 串行 0.2s 间隔连打 20 次 → 20/20 全成功;并发 3 → 2/3;并发 5 → 3/5 必须串行;间隔只是「礼貌」,不是硬阈值(见 1.5)
3 total 字段不可信 cursor≥15000 时 total 退化成 cursor 本身(15000/18000/18700…) 不能拿 total 算进度或判结束
4 count 上限 50 传 100 / 200 也只返回 49 条 必须老老实实翻 200+ 页
5 翻页深度上限 ≈ cursor 10369 超过后真的没有数据(不是假 false) 拿不到声明的 1.8 万,实得 ≈ 1 万(52%)
6 限速维度必须用「请求数/小时」 评论量差异达 20 倍:500 条=10 请求,9,931 条=199 请求 按「视频个数」设固定间隔 → 速率失控(见 1.5)

真实可抓范围(实测):

项 值
顶级评论 cursor 从 0 翻到 ≈10369 后真的没数据(约 212 页)
单页 46~50 条(count=50)
子回复 需单独接口;reply_total 声明值可信(声明 367 → 实测抓到 366)
声明总量 total=18752(官方显示 18900),但回复总量远超此数

⚠️ reply_total 分布极不均匀:仅 cursor=0 这一页 49 条顶级评论, reply_total 合计就 3447(单条最高 1446)。但全量统计后实际总量只有 5952 条 (1060 条评论带回复),缺口不大。

回复接口 comment/reply 比列表接口更不稳(实测):

现象 证据
空页随机出现 cursor=50 空,cursor=100 却正常
返回条数会超过声明 声明 81 → 实得 278;声明 20 → 实得 23
也可能只拿到一部分 声明 21 → 实得 1(cursor 50/100 都空,tikwm 侧就没有)

所以回复的终止规则是「已达标 + 末页未满」而不是拿 declared 硬卡:

① all.length >= declaredTotal 且 本页 rawN < count  → 收工(末页)
② 连续 replyEmptyStreakLimit(=5) 页空              → 收工
③ cursor > declaredTotal + 2*count                 → 收工(hardCap)

(踩过:用 emptyStreak>=2 会让「声明 61 实得 4」这种被误判成结束。)


1.5 限流模型与推荐抓取节奏(2026-09-24 标定)

先给结论:tikwm 只卡并发,不卡频率。

用云机 curl 在同一出口 IP 上做了两组标定:

组 1 —— 串行下的频率:没有阈值

请求间隔 次数 成功 code=-1
0.2s(网络极限,≈24 req/min) 20 20 0
1.0s 20 20 0
3.0s 15 15 0

组 2 —— 并发:阈值 ≈ 2

并发数 成功 code=-1
2 2/2 0
3 2/3 1
5 3/5 2

且触发后无需冷却——限流一结束,下一次请求立即恢复(恢复校验全通过)。

所以「多久抓一个视频」的真实答案

① 硬约束(有实测支撑):必须串行。 这是唯一会被服务端拒绝的行为。 本脚本天然串行,所以你不必把"间隔"当成某个必须达到的阈值。

② 建议节奏(主动收敛,非实测阈值)。 之所以要主动收敛,是因为:

  • tikwm 是免费第三方镜像,靠广告养活,容量有限,持续满速打是透支共享资源;
  • 请求出口是 NekoBox 的代理节点 IP,若该节点被 Cloudflare/tikwm 标记, 你整个 VPN 都会受影响,代价远大于抓慢一点。

2026-09-25 起不再需要人工分档:旧的「视频间隔 90s」被证明是有缺陷的—— 评论量差异达 20 倍,固定间隔会让轻视频空等、重视频歇不够。现改为自动两级限速。

现在的做法:按请求数驱动的两级限速

级别 机制 作用
① 令牌桶 targetReqPerHour: 1200 每次请求前预占一个「令牌」,跨视频累计、不因换视频重置 小时级硬上界(1200 → 每请求至少 3s)
② 视频间动态冷却 videoCooldownMinMs(15s) + 上一视频请求数 × videoCooldownPerReqMs(250ms) 重视频自动多歇,轻视频不空等
③ 批次长休(沿用) 每 5 个视频休 10 min(±20%) 打断长时间匀速

端到端实测(_probe/test_pacing.js 模拟「轻 10 请求 + 重 199 请求 + 轻 10 请求」):

219 请求 / 726s  →  1086 请求/小时        (目标上界 1200)

成本对照(已含限速后的单视频耗时):

评论量 请求数 动态冷却 令牌桶耗时 单视频合计
500 11 18s 33s ≈ 51s
2,000 41 25s 123s ≈ 2.5 min
5,000 101 40s 303s ≈ 5.7 min
9,931(本视频) 200 65s 600s ≈ 11 min

⚠️ 加了主动限速后,本视频从原来的 ≈6 min 变成 ≈11 min。 这是「把速率压进安全区」的代价,不是性能 bug。

仍然别按"抓几个"规划,按"这个视频要发多少次请求"规划。 建议单 IP 控制在 ≤ 1000~1500 请求/小时,默认配置(1200)已落在区间内。

脚本内置的节流保护

机制 行为
串行 天然单线程,不存在并发风险
令牌桶 targetReqPerHour 全局速率上界,跨视频累计(主要限速手段)
页间最小间隔 requestDelay: 400 含 ±40% 抖动;通常小于令牌桶间隔,只在目标设得很高时主导
自适应退避 命中 code=-1 → 页间隔与令牌桶同步 ×2(分别 8s / 30s 封顶)
快速恢复 连续 20 页干净通过 → 两者同步一步回到基准(不做缓慢衰减)
视频间动态冷却 15s + 请求数 × 250ms,±50% 抖动
批次长休 每 5 个视频长休 10 min,±20% 抖动
断点续跑 skipExisting 已有 _stats.json 的视频自动跳过,跳过的不触发冷却
失败也冷却 抓取失败的视频同样进入冷却(失败多因风控,连续快跑只会加剧)
结果自检 结束打印「共发请求 N 次,命中限流 M 次」+「实际速率 ≈ X 请求/小时」

以上逻辑已用「加载生产文件本体 + 注入假 sleep 与可控时钟」的方式单测覆盖(40 项断言全绿)。

怎么看收尾那两行日志:

[pace] 期间共发请求 1987 次,命中限流 3 次
[pace] 实际速率 ≈ 1102 请求/小时(目标上界 1200)
  • 命中限流 M:偶发 1~5 次属正常(假 hasMore / 空页重试引起);持续 > 0 或出现 CF 403 页面(返回 HTML 而非 JSON)→ 换节点,而不是继续降速。
  • 实际速率:长跑(几十个视频以上)会稳定落在目标以内;视频数很少时会偏高, 因为固定冷却占比低 —— 长跑自然收敛。

顺带一个好消息:抓评论不牵连 TikTok 账号

评论全程走 tikwm,不携带任何登录态(sessionid 对评论接口无效,见 1.3)。 所以批量抓取的主要风险是「IP 被镜像站/代理节点标记」, 而不是「TikTok 账号被封」——脚本也不会拿 Cookie 去请求评论接口。


1.6 登录态是否必需?换 IP 的边界在哪(2026-09-25 定论)

结论:不登录 TikTok 账号,本方案可完整跑通。 上一轮的成功抓取 (9,931 顶级评论 + 3,316 回复)本身就是无 Cookie 跑出来的,已逐行核对脚本:

链路 函数 请求头 用 Cookie?
计数 fetchStats() User-Agent + Accept 否
评论 getCommentsViaMirror() User-Agent + Accept 否
官方评论 getCommentsViaOfficial() 曾含 Cookie 接口已被风控拦死,函数已于 2026-09-25 删除

→ 结论落地:cookie / cookieFile / tryOfficial 三项配置已从脚本移除, 主脚本现在完全没有携带登录态的代码路径。

登录态唯一还能帮上忙的三个场景(都不是"防封"):

  1. 地区 / 年龄限制内容 —— 未登录时 itemStruct 可能整个缺失;
  2. 私有、好友可见内容;
  3. 想拿 TikTok 自己那份"完整 total"时(SSR 计数可能有延迟/取整)。

其中 1、3 用目标地区的出口 IP 同样能解决 → 短效 IP 确实可以替代登录态。

换 IP 治的是哪一层(关键)

两条风控对象不同,混在一起看就会误判:

层 谁在判定 换你的 IP 是否有效
m.tiktok → TikTok Cloudflare 你的出口 IP 有效
tikwm 入口限流(并发 ≈ 2) 你的出口 IP 有效(换 IP = 变成新客户端,额度重置)
tikwm 服务端 → TikTok tikwm 自己的 IP 无效

三个反直觉点:

  1. 换频率 ≠ 提速。 标定结论是"只卡并发、不卡频率"。只要串行跑, 30 分钟一换和 4 小时一换差别不大。换 IP 买的是「被标后的恢复速度」, 而不是「跑得更快」。
  2. 换 IP 救不了 tikwm 上游被限。 若 tikwm 自身取数被风控,表现为 所有视频同时失败(而非偶发 code=-1)——此时换节点无用,应等其恢复或换镜像。
  3. 代理质量 > 更换频率。 30 分钟一换的机房段 IP,比一个稳定的住宅 IP 更容易被 CF 直接判 bot;换太勤反而像扫描器。优先住宅 / 移动 IP。

换 IP 的触发信号(按需换,而非纯定时)

信号 判定
[pace] 命中限流 M 持续 > 0 偶发 1~2 次属正常抖动(假 hasMore / 空页重试),持续才是信号
连续出现 code=-1 换节点
返回 HTML 而非 JSON(CF 403 挑战页) 换节点
所有请求成功率骤降 换节点

命中任一条 → 立刻换节点;无异常时按期轮换(20~30 min)作兜底即可。

换 IP 也治不了的天花板: tikwm 顶级评论 cursor 上限 ≈ 10369(见 1.5 与第六节), 这是镜像的数据完整性上限,与账号、IP 均无关。

合规提示:用 IP 轮换替代登录态,不等于免除平台 ToS 约束。控制量级、 避免对单一镜像站高频施压。


二、依赖与运行

  1. Auto.js Pro 8;只需悬浮窗权限(看日志),无障碍、root 都不需要。
  2. 手机网络必须能访问 TikTok(走 VPN)。
  3. 运行:

    adb push tiktok_stats.js /sdcard/Download/tiktok_stats.js
    # Auto.js Pro 导入 → 运行
    

输出目录 /sdcard/Download/tiktok_data/:

  • tiktok_<ID>_stats.json — 5 项计数 + 元信息
  • tiktok_<ID>_comments.json — 评论(结构化)
  • tiktok_<ID>_comments.txt — 评论(可读文本)

三、关键修改点(CONFIG)

配置项 说明
shareUrl 目标短链
awemeId 已知视频 ID 直填(当前 7552202593888603410),留空则解析短链
maxComments 0=顶级评论全量;>0=只抓前 N 条(调试建议先设 200)
commentPageSize 50(tikwm 上限,填大也没用)
useMirror ★ 评论来源:tikwm 镜像(false 则只抓计数、不抓评论)
proxyHost / proxyPort 一般留空;Auto.js 连不上时填 127.0.0.1 / 2080
targetReqPerHour ★ 小时级请求上界(默认 1200 → 每请求至少 3s)。0=不限速
requestDelay / maxRetries 页间最小间隔(400ms)与重试次数;必须串行
jitterRatio 页间间隔抖动比例(默认 0.4)
throttleBackoffFactor 命中限流时的退避倍数(2 倍,页间隔与令牌桶同步放大)
throttleMaxDelay / throttleMaxInterval 页间隔上限 8s / 令牌桶上限 30s
throttleRecoverPages 连续 N 页干净通过后同步回到基准(默认 20)
emptyStreakLimit 连续 N 页无数据才判定结束(默认 3,配合假 hasMore 容错)
maxOffset 0=翻到无数据为止;设值则到该 cursor 停(实测上限 ≈10400)
batchUrls 批量模式:填多个短链/ID 则按顺序批量抓(留空=只抓 shareUrl)
videoCooldownMinMs ★ 视频间冷却底(默认 15s)
videoCooldownPerReqMs ★ 每请求追加冷却(默认 250ms)→ 重视频自动多歇
videoCooldownJitter 视频间冷却抖动(±50%)
batchCooldownEvery / batchLongCooldownMs 每 5 个视频长休 10 分钟
skipExisting 批量模式下已有结果的视频跳过(断点续跑,仅批量生效)
fetchReplies 是否抓子回复(默认 false,见 1.4 的量级说明)
replyTopN 抓回复最多的前 N 条评论的回复(默认 30)
replyMinTotal 只抓 reply_total >= 该值的评论(默认 2)
outputDir 输出目录(自动尝试多个候选并校验可写)

关于登录态:本脚本不提供 cookie / cookieFile 配置项,也刻意不带登录态。 计数与评论两条链路都不读 Cookie(见 1.6),且「登录态 + 轮换 IP」会造成同账号 跨地区跳变,是比匿名请求更强的风控特征。原先的 cookie / cookieFile / tryOfficial 三项已于 2026-09-25 移除。

批量抓取示例

// 只改这一处即可批量:填链接或裸 ID 都行
batchUrls: [
    "https://www.tiktok.com/t/ZTy8r7T2a/",
    "7552202593888603410",
    "https://www.tiktok.com/@someone/video/7512345678901234567"
],
targetReqPerHour: 1200,       // ★ 小时级上界(每请求至少 3s)—— 主要限速手段
videoCooldownMinMs: 15000,    // 视频间冷却底 15s
videoCooldownPerReqMs: 250,   // + 每请求 250ms(重视频自动多歇)
batchCooldownEvery: 5,        // 每 5 个
batchLongCooldownMs: 600000,  // 长休 10 分钟
skipExisting: true            // 中断后直接重跑,只补没抓的

大批量长跑建议

  • 想更保守:只调 targetReqPerHour: 600(每请求 6s),其余不用动。
  • 想更快:先确认出口节点是住宅 / 移动 IP 再提高 —— 机房段 IP 提上去极易撞 CF 挑战。参考上限 ≤ 1500 请求/小时。
  • 无人值守:直接跑即可。失败会自动重试并进入冷却;中断后原样重跑即可续抓。
  • 换节点时机:看收尾的 [pace] 行。命中限流 偶发属正常;持续 > 0 或出现 CF 403 页面就换节点,别靠降速硬扛。

四、已修复的问题

  1. 保存失败 ENOENT:files.ensureDir(dir) 只创建传入路径的父目录,不创建 dir 自身。 已改为 new java.io.File(dir).mkdirs() 递归创建 + createNewFile() 可写校验, 失败自动降级到 files.getSdcardPath() / files.cwd() 等备选目录。
  2. Rhino invalid return(0.004s 闪退):Rhino 禁止顶层 return。 所有逻辑已收进 main()。
  3. 收藏数时有时无(246727 → 0):改为从 m.tiktok.com 的 SSR stats 块解析,稳定拿到。
  4. 评论一直为 0:官方接口全部被签名风控拦截,改用实测可用的镜像接口。
  5. 提取器在云机上误命中 /data/system/graphicsstats/...:find /data -maxdepth 4 兜底时把 graphicsstats 的日期目录当成了数据目录。已改为先 pm dump 拿权威 dataDir,再用 /proc/1/root 前缀探测可读性,不再靠 find 猜路径。
  6. 提取器 grep 命中 0 项:见 5.1「坑 2」,Cookie 存储是 Java 序列化 hex / SQLite, 不是明文,已改为结构化解析。
  7. 评论只抓到 426 条(而不是 1 万+):tikwm 的 hasMore 会偶发假 false, 旧逻辑在 cursor=400 那页直接 break,累计正好 426。 已改为:hasMore 只作提示不作终止;空页重试 3 次;连续 3 页无数据才结束。 验证方式:录制 12 页真实响应(含出问题的那页)回放生产代码 → 426 → 559 条,且正确打印「⚠ 第9页 hasMore=false,但实际返回 41 条 → 判定为假 false,继续翻页」。
  8. reply_total 误以为是总量:tikwm 的 total 是「顶级评论 + 回复」的混合口径, 且回复量极大;已把子回复拆成独立可选阶段(fetchReplies),默认只抓 Top N。
  9. 没有批量能力:新增 batchUrls 批量模式 + 视频间冷却 + 批次长休 + skipExisting 断点续跑(故意做在同一份脚本里,避免复制出第二份代码后两边漂移)。
  10. 退避后恢复过慢(单测发现):原设计从 8000ms 回到 400ms 需 100 次成功, 相当于一次瞬时限流就把整段视频拖慢 20 倍。已改为「连续 20 页干净通过 → 一步回到基准」。
  11. 被跳过的视频仍空等冷却:批量续跑时会让 90% 的时间浪费在空等上。 已改为仅当本次视频真的发过请求才进入冷却。
  12. 登录态零贡献却仍被保留、并诱导使用(2026-09-25):逐行核对发现计数与评论 两条链路都不读 Cookie,唯一读 Cookie 的官方评论链路早已被风控拦死。 已删除 buildCookie() / getCommentsViaOfficial() 两个死函数, 以及 cookie / cookieFile / tryOfficial 三项配置 —— 顺带每个视频少打 1 次注定失败的请求。
  13. 「固定 90s 视频间冷却」不适配大批量:评论量差异达 20 倍(10 请求 vs 199 请求), 固定间隔让轻视频空等 5 倍时间、重视频又歇不够。已改为 令牌桶(跨视频累计)+ 动态冷却(底 15s + 请求数 × 250ms) 两级限速。
  14. 失败视频不进入冷却:原逻辑只在成功时冷却;但失败多因风控/网络, 且失败前通常已发过计数请求。已改为失败同样进入冷却,避免连续快跑放大风控。
  15. 退避只作用于页间隔 → 在令牌桶面前形同虚设(设计期规避 + 单测防回退): 引入令牌桶后,若 code=-1 退避只放大页间隔(400ms→8000ms), 仍会被令牌桶的 3s/请求淹没,退避等于没生效。 已改为页间隔与令牌桶同步放大、同步回落,并新增断言「令牌桶确实被退避放大」。

五、登录态提取(root + shell)

⚠️ 2026-09-25 提示:日常抓取不需要这一节。 已核实登录态对计数与评论零贡献(见 1.6),主脚本也不读 Cookie。 这里保留完整提取链路,仅用于:① 确实遇到地区/年龄限制内容需要验证; ② 调试其他依赖登录态的接口。不要把提取到的 Cookie 塞进批量抓取流程。

5.1 手机侧:tiktok_extract_cookie.js(Auto.js 版)

adb push tiktok_extract_cookie.js /sdcard/Download/
# Auto.js Pro 导入运行

云机上踩的两个坑(已在脚本内解决,勿回退)

坑 1 —— mount namespace 隔离

云机会把 /data/data 用 tmpfs 重新挂载成空目录,只 bind 进 Auto.js 自己和 GMS:

tmpfs on /data/data type tmpfs (mode=751)          ← 空壳
/dev/loop2 on /data/data/org.autojs.autojspro
/dev/loop2 on /data/data/com.google.android.gms

所以 test -d /data/data/com.zhiliaoapp.musically 返回 NO、find /data 只能扫到 /data/system/graphicsstats/... —— 这是挂载视图问题,不是权限问题,加 root 也没用。

解决:用 /proc/1/root 前缀穿透到 init 的挂载视图。/proc/<pid>/root 是 magic symlink,会跟随目标进程的 mount ns,不需要 CAP_SYS_ADMIN(nsenter 需要,故仅作兜底):

/proc/1/root/data/user/0/com.zhiliaoapp.musically/shared_prefs/ttnetCookieStore.xml

脚本按 [/proc/1/root 前缀] → [nsenter] → [直接路径] 依次探测并打印结果。

另外:files.* / java.io.* 在 Auto.js 进程 uid 下运行(非 root),读不到 /proc/1/root 下的文件 → 必须先用 root shell cp 到 /sdcard,再用 files.read()。

坑 2 —— Cookie 存储不是明文

存储 形态 grep 结果
shared_prefs/ttnetCookieStore.xml 值是 Java 序列化 hex(ACED0005…) ❌ 永远 0 命中
app_webview/Default/Cookies SQLite,name 与 value 分列 ❌ 匹配不到 name=value

所以必须结构化解析:

  • XML:<string name="http://tiktok.com/|d_ticket">ACED0005…</string> 字节流结构 [TC_STRING 名称][TC_STRING 值][TC_STRING 域名][TC_STRING 路径], 取第 2 个 TC_STRING(0x74) 即值。实测 170 条目 → 24 个 Cookie 名。
  • SQLite:用 Android 内置 android.database.sqlite.SQLiteDatabase 直接查。

5.2 PC 侧:tiktok_extract_cookie_pc.js(备用通道)

node --experimental-sqlite tiktok_extract_cookie_pc.js

同步用 adb pull 取两个存储,解析逻辑同上,并做双源交叉校验。

备注:这台云机的 su 不支持 -c,必须用 su 0 sh -c '...'。 Git Bash 下需 export MSYS_NO_PATHCONV=1,否则 /sdcard/... 会被转成 Windows 路径。

5.3 实际能提取到的东西

24 个 Cookie 名(2018 字符):sessionid sessionid_ss sid_tt uid_tt uid_tt_ss sid_guard msToken odin_tt passport_csrf_token passport_csrf_token_default multi_sids tt-target-idc store-idc cmpl_token d_ticket install_id ttreq store-country-code store-country-sign tt_session_tlb_tag user_oec_info BUYER_TOKEN 等。

ttwid 不在 App 的存储里(App 不持久化它),只能向 www.tiktok.com 现捞。 但当前主脚本不需要它 —— 不携带任何 Cookie 也能正常抓取(见 1.6)。


六、取数链路

计数:m.tiktok.com/v/<id>.html → __UNIVERSAL_DATA_FOR_REHYDRATION__ → 定位 itemStruct → 遍历 "stats":{...} 取含 diggCount 的块。 失败降级 embed/v2/<id> → 全局正则。 全程不携带 Cookie(未登录 SSR 同样完整)。

评论:

  1. www.tikwm.com/api/comment/list(唯一来源,实测可用)
    • 从 cursor=0 串行翻页,单页 50,hasMore 假 false 忽略,空页重试, 连续 3 页无数据结束 → 实得顶级评论 ≈10369 条
  2. www.tikwm.com/api/comment/reply(子回复,可选)
    • 参数 video_id + comment_id(不需要 url)
  3. www.tiktok.com/api/comment/list(官方兜底 —— 已被 A-Bogus 签名风控拦死, 调用代码于 2026-09-25 移除,仅保留此说明)

输出:

  • tiktok_<ID>_stats.json — 5 项计数 + topLevelComments / repliesFetched
  • tiktok_<ID>_comments.json — 结构化(回复挂在父评论的 replies 数组)
  • tiktok_<ID>_comments.txt — 可读文本(回复以 └─ 缩进)