抖音APP抓包分析实践

不管做内容分析还是用户行为研究,抖音这类头部短视频 APP 的数据几乎是绕不开的——可惜网页版要么功能阉割,要么反爬机制天天更新,而 APP 端看似更贴近真实数据,却又面临 SSL Pinning、复杂签名、设备绑定这些门槛。

今天我们从「入门级极简方案」切入,帮你理清一条完整的思路:
抓包流量 → 识别核心 API → 解析抓包响应 → 避开签名抓取数据
文末还附上一个可以直接用抓包数据跑起来的 Python 解析工具,方便验证数据结构和提取逻辑。


一、前置操作:先拿到「能看懂的明文流量」

抓包是所有 API 分析和爬虫开发的第一步,这一步没通,后面的都白谈。

为什么要选「旧组合」?

主要是为了绕过目前抖音最基础的两道反爬门槛

  1. 系统证书信任限制
    Android 12 及以上版本默认不再信任用户安装的 CA 证书,必须把证书塞到 Root 后的系统目录;iOS 同理,不越狱也很难办。所以选 Android 9 及以下,或者带 Root / Frida 的旧模拟器/旧真机,是最省心的方法。

  2. SSL Pinning(证书锁定)
    2024 年中之后的新版抖音,基本全都开启了证书锁定,只认内置的字节跳动根证书,你就算把抓包工具的 CA 装上了也没用。优先找 23.5.0 到 23.9.0 这些旧版 APK,能绕开大部分 Pinning 限制。

极简入门方案:雷电9模拟器 + Mitmproxy + 旧版抖音

这套组合门槛最低,新手 15 分钟内大概率就能把流量跑通。

1. 工具安装与基础配置

角色推荐工具安装 / 配置要点
流量捕获 + HTTPS 解密Mitmproxy / Mitmweb下载最新版 Mitmproxy 并解压,终端 / CMD 运行 mitmweb -p 8080(会自动在 8081 端口打开 Web 监控页)
模拟器 WiFi 代理设置雷电模拟器 9(Android 9)右上角「设置」→「WiFi」→ 长按默认的 LeidianWiFi →「修改网络」→「高级选项」→ 代理选「手动」,IPv4 填你本机 WLAN 的局域网 IP(Windows:cmdipconfig → 找到 WLAN 适配器的 IPv4 地址),端口填 8080
模拟器安装 CA 证书Mitmproxy 自带 CA在模拟器浏览器访问 mitm.it → 选择 Android 下载证书 → 拖入模拟器「文件传输」→ 找到后安装,命名随便,但要记住它是一个「根证书」

💡 常见坑点:

  • 代理 IP 不要填 127.0.0.1,那指向模拟器自身,要填你宿主机的局域网 IP
  • 安装证书时系统可能会要求设置锁屏密码,按提示设一下就行。

2. 验证配置是否成功

  1. 先打开 Mitmweb 的 Web 监控页(http://localhost:8081),看看有没有基础的网络请求冒出来。
  2. 安装一个 23.7.0 左右的旧版抖音(别去应用商店搜,去 APKPure 或历史版本站点找安装包)。
  3. 打开抖音,随便刷 3~5 条推荐视频,回到 Web 监控页,看有没有 aweme.snssdk.com 开头的、且返回内容是明文 JSON 的请求——如果有,说明双关全过,可以继续往下走了。

二、流量筛选:只抓有用的「核心 API」

一次抓包会冲出成百上千条请求,不要慌,先把精力放到关键域名和返回格式上。

快速过滤三原则

  • 静态资源直接忽略p*.douyinpic.com(图片)、v*.douyinvod.com(视频文件)这类请求只是在下载素材,对数据抓取没什么价值。
  • 只留 JSON 明文:重点关注 aweme.snssdk.com 开头,且 Content-Type 为 application/json 的接口。
  • 记录高频好用的核心接口,下面这几个先认一下脸:
功能接口路径片段关键必带参数
用户主页信息/aweme/v1/user/sec_user_id(加密稳定的用户 ID,比纯数字 user_id 可靠)
用户发布的全部视频(分页)/aweme/v1/aweme/post/sec_user_idcount(单页数量,最大一般 20~30)、max_cursor(翻页游标,首次传 0)
单个视频详情/aweme/v1/aweme/detail/aweme_id(视频唯一 ID)
推荐流数据/aweme/v1/feed/type=0count

📌 简单理解:

  • sec_user_id 是用户 ID 的高稳定版本,不会因为纯数字 ID 的变化而失效,优先用它。
  • max_cursor 就是分页游标,响应里会给一个 has_more 字段和下一次请求要用的 max_cursor,照着传入就能翻页。

三、代码实践:抓包后的「API 响应解析工具」

⚠️ 特别声明

抖音真实的签名算法(像 _signaturex-gorgonx-khronosx-ss-stub 等)极其复杂,涉及 native 层和动态生成的函数,新手想在短时间内复现几乎不可能。
因此这里提供的是一个 「用抓包拿到的真实完整 URL / JSON 做解析 + 保存」 的工具——它完全不碰签名,直接利用你已经捕获到的明文数据,帮你验证 API 响应结构和数据提取逻辑。

完整 Python 代码

import requests
import json
import csv
from typing import Dict, Any, List, Optional
from datetime import datetime


class DouyinDataParser:
    """抓包后抖音API响应解析与结构化保存工具"""

    @staticmethod
    def extract_user_posts(
        response_data: Dict[str, Any],
        only_save_public: bool = True
    ) -> List[Dict[str, Any]]:
        """
        从「用户发布视频列表」接口提取结构化数据
        :param response_data: 抓包后 JSON 转成的 Python 字典
        :param only_save_public: 是否只保存公开可见的视频
        :return: 结构化后的视频列表
        """
        structured_videos = []
        raw_aweme_list = response_data.get("aweme_list", [])

        if not raw_aweme_list:
            print("未找到 aweme_list 字段,请检查是否传入了正确的接口响应!")
            return []

        for aweme in raw_aweme_list:
            try:
                # 过滤非公开视频
                if only_save_public and aweme.get("is_top", 0) != 1 and aweme.get("status", {}).get("allow_share", 1) != 1:
                    continue

                # 转换时间戳
                create_datetime = datetime.fromtimestamp(
                    aweme.get("create_time", 0)
                ).strftime("%Y-%m-%d %H:%M:%S")

                # 提取数据
                video_data = {
                    "视频ID": aweme.get("aweme_id"),
                    "视频标题": aweme.get("desc", "").strip().replace("\n", " "),
                    "发布时间": create_datetime,
                    "作者UID": aweme.get("author", {}).get("uid"),
                    "作者昵称": aweme.get("author", {}).get("nickname", "").strip(),
                    "作者抖音号": aweme.get("author", {}).get("unique_id", "").strip(),
                    "视频播放地址(CDN链接,可能失效较快)": aweme.get("video", {}).get("play_addr", {}).get("url_list", [None])[0],
                    "视频封面地址": aweme.get("video", {}).get("cover", {}).get("url_list", [None])[0],
                    "点赞数": aweme.get("statistics", {}).get("digg_count", 0),
                    "评论数": aweme.get("statistics", {}).get("comment_count", 0),
                    "转发数": aweme.get("statistics", {}).get("share_count", 0),
                    "播放数": aweme.get("statistics", {}).get("play_count", 0),
                    "是否置顶": "是" if aweme.get("is_top", 0) == 1 else "否",
                }
                structured_videos.append(video_data)
            except Exception as e:
                print(f"解析单个视频数据失败,跳过:{str(e)}")
                continue

        return structured_videos

    @staticmethod
    def save_to_csv(
        data: List[Dict[str, Any]],
        filename: Optional[str] = None
    ) -> None:
        """
        把结构化数据保存到 CSV 文件
        :param data: extract_* 函数返回的结构化列表
        :param filename: 保存的文件名,默认带时间戳
        """
        if not data:
            print("没有可保存的数据!")
            return

        if not filename:
            filename = f"douyin_posts_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"

        try:
            # 用 UTF-8 with BOM 保存,避免 Excel 打开乱码
            with open(filename, mode="w", newline="", encoding="utf-8-sig") as f:
                writer = csv.DictWriter(f, fieldnames=data[0].keys())
                writer.writeheader()
                writer.writerows(data)
            print(f"数据保存成功!文件路径:{filename}")
        except Exception as e:
            print(f"保存 CSV 失败:{str(e)}")


def main():
    print("=" * 60)
    print("抖音抓包后 API 响应解析工具(仅用于学习 API 结构)")
    print("=" * 60)
    print("\n使用步骤:")
    print("1. 完成前置抓包,拿到「用户发布视频列表」接口的完整响应 JSON;")
    print("2. 把 JSON 复制到当前目录下的「response.json」文件中;")
    print("3. 运行本工具即可自动解析并保存为 CSV。")
    print("=" * 60 + "\n")

    # 读取本地 JSON 文件
    try:
        with open("response.json", mode="r", encoding="utf-8") as f:
            raw_response = json.load(f)
    except FileNotFoundError:
        print("❌ 未找到「response.json」文件,请按使用步骤操作!")
        return
    except json.JSONDecodeError:
        print("❌ 「response.json」格式错误,请检查是否是有效的 JSON!")
        return

    # 解析数据
    parser = DouyinDataParser()
    posts = parser.extract_user_posts(raw_response)
    print(f"\n✅ 成功解析 {len(posts)} 条视频数据!")

    # 保存到 CSV
    if posts:
        parser.save_to_csv(posts)


if __name__ == "__main__":
    main()

🛠 使用方式:

  1. 从 Mitmweb 监控页里找到一次 /aweme/v1/aweme/post/ 接口的响应体,完整复制;
  2. 在脚本目录新建 response.json,把复制的内容粘贴进去并保存;
  3. 运行脚本,就会在当前目录生成一个带时间戳的 CSV 文件,里面是结构化好的视频信息。

四、后续进阶与合规提示

进阶取数据方案(避开 / 解决签名)

如果不想只停留在「先抓包、再解析」的半自动状态,希望实现半自动甚至全自动的数据采集,可以注意下面两个新手相对友好的方向:

  1. Appium + Mitmproxy 联动
    用 Appium 模拟真人的滑动、点击操作,Mitmproxy 在中间拦截真实接口的请求和响应,直接保存结构化数据。这种方式完全不需要复现签名算法,因为你始终在劫持真实 APP 发出的合法请求。

  2. Frida Hook 签名函数
    如果你已经有了一定的逆向基础,可以尝试用 Frida hook 抖音的 libxgorgon.so 或者 Java 层的签名生成类。实时调用这些原生函数来生成真实的 x-gorgon 等参数,再配合 Python 的 requests 模拟请求,就可以脱离抓包单独跑通接口。

⚠️ 合规提示(非常重要)

请务必遵守《中华人民共和国网络安全法》《中华人民共和国数据安全法》《中华人民共和国个人信息保护法》等相关法律法规:

  1. 只爬取公开可见的非敏感数据
  2. 不要高频请求,避免对抖音的正常服务造成影响;
  3. 不要将抓取到的数据用于商业用途
  4. 不要传播爬取到的个人信息(如手机号、地址、实名信息等)。

以上就是抖音 APP 抓包分析从零到一的实战思路。
核心要点其实就三条:选对旧版本绕过基础防护用 Mitmproxy 看清 API 流量先用抓包数据验证解析逻辑,再考虑自动化
只要这几点踩稳了,后面不管是做数据分析还是进阶逆向,都会顺手很多。