ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TikTok用户详情采集实战:从接口解析到签名绕过与数据落库

TikTok用户详情采集实战:从接口解析到签名绕过与数据落库 简介一份面向 Python 开发者和数据分析人员的 TikTok 个人详情抓取演示工程以 7z 压缩包形式分发核心解决如何通过 requests、BeautifulSoup 等工具模拟请求并解析用户主页信息的问题。包体仅 1KB共 2 个文件其中 Python 源码文件演示了构造链接、发送 HTTP 请求、解析 HTML、提取用户名、头像、粉丝数、视频列表等字段的完整流程另一个文件为示例用户标识数据可作为请求参数或数据来源。资源还涉及 API 调用思路与异常处理设计能帮助初学者理解爬虫程序的基本骨架和常见排错方法。适合有一定 Python 基础、希望了解爬虫项目结构的开发者也适合需要采集用户公开信息的数据分析人员用来快速验证技术方案。目前已有 1170 人学习下载对于想快速上手网络数据采集与页面解析的开发者来说是一份短小精悍、可直接对照练习的参考样例。1. 一个名为 1033383881 的目录和一份可以跑的 test.py解压Tiktok个人详情demo.7z之后你得到的东西比想象中克制一个以纯数字命名的目录1033383881一个test.py。没有 README没有 requirements.txt没有配置模板。把这个 demo 跑通的过程其实就是把 TikTok 用户详情页的数据链路重新走一遍的过程请求入口怎么选、签名参数卡在哪、返回结构长什么样、哪些字段值得落库。这个资源适合两类人——刚接触爬虫想找一个有真实反爬场景的练手项目的 Python 开发者以及需要批量维护达人数据的运营或数据分析师。我的建议是别急着跑脚本先把请求入口和返回结构看清再去纠结签名问题这篇就按这个顺序拆。2. test.py 的请求入口与 TLS 指纹2.11033383881到底是 uniqueId 还是 secUid先分清再写代码TikTok 的用户标识有两种常见形态uniqueId是用户在设置里自定义的短链名称比如scout2015这种secUid则是一长串加密后的字符串通常以MS4wLjABAAAA开头。纯数字 ID 在这套体系里有点特殊它一般出现在旧版接口的uid字段里或者是移动端数据上报时使用的用户 id。我看见 demo 目录用1033383881命名这个值最合理的用途是作为secUid或uid传入用户详情接口少数情况下它也会是uniqueId。区分方法很简单直接请求用户详情页看返回给浏览器的初始数据里userInfo.user.uniqueId字段是否与传入值一致。如果不一致说明传入的是uid体系的数据需要改参数名。下面这段脚本是请求入口的最小骨架import requests session requests.Session() session.headers.update({ User-Agent: (Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36), Accept: application/json, text/plain, */*, Referer: https://www.tiktok.com/1033383881, }) def fetch_user_detail(user_id: str): # 注意TikTok 的接口参数名在改版中变过同一套参数不一定适用所有地区 params { uniqueId: user_id, aid: 1988, } resp session.get( https://www.tiktok.com/api/user/detail/, paramsparams, timeout10, ) print(HTTP status:, resp.status_code) print(response head:, resp.text[:200]) return resp if __name__ __main__: fetch_user_detail(1033383881)这段代码里有两个容易被忽略的点。aid1988是 Web 端常用的应用标识但这并不是官方公开约定的稳定值TikTok 调整客户端参数时会连带更换Referer必须带上用户主页地址否则部分地区的接口会直接返回 403。整个请求的关键不是requests.get本身而是后续对返回内容的处理——如果拿到的 body 里根本没有userInfo字段通常是签名校验没通过而不是代码写错。2.2 请求发出之后先看三个地方而不是直接看解析结果新手最容易犯的错是拿到响应就立刻写BeautifulSoup去解析 HTML。TikTok 的页面结构经过多次改版绝大部分信息都不在静态 HTML 里而是塞在页内嵌的 JSON 或接口响应里。test.py 这类示例代码的正确打开方式是先做三件事看状态码、看返回头里的Content-Type、看 body 前 200 字符是不是 JSON。resp fetch_user_detail(1033383881) print(server:, resp.headers.get(x-tt-trace-id)) print(content-type:, resp.headers.get(content-type)) try: data resp.json() except requests.exceptions.JSONDecodeError: print(not json, maybe html or verify page) data Nonex-tt-trace-id是一个比较有用的排错线索接口正常时它一定存在如果响应被风控拦截这个头会缺失或返回一个错误的 trace id。body 前 200 字符若是 HTML 片段则大概率是验证页或落地页。处理这类响应时的常见做法是检查verify关键字或captcha字样而不是试图去暴力解析。2.3 签名参数对请求的影响范围比你想象的更广requests发出去的请求和浏览器发出去的请求在 TLS 指纹、HTTP 头顺序、Cookie 状态上都有差异。TikTok 对/api/user/detail/这类接口的校验通常由两层组成第一层是常规的 User-Agent、Cookie 校验第二层是ms_token、X-Bogus这类签名参数。缺少第二层时接口不一定会拒绝请求但返回的 JSON 里userInfo字段会是nullstatusCode可能仍然是 0表现得很像正常响应。只有在签名校验被绕过或成功生成的情况下请求中间层才会把userInfo完整吐出。正因为这个原因test.py这类 demo 学到的第一课不是解析而是识别假性成功。判断接口是否真的通了不能只看 HTTP 200要看业务字段。3. 个人详情返回结构与关键字段的解析策略3.1 先拆 JSON 层级再做字段映射当请求真正返回数据后TikTok 用户详情接口的顶层一般包含userInfo、stats、shareMeta等几个区块。userInfo.user里是用户基础信息userInfo.stats里是粉丝数、点赞数、作品数这些核心指标。不同时间段的接口返回层级会有差异有的版本把stats提到顶层有的版本嵌套在userInfo下。稳妥的做法是先递归打印 key 结构再写字段映射。def walk_json(obj, prefix, depth0): if depth 3: return if isinstance(obj, dict): for k, v in obj.items(): print(f{prefix}{k}: {type(v).__name__}) walk_json(v, prefix , depth 1) elif isinstance(obj, list) and obj: print(f{prefix}[0]: {type(obj[0]).__name__}) walk_json(obj[0], prefix , depth 1) walk_json(data.get(userInfo, {}))这个递归打印的价值在于快速确认接口的真实层级避免靠记忆写路径。实际处理test.py这类示例时解析代码通常会写成直接访问data[userInfo][stats][followerCount]的形式一旦接口结构调整就崩。我的习惯是在解析前用json.dumps(data, ensure_asciiFalse)把完整结构存一份到本地改接口时能少做很多无用功。3.2 个人详情字段对照表以及哪些值得落库以下字段是目前 Web 端用户详情里最常见的一批变量名以接口实际返回为准使用前先walk_json核对层级字段路径含义类型推荐落库类型userInfo.user.id用户 uidstringBIGINTuserInfo.user.uniqueId用户唯一 IDstringVARCHARuserInfo.user.nickname昵称stringVARCHARuserInfo.user.signature个人简介stringTEXTuserInfo.user.avatarLarger大头像 URLstringVARCHARuserInfo.user.verified认证状态booleanTINYINTuserInfo.user.secUid加密 uidstringVARCHARuserInfo.stats.followerCount粉丝数intINTuserInfo.stats.followingCount关注数intINTuserInfo.stats.heartCount总点赞数intINTuserInfo.stats.videoCount视频数intINTuserInfo.stats.diggCount总获赞数intINT真正值得长期维护的其实是followerCount、heartCount、videoCount这组随时间变化的量昵称和签名随时会改uniqueId也可能变所以每次采集时最好带上collected_at时间戳保留历史版本而不是覆盖。3.3 把解析结果整理成字典并处理缺失字段接口返回的字段并不总是齐全未认证用户的verified可能直接缺席部分账号没有signature商业账号多出commerceInfo区块。直接访问不存在的 key 会抛KeyError即使捕获异常也会中断整批处理。更合适的方案是用dict.get()配合默认值或者写一个带默认值兜底的取值函数def get_stat(data: dict, key: str, default: int 0) - int: try: return int(data.get(key, default)) except (TypeError, ValueError): return default user data.get(userInfo, {}).get(user, {}) stats data.get(userInfo, {}).get(stats, {}) row { uid: user.get(id), unique_id: user.get(uniqueId), nickname: user.get(nickname), signature: user.get(signature, ), followers: get_stat(stats, followerCount), hearts: get_stat(stats, heartCount), videos: get_stat(stats, videoCount), }这样每条记录的字段类型一定是可控的入库时不需要再清洗。签名校验失败时userInfo缺失dict.get(userInfo, {})返回空字典row里的字段就都是默认值不会抛异常但这种情况会在下游产生脏数据。所以在更贴近生产的使用里我一般会在字段提取后加一个校验if not row[uid]: raise ValueError(userInfo missing)把假性成功挡在入库前。4. 签名校验的绕过思路与异常识别4.1 从浏览器直接复制签名参数的思路适合快速验证ms_token和X-Bogus这两种参数与浏览器环境绑定在完全没有逆向能力的前提下验证数据解析逻辑是否正确的常用做法是打开浏览器开发者工具切到 Network 面板刷新用户主页找到user/detail这条请求把Request Headers里的关键参数复制到一个配置文件里。test.py这种 demo 里也常见这种写死参数的实现因为它的核心目标是把数据链路跑通而不是把全自动采集做完整。# config.json 手动维护不提交到仓库 { ms_token: 从浏览器复制, x_bogus: 从浏览器复制, cookies: odin_tt...; passport_csrf_token... }import json with open(config.json, r, encodingutf-8) as f: cfg json.load(f) session.headers.update({ ms_token: cfg[ms_token], X-Bogus: cfg[x_bogus], Cookie: cfg[cookies], })这种手工维护方式有两个硬伤ms_token有有效期通常几小时到几天不等失效后需要人工更换X-Bogus是和 URL 参数算出来的动态签名如果请求参数变了它也会变复制下来的值只能管一次请求。因此它适合调试解析逻辑不适合批量采集。4.2 构造带签名参数的请求时要注意 URL 编码当你开始尝试构造自己的请求而不是直接复制浏览器的完整 URL 时secUid中的特殊字符会成为第一道坎。secUid以MS4wLjABAAAA开头后续部分包含、/、等 base64 字符放进 URL 查询参数时必须做 URL 编码。from urllib.parse import quote, urlencode sec_uid MS4wLjABAAAAXXXX encoded_sec quote(sec_uid, safe) params { secUid: encoded_sec, aid: 1988, app_language: en, } query_string urlencode(params) url fhttps://www.tiktok.com/api/user/detail/?{query_string}这里quote(sec_uid, safe)把变成了%3D%3Durlencode会处理空格和特殊字符。常见的错误是用requests.get(url, paramsparams)requests 内部也会做一次编码这时如果secUid已经被你手动编码过一次就会出现双重编码服务器解出来的字符串和原始值不一致。解决方法是只保留一种编码方式要么自己拼接完整 URL要么把原始值传给params。4.3 签名参数校验失败时请求返回什么样的特征识别签名校验失败比构造签名更重要。根据我处理这类接口的经验失败响应通常表现出以下一个或多个特征HTTP 状态码为 200但响应体是 HTML 而不是 JSON响应体是 JSON但userInfo字段为nullstatusCode为 0响应头中缺少x-tt-trace-id页面返回verify相关标识重定向到验证码 URL。当看到这些特征时问题通常不在代码而在签名。业务字段的缺失是判断失败的核心标准不建议用resp.status_code ! 200作为阻断条件因为大多数风控拦截恰恰是 200。5. 用 py7zr 与 p7zip 正确处理这个 7z 包5.1 一个 7z 包在 Python 里应该怎么解而不是靠双击项目标题里的demo.7z格式决定了它和 zip 的分工不同7z 使用 LZMA2 压缩算法同体积下压缩率比 deflate 高但解压速度与内存占用呈正相关。Windows 上解压这类文件通常会装 7-Zip但放在部署环境里最常见的做法是用py7zr在 Python 里直接处理不依赖外部 GUI。py7zr的接口非常简单解析和提取都封装在SevenZipFile里import py7zr archive_path Tiktok个人详情demo.7z with py7zr.SevenZipFile(archive_path, moder) as archive: archive.extractall(pathoutput) file_list archive.getnames() print(压缩包内文件, file_list)extractall(pathoutput)会把1033383881目录和test.py解压到指定目录getnames()返回包内路径列表适合解压前先检查文件结构。需要注意py7zr的版本差异——0.20 之前的版本对某些 7z 分卷和加密头的支持有缺陷跑 demo 时如果报UnsupportedCompressionMethodError优先升级py7zr到最新版本再排查其他问题。5.2 只读包内文件内容而不完整解压效率更高如果你只是想快速看一眼test.py里的核心逻辑不需要把整个包解出来。py7zr提供了read()方法直接读取包内文件内容返回的是一个字典key 是文件路径value 是ByteIO对象。这比解压到磁盘再读文件省去了两次 IO对包含大量资源的压缩包来说差距很明显。import py7zr with py7zr.SevenZipFile(Tiktok个人详情demo.7z, moder) as archive: content archive.read([test.py]) for name, byte_stream in content.items(): text byte_stream.read().decode(utf-8) print( 文件:, name, ) print(text[:300])读取后要对byte_stream调用read()拿到字节再用decode(utf-8)转文本。如果源码里有中文注释并且你的默认编码不是 UTF-8decode需要显式指定encodingutf-8否则会抛UnicodeDecodeError。这种情况在 Windows 上最常见因为 Python 在 Windows 的默认编码是gbk。5.3 Linux 服务器上处理 7z 的另一个落地方案服务器环境不一定有 Python 依赖安装权限或者你对py7zr的压缩性能不放心这时可以退回使用p7zip提供的命令行工具。在 Debian/Ubuntu 上安装p7zip-full后直接用7z命令操作# 查看压缩包内容列表不解压 7z l Tiktok个人详情demo.7z # 完整解压到当前目录 7z x Tiktok个人详情demo.7z -o./extracted -y7z l是 list 命令输出包内文件名和原始大小7z x是 extract 命令-o指定输出目录-y跳过所有交互确认。当压缩包文件名里带有中文或特殊字符时在 Linux shell 下需要额外注意-o路径不要带空格否则容易被 shell 拆分成多个参数。如果你的 Python 项目最终要部署到容器里在 Dockerfile 中用RUN apt-get install -y p7zip-full会比在 Python 里维护py7zr的依赖更省事因为后者默认不包含任何系统级编译依赖。5.4 解压后第一件事是看目录名而不是改代码解压拿到1033383881目录和test.py后我一般会先确认一件事1033383881作为目录名是不是和test.py里的用户 ID 完全一致。这个 demo 里的纯数字 ID 被用作目录名它要么是账号的uniqueId要么是代码里的uid缺省值。打开test.py搜索1033383881如果代码里根本没有这个值说明目录名和代码解耦了——你需要手动把目录名当作参数传进去。如果代码里有硬编码的 ID 但和目录名不一致优先以代码里的值为准因为目录名可能是文件名生成的默认值。这是一个不写进 README 的隐性约定也是这类资源最常见的信息缺口命名人和写代码的人不一定是同一个人。本文还有配套的精品资源点击获取
返回列表