news 2026/9/26 3:31:49

【Python爬虫案例】抖音视频下载 + X-Bogus 参数 JS 逆向:从抓包到复现的完整链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Python爬虫案例】抖音视频下载 + X-Bogus 参数 JS 逆向:从抓包到复现的完整链路

1. 抖音网页端视频下载为什么会卡在 X-Bogus

抖音网页端的视频列表接口aweme/v1/web/aweme/post/不是拿到 URL 就能直接请求的。你在浏览器里打开一个博主主页,翻两页,Network 面板里能看到请求参数里带了一个X-Bogus,长度固定 28 位,每次刷新都不一样。少了它,服务端直接返回空数据或者风控页;随便填一个,同样拿不到aweme_list。

这个参数就是抖音前端 JS 在发请求前现算出来的签名。它跟 URL 里的 query 字符串、User-Agent、时间戳等都有关系,所以你不能写死一个值复用,必须把生成逻辑还原出来,在 Python 里每次请求前动态算。

这篇要解决的就是这条链路:怎么在浏览器里定位到 X-Bogus 的生成入口,怎么把混淆过的 JS 抠出来在 Node 环境跑通,怎么用 Python 的execjs调用它,最后拼出完整请求把视频真实地址拿到并下载。适合已经会写基础 requests 爬虫、想搞明白 JS 逆向签名这一环的人。全程只讲技术链路,请求频率自己控制,别拿去批量刷。

我试过直接抄网上现成的 X-Bogus 算法,版本一变就废,所以更推荐你把「定位入口 → 抠 JS → 本地复现」这套方法学会,换参数也能自己跟。

2. 抓包定位:从 XHR 筛选到加密函数入口

2.1 筛选目标接口

打开目标博主主页,F12 进 Network,勾选 XHR,然后在过滤框输入post。翻页时会出现形如:

https://www.douyin.com/aweme/v1/web/aweme/post/?device_platform=webapp&aid=6383&...

点开这个请求,看 Payload/Query String Parameters,里面有一堆device_platform、aid、sec_user_id、max_cursor、msToken,最后一个就是X-Bogus。对比两次翻页请求,你会发现只有X-Bogus、max_cursor、msToken在变,其余基本固定。max_cursor是翻页游标,msToken是另一个风控参数,本篇先聚焦 X-Bogus。

2.2 用启动器(Initiator)找调用栈

直接在 Sources 里全局搜X-Bogus往往搜不到,因为代码被混淆了,字符串是拼接出来的。更靠谱的是用 Initiator:

在 Network 里点开那个 post 请求,切到 Initiator 标签,能看到一条调用栈。点最上面一层,会跳到发起 ajax 的位置。这里通常是一个被混淆的XMLHttpRequest封装,X-Bogus在发送前已经被塞进 URL 了。

如果调用栈太深,用 XHR 断点更直接:Sources → XHR/fetch Breakpoints → 添加,URL 填aweme/v1/web/aweme/post。刷新页面,请求发出前会断下,此时在 Call Stack 里逐层往上翻,找到计算签名的那一层。

2.3 日志断点确认生成结果

断下来之后,在可疑的那行右键 → Add logpoint,输入:

console.log(_0x2458f0['apply'](_0xc26b5e, _0x1f1790))

继续执行,Console 里会打印出一串 28 位字符串,形如DFSzswVOljJANtOstup5PBt/pL3I,跟请求里的 X-Bogus 一致,说明找对了函数。

输出太多时用条件断点,因为 X-Bogus 长度固定 28:

_0x2458f0['apply'](_0xc26b5e, _0x1f1790).length == 28

这样只在真正生成签名时停下。顺着_0x2458f0往上找它的定义,最终会定位到一个加密函数,入参是拼好的 query 字符串,出参就是 X-Bogus。

3. 把混淆 JS 抠出来在本地跑通

3.1 复制核心 JS 文件

定位到加密函数后,看它来自哪个文件,通常是webmssdk.es5.js这类。把这个文件的全部代码复制到一个新建的douyin.js里。直接node douyin.js会报一堆错,因为它是给浏览器环境写的,缺window、document、navigator。

3.2 补齐浏览器环境

在douyin.js顶部补上这些:

window = global; document = {}; document.addEventListener = function () {}; navigator = { "userAgent": 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36' };

再执行,报错会少很多。差什么补什么,常见还有location、screen,按报错逐个补空对象或简单实现即可。

3.3 暴露生成函数

找到之前那个加密函数的位置,在文件末尾把它挂到全局,方便 Python 调用:

window.xiaoc = function (params) { return _0x5a8f25(params); // 换成你实际定位到的函数名 };

注意这里的userAgent必须和后面 Python 请求头里的 UA 完全一致,否则算出来的签名服务端不认。

4. Python 调用 JS 并拼出完整请求

4.1 用 execjs 调用

先装依赖:

pip install PyExecJS requests

调用代码:

import execjs with open("douyin.js", encoding="utf-8") as f: js_data = f.read() js_compile = execjs.compile(js_data) params = "device_platform=webapp&aid=6383&channel=channel_pc_web&..." xb_data = js_compile.call("window.xiaoc", params) print(xb_data) # 应输出 28 位字符串

如果这里报ReferenceError,回到第 3 步继续补环境;如果输出长度不是 28,检查传入的params是否和浏览器里完全一致(顺序、编码都不能变)。

4.2 请求头模板

headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36", "Referer": "https://www.douyin.com/user/MS4wLjABAAAAF0zlK2_5qCr5Lqy6zLNMH8W146aOetdfKSX95jYXwi8", "Cookie": "你的Cookie" }

UA 必须和 JS 里写的一致。Cookie 从浏览器复制,包含msToken、ttwid等,缺失会触发风控。

4.3 拼接 URL 并发请求

import requests url = "https://www.douyin.com/aweme/v1/web/aweme/post/?" params = "device_platform=webapp&aid=6383&channel=channel_pc_web&sec_user_id=MS4wLjABAAAAF0zlK2_5qCr5Lqy6zLNMH8W146aOetdfKSX95jYXwi8&max_cursor=0&count=18&..." xb_data = js_compile.call("window.xiaoc", params) full_url = url + params + "&X-Bogus=" + xb_data response = requests.get(url=full_url, headers=headers) print(response.json())

params里的字段顺序要和浏览器抓到的保持一致,X-Bogus是对整个 query 串算的,顺序变了签名就错。

5. 验证签名有效性并下载视频

5.1 判断签名是否生效

请求返回后,看 JSON 里有没有aweme_list。有且非空,说明 X-Bogus 有效;返回{"status_code": 8}或空列表,多半是签名错、Cookie 过期或 UA 不匹配。先用一个真实视频链接跑通单次请求,再考虑翻页。

5.2 提取视频地址

douyin_videos = response.json().get("aweme_list", []) for v in douyin_videos: print(v["video"]["play_addr"]["url_list"][0])

play_addr.url_list里通常有多个备用地址,取第一个即可。

5.3 下载到本地

import os os.makedirs("./DouYin", exist_ok=True) for v in douyin_videos: title = v["desc"].replace("/", "_") video_url = v["video"]["play_addr"]["url_list"][0] res = requests.get(video_url, headers=headers) try: with open(f"./DouYin/{title}.mp4", "wb") as f: f.write(res.content) print(f"视频 {title} 下载完成") except Exception as e: print(f"视频 {title} 下载出错 {e}")

标题里可能有/等非法字符,先替换掉再当文件名。

6. 本篇常见报错排查

execjs 报ReferenceError: window is not defined:douyin.js顶部没补window = global,或者补的位置在引用之后。放到文件最前面。

签名长度不是 28:传入的params和浏览器不一致,重点检查字段顺序、是否漏了msToken、URL 编码是否被二次处理。

返回空aweme_list:Cookie 过期、UA 与 JS 不一致、X-Bogus算错三者之一。先用浏览器复制的最新 Cookie 重试。

max_cursor翻页拿不到新数据:把上一次响应里的max_cursor传给下一次请求,同时重新算 X-Bogus,不能复用。

Node 执行报document is not defined:补document = {}和document.addEventListener = function(){},按报错继续补location、screen。

请求被重定向到验证页:请求频率过高触发风控,降低频率、加随机间隔,别用多线程猛刷。

排障和接入细节可以对照官方接入文档:https://taotoken.net/api ,API Keys 在 https://taotoken.net/api-keys 管理。想先验证模型返回结构,用模型对话页面 https://taotoken.net/chat 试跑;长期做编码或 Agent 任务,看 Coding Plan https://taotoken.net/coding-plan 。

翻页批量下载时,把max_cursor循环更新、每次重算签名,再配合time.sleep控制节奏,基本就能稳定拿到一个博主的全部视频列表。评论接口是另一套参数,思路一样,先抓包定位签名再复现。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 3:31:05

Ubuntu低配CPU部署YOLOv8:C++与onnxruntime推理实践

简介:在Ubuntu系统下需用C完成YOLOv8模型部署的开发者,可借助这套包含完整源码与说明文档的资源,实现基于onnxruntime和OpenCV的模型加载、推理与输出解析,尤其适合低配置机器上的深度学习应用体验。压缩包共363个文件&#xff0c…

作者头像 李华
网站建设 2026/9/26 3:29:09

JLink VCOM虚拟串口:无需USB转TTL实现printf打印调试

1. 为什么我放弃了USB转TTL,转投JLink VCOM搞嵌入式开发的朋友大概率都经历过这样的场景:板子已经连了JLink做下载和调试,程序里想加几行printf打印看看变量状态,结果发现手头没有USB转TTL模块,或者串口线被别的设备占…

作者头像 李华
网站建设 2026/9/26 3:26:56

基于Java的IEC 62056-21 C模式主站协议库:统一读取电水气热表

简介:面向能源管理、智能家居与市政计量领域的Java开发者,这份资源实现了IEC 62056-21 C模式主站协议,支持通过串口或网络连接燃气表、水表、热量表、电表等计量设备,直接读取标准化数据。协议库基于国际电工委员会标准设计&#…

作者头像 李华
网站建设 2026/9/26 3:25:43

全栈开发者桌面状态仪表盘:ESP32-S3 + JSON + BLE 实战设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华