从TikTok抓取的45亿帖子

Hacker News Top 新闻

摘要

本文提供了抓取TikTok私人移动API的技术指南,产生了一个包含45亿视频的庞大数据集,该数据集已上传至Hugging Face供公众访问。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/09/03 11:56

# 抓取TikTok移动端API 来源:https://tiktok-api.seeksocial.io/ 技术指南 · 24个端点 · 实测TikTok安卓应用通过一个私有HTTP+JSON API通信,该API比网页端点更快,且返回数据量显著更多。本文提供接入该API的技术指南:包括设备注册机制、请求签名方式、区域主机分区规则以及TLS握手指纹识别。基于此构建的系统在三周内收集了32.3亿创作者资料、59.4亿视频和28亿条评论。获取完整代码(https://tiktok-api.seeksocial.io/#code) **免费数据集。**我已将45亿条视频上传至Hugging Face:包含字幕、观看、点赞、评论和收藏数、音乐、国家及发布时间。huggingface.co/datasets/kuben-developer/tiktok-videos-4b (https://huggingface.co/datasets/kuben-developer/tiktok-videos-4b) **可获取数据范围。**创作者资料、创作者发布的所有视频、粉丝和关注列表、TikTok自有相似创作者图谱。包含完整统计区块的视频详情。评论及评论回复(含评论者账号信息)。音乐、使用该音乐的视频及热门音乐排行榜。话题标签及其相关视频(按最新或最热门排序)。跨视频、创作者和音乐的关键词搜索。热门推荐栏与相机特效。共计24个端点(https://tiktok-api.seeksocial.io/#api),每个端点均标注实测成功率。 ## 这是什么 几乎所有能找到的TikTok爬虫都在驱动无头浏览器或访问公开网页端点。这两者都属于错误的层级:速度慢、不稳定且缺失大部分有效字段。安卓应用不采用这两种方式,它通过私有HTTP+JSON API通信——即你刷视频时`com.zhiliaoapp.musically`调用的那个API。该API速度快、稳定性高且返回数据更丰富。 接入难度在于四个完全无关的要素必须同时正确:TikTok颁发的设备凭证、有效的请求签名、正确的区域主机、以及看起来像手机的TLS握手。任何一项错误都会收到完全相同的响应:**一个状态码为`HTTP 200`但响应体为空的请求**。没有错误信息,没有状态码。你的HTTP客户端会报告成功,日志保持绿色,数据库里只会填满空数据。没有任何信号能告诉你具体是四个要素中的哪一个出了问题。 本文将逐一解析这四个要素,并记录由此生成的24个端点。内容将定义基础构件,展示真实处理流程,并提供实测数据而非推测。 这四个要素均无反馈循环。错误的轮转常量、错误的字节序、错误的主机以及握手时错误的加密套件,都会产生格式正确的请求和相同的空响应,因此无法通过错误进行二分排查,也没有部分成功的情况。 ## 范围说明 以下所有内容均为匿名设备流量。系统中没有任何登录、账户或会话cookie。这也意味着任何真正需要账户权限的内容(你的私信、私密视频、谁点赞了什么)均无法获取且无法突破。任何调优手段都无法实现。 ## 请求解析 首先,展示一个真实请求的实际样貌。这是一个真实调用(部分识别值已缩短): ``` GET /aweme/v1/aweme/post/?# ── 请求内容 ────────────────────────────── source=0 &user_id=6744630345964389381 &count=20 &max_cursor=1751028792000 &sort_type=0 &# ── 请求方:38个参数,顺序固定 ───────────── ts=1788361402&ac=mobile&ac2=lte &aid=473824 # 应用ID:TikTok Lite &iid=7680617333853718293 # 安装ID ← 来自注册 &device_id=7680616891110524437 # 设备ID ← 来自注册 &cdid=4a1d... # 客户端生成UUID &openudid=8f2c... # 客户端生成16位十六进制 &device_brand=Samsung&device_type=SM-A136U&os_version=12&os_api=30 &resolution=1080*2280&dpi=440&host_abi=arm64-v8a ®ion=SG&carrier_region=SG&sys_region=SG&mcc_mnc=52506 &language=ja&app_language=ja&locale=ja-SG&timezone_name=Asia%2FSingapore &version_name=32.8.2&version_code=320820&manifest_version_code=320820 &_rticket=1788361402193&channel=googleplay&app_type=normal Headers: user-agent: com.ss.android.ugc.tiktok.lite/320802 (Linux; U; Android 12; ...) x-tt-trace-id: 00-6a9f...-6a9f...-01 x-ss-req-ticket: 1788361402193 x-khronos: 1788361402 # 时间戳 x-ladon: XKp9... # Speck-128/256 x-argus: cQqbRZm8k1x... # 核心难点 x-gorgon: 0404b0d30000... # 传统摘要 ``` 这是一个创作者时间线请求。分割线以下均为设备标识信息。 有三点需要注意(后续会逐个分析): - **三分之二的URL是设备标识。** 三十八个通用参数描述手机、运营商、地区和应用版本。它们不是装饰,签名覆盖这些参数。 - **`device_id`和`iid`由TikTok颁发**,而非你自行选择。`cdid`和`openudid`是注册时生成并提交的。混淆这种区别是第一道障碍。 - **参数顺序固定。** 签名将查询字符串作为字面量进行哈希,因此像Go语言中按字母顺序排序键的`url.Values.Encode()`方法会静默生成无效签名。在Go中必须手动构建查询字符串。 相关术语定义(全文重复出现): | 字段 | 说明 | 来源 | |------|------|------| | aid | 应用ID。`1233`是主应用(musically),`473824`是Lite版,`1340`是musically_go。不同`aid`意味着不同的签名密钥和端点集。 | 常量 | | device_id | 持久设备标识,19位数字。 | TikTok,注册时颁发 | | iid | 安装ID,与`device_id`配对。 | TikTok,注册时颁发 | | cdid | 客户端设备ID,自动生成的UUID。 | 用户生成 | | openudid | 16位十六进制字符,自动生成。 | 用户生成 | | license_id | 用于X-Ladon密钥调度。 | 应用内常量 | | version_code | 应用版本号,决定哪些端点可用。 | 用户选择 | ## 首次空200响应 正确实现的签名器生成的输出应能验证捕获的流量,参数逐字节匹配。但响应仍然是: ``` $ curl -sD- -o /tmp/body "https://api16-normal-c-alisg.tiktokv.com/aweme/v1/user/profile/other/?..." HTTP/1.1 200 OK content-type: application/json content-length: 0 x-tt-logid: 2026090117... server: TLB $ wc -c /tmp/body 0 /tmp/body ``` 200状态码。0字节。没有`status_code`,因为没有响应体。 这是TikTok的软拦截,也是理解该API最重要的点。它不是403,不是429,不是验证页面。这是一个成功的HTTP响应,但内容为空。 这意味着这段所有人都会先写的代码实际上是静默失效的: ```python res = requests.get(url, headers=signed) if res.ok: # True。永远为True。 store(res.json()) # 存入{},无异常 # 六小时后:数据库40万行,全部为空, # 错误日志无记录,仪表盘显示绿色 ``` 调试成本高昂,因为**四种不同的故障都会导致此现象**: 1. 设备从未激活(§ 激活 (https://tiktok-api.seeksocial.io/#activate)) 2. 签名错误(§ X-Argus (https://tiktok-api.seeksocial.io/#argus)) 3. 与错误的区域主机通信(§ 区域 (https://tiktok-api.seeksocial.io/#regions)) 4. TLS握手看起来像服务器而非手机(§ JA3 (https://tiktok-api.seeksocial.io/#ja3)) 响应中没有任何信息能指明是哪一个。你无法通过错误信息进行二分排查,因为根本没有错误。唯一的解决方法是逐一修复这四个要素,并分别进行测量。 ## 设备ID的来源 你无法伪造`device_id`。TikTok通过其日志主机的`/service/2/device_provisioning/`端点颁发此ID,以换取看似合理的手机请求。请求体是JSON文档(应用头、设备头、自定义块),使用TTEncrypt加密(TikTok自有的请求体加密算法,采用固定密钥调度的简单字节级变换),并以`application/octet-stream;tt-data=a`格式发送。 该请求携带完整签名集,因此**你需要有效的签名才能获取设备ID,而签名又需要设备ID**。你通过客户端生成的字段和颁发字段的占位零值进行引导。 请求体结构(关键部分): ```json { "magic_tag": "ss_app_log", "header": { // 应用标识:必须与查询字符串中的aid一致 "aid": 473824, "package": "com.ss.android.ugc.tiktok.lite", "app_version": "32.8.2", "version_code": 320820, "sdk_version": "...", "git_hash": "...", "sig_hash": "...", // 硬件:此处每个字段都必须内部一致 "device_model": "SM-A136U", "device_brand": "Samsung", "device_manufacturer": "samsung", "cpu_abi": "arm64-v8a", "os_version": "12", "os_api": 30, "resolution": "2280*1080", "density_dpi": 440, "rom": "...", "rom_version": "...", // 你生成并即将交换的标识信息 "cdid": "", "openudid": "<16 hex>", "clientudid": "", "google_aid": "", // 区域:运营商必须在该国家真实存在 "region": "SG", "sim_region": "sg", "carrier": "Singtel", "mcc_mnc": "52506", "tz_name": "Asia/Singapore", "tz_offset": 25200, "custom": { "screen_width_dp": 408, "screen_height_dp": 883, "web_ua": "Dalvik/2.1.0 (Linux; U; Android 12; SM-A136U Build/...)", "apk_last_update_time": 1788361409271 }, "apk_first_install_time": 1788360902118 }, "_gen_time": 1788361402240 } ``` 每个字段都会与其他字段进行校验。三星`SM-A136U`有特定的屏幕分辨率、DPI、ABI,并搭载特定版本的Android系统。它在某些国家通过某些运营商销售,不在其他国家。在从未销售过该机型的网络上使用旗舰手机,就不是真实设备,注册会被拒绝。 与其通过程序生成这些信息,不如从约250个真实Android设备配置文件目录中选取,并结合运营商表(MCC/MNC对,约2000行,源自公开号码计划数据)进行交叉验证。选择一个手机型号,选择目标国家实际存在的运营商,填入协调一致的值。 成功注册会返回你需要的两个ID: ```json { "device_id_str": "7680616891110524437", "install_id_str": "7680617333853718293", "new_user": 1 } ``` 大多数实现到此为止。 ## 激活调用 注册成功后,大多数端点都能正常响应。视频列表、搜索、话题标签、音乐都没问题。但`/aweme/v1/user/profile/other/`(完整资料记录)每次都会返回空200,无论我创建多少设备,永远如此。 明显的怀疑对象是签名,但这是错误的线索。关键在于:一个月前由不同代码生成的一批旧设备,在使用相同签名器和相同参数的情况下,在同一个端点上工作正常。唯一的区别在于设备创建方式,最终归结为一个额外的HTTP调用: ``` GET /service/2/app_alert_check/? &cronet_version=...&ttnet_version=... &tt_info=... → {"message":"success"} ``` 仅此而已。它返回的内容都不是必需的。看起来像遥测数据,功能上*就是*遥测数据。这是真实应用在启动时、请求任何数据之前进行的调用。这就是该调用的目的。 注册后立即开始查询API的设备,从字节跳动的角度来看,是一个**从未启动**的安装。仅注册不会让你成为一个运行中的应用,启动调用才会。 | 设备生成流程 | 资料端点成功率 | 说明 | |--------------|----------------|------| | 仅注册 | 0 / 360 | 签名正确,响应体始终为空,永远如此。 | | 注册 + 启动调用 | 100 / 100 | 相同代码、相同签名,仅多一个请求。0/360变为100/100,仅通过一个响应被丢弃的调用实现。 | 该内容未在任何文档中记载。在签名转储中不可见。不会发出明显的失败信号。并且由于症状是空200,它与损坏的签名器无法区分。 `tt_info`数据块是请求的有趣部分:约六十个`key=value`对(GAID、时区、安装ID、设备ID、运营商、屏幕、ABI、区域设置、请求UUID),经TTEncrypt加密并base64url编码。这是应用在启动时报告其完整环境。 我的猜测(仅是猜测)是设备在此处被标记为真实安装而非裸注册;我尚未尝试证明,因为实验结果是明确的。 ## 使用前验证设备 激活修复了资料端点问题,但引入了二阶问题:激活本身有时会静默失败,且激活失败的设备在表现上与成功设备完全相同,直到你实际使用它。 因此生成流程不结束于激活,而是结束于对已知创作者进行真实读取。如果返回真实内容,则设备加入资源池。否则将被丢弃。不重试,不隔离,*直接丢弃*。 ```go func GenerateDevice(client *http.Client, country string) (map[string]any, error) { tmpl, err := NewAndroidTemplate() // 手机型号 × 运营商 // ... if err := registerDevice(client, tmpl); err != nil { return nil, fmt.Errorf("register: %w", err) } // 无此步骤,TikTok不会向新设备提供资料详情 if err := appAlertCheck(client, tmpl); err != nil { return nil, fmt.Errorf("activate: %w", err) } // 存活率过滤:只有经过验证具备能力的设备才进入资源池 if !profileCapable(client, tmpl) { return nil, errors.New("profile probe failed: device not capable") } return tmpl, nil } ``` 三阶段处理流程。大约60-95%的尝试能通过此流程,主要取决于代理质量。没有过滤器的话,资源池会混合正常工作和静默失效的设备,由于失效设备同样返回空200(与其他故障相同),资源池会不可察觉地退化。成功率会在数天内逐渐下降,而日志中没有任何解释。 有了过滤器,资源池在设计上就具备统一能力。运行时的服务器健康状态一目了然: ``` $ curl -s localhost:8080/v1/devices | jq { "live": 43, "generated_total": 43, "rejected_total": 2, "evicted_total": 0, "success_total": 177, "failure_total": 74, "generation_survival_rate": 0.9555 } ``` ## X-Argus内部机制 `X-Argus`不是字符串的哈希值。其明文是proto3线格式(变长整数和长度分隔字段)的**protobuf消息**,然后通过两级加密流水线处理。 该消息携带(除其他字段外): ```go type Argus struct { Magic int32 // 固定标记 Version int32 Rand int64 // 每请求随机值,0x10000000..0xFFFFFFFF MsAppID string // "1233" / "473824" LicenseID string DeviceID string SdkVersion int32 SdkVersionStr string AppVersion string EnvCode []byte CreateTime int64 // X-Khronos,同样在数据块内 BodyHash []byte // 请求体的SM3值(GET请求为16个零字节) QueryHash []byte // 字面查询字符串的SM3值 AlgorithmCount struct { SignCount int32 // 此安装已签名的请求数量 ReportCount int32 SettingCount int32 } Timestamp int64 SecDeviceToken string IsAppLicense int64 PskHash []byte CallType int32 ChannelInfo struct { PhoneInfo, Channel string; ... } } ``` 这是签名器实际填充的字段子集。确定字段编号是逆向工程的主要工作。 `AlgorithmCount.SignCount`是此安装已签名的请求数量计数器。真实手机的计数器在安装生命周期内稳步增长。发出常数值或每次请求重置为零的爬虫器,即使每个单独签名都能验证通过,也会产生统计上明显的模式。我在每个设备的合理范围内随机初始化它,这从未成为问题,但这种字段存在的目的正是为了在整体层面而非单个请求层面检测到简单的重放攻击。 ### 处理流水线 protobuf序列化后,按顺序经历以下步骤: ``` 1. pb = proto3_serialize(Argus{...}) 2. padded = pkcs7(pb, 16) // 密钥派生:签名密钥是每个aid对应的32字节常量 ```

相似文章

@sairahul1: https://x.com/sairahul1/status/2066809666592718879

X AI KOLs Timeline

一份关于构建AI驱动内容机器的详细指南,该机器从TikTok和Instagram等平台抓取热门内容,使用AI生成平台原生帖子,并利用ScrapeCreators、Kie.ai和Postiz等工具自动安排发布计划。

@Honcia13: 老外直接把AI全自动批量做TikTok爆款的完整教程甩出来了! 你还在手动发帖?太落后了! 五步零成本流程: -TikTok找爆款下载 -扔进Claude Opus 4.7分析钩子+文案 -Pinterest白嫖顶级图片 -Node.js…

X AI KOLs Timeline

一个完整的AI全自动批量制作TikTok爆款教程,五步零成本流程:从TikTok下载爆款视频、使用Claude Opus 4.7分析钩子和文案、从Pinterest获取图片、用Node.js自动合成图文视频,最后通过Postiz自托管定时群发。每周仅需2小时即可稳定产出30条内容。