做内容分析和账号运营的朋友,多少都遇到过这样的需求:想批量看一个对标账号最近发了什么视频,想统计竞品账号的点赞评论趋势,或者纯粹想把自己账号的历史作品备份下来。这种时候,“抖音视频数据抓取”就成了绕不开的话题。抖音的网页端、App端每天都在产生大量公开数据,通过分析接口、构造请求,完全可以自动化地拿到这些数据,并用在做内容选题、账号诊断、爆款分析等场景。这篇文章梳理了一套基于Python的抖音数据抓取实践方案:包含整体思路、Web端接口分析、签名参数处理、无水印视频下载、并发设计,以及我实际跑过之后踩到的坑,适合有一定Python基础、想做抖音数据分析或自媒体运营参考的读者。
1. 抖音数据抓取先定方案:三类主流路径怎么选
1.1 先想清楚要抓什么,再谈怎么写代码
很多新手上来就搜“抖音爬虫”,然后找个代码跑一下,发现要么接口失效,要么被限制,最后不了了之。我个人的做法恰恰相反,动手写代码之前,先把目标数据列清楚。
拿最常见的需求举例:
- 对标账号分析,核心是“用户主页作品列表”,需要拿到每条视频的标题、发布时间、播放量、点赞数、评论数、分享数;
- 爆款选题挖掘,核心是“搜索接口”,根据关键词拉一批视频,再看它们的共同特征;
- 视频备份,核心是“单个视频详情”,重点是下载地址和封面图;
- 评论区运营,核心是“评论列表”,需要分页拉取完整评论内容。
不同的目标,对应完全不同的接口和字段,代码结构也不一样。如果只抓主页列表却想分析评论区,等于从一开始就走错了方向。所以第一步是把自己要的字段写在一张纸上,再反推需要调哪些接口。这个习惯能帮你省下大把无效编码时间。
1.2 三类方案对比:官方接口、Web端接口、App端接口
我把自己试过的方案整理成一张表:
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 抖音开放平台官方接口 | 合规、稳定、有文档 | 申请门槛高、权限有限、数据字段少 | 企业级合规数据服务 |
| Web端接口(网页版抖音) | 接口直接、签名可控、爬取难度适中 | 需要处理Cookie与签名、频率过高会被限制 | 个人学习、中小规模数据分析 |
| App端接口 | 数据全、字段丰富 | 签名复杂、逆向工作量大、版本更新频繁 | 进阶研究、大规模采集 |
我平时用得最多的是Web端接口。原因很简单:Web端接口用浏览器就能看到,抓包分析方便;签名参数虽然有风控,但社区里已经有相对成熟的生成方案;如果只是拉几千条公开的作品列表和评论,性能完全够用。App端接口我建议作为进阶了解,没有充分的逆向经验不要一上来就碰。
1.3 我为什么推荐从Web端接口入门
新手最容易犯的错,是以为“接口越底层越厉害”,所以一上来就研究App逆向。但实际上,Web端接口的数据量和字段已经非常丰富。比如用户主页的接口会返回完整的视频列表,每个视频的统计信息基本都有;评论接口也支持翻页,能拉到几千条评论。
另外,Web端接口的理解成本低。浏览器开发者工具里能看到完整的请求链路,遇到问题可以立刻对比参数差异,调试体验比App逆向友好太多。而且Python的requests库可以直接模拟,不需要处理HTTPS证书锁定这些复杂问题。等Web端跑通了,再回头研究App端会容易很多。
2. 核心细节拆解:抖音接口、签名参数与无水印下载原理
2.1 用Charles抓包,定位抖音主页的真实请求
定位接口是整个爬虫项目里最重要的基本功。我的习惯是用Charles抓包Web端请求,因为它能解密HTTPS流量,看得比浏览器Network面板更细。
在Mac或Windows上,Charles的基本流程是一样的:打开Charles,在菜单里开启SSL Proxying,然后在手机或浏览器里配置代理,安装并信任Charles的根证书。之所以要装这个证书,是因为HTTPS流量是加密的,Charles需要借助本地根证书做中间解密,才能看到请求的具体参数。这也是很多新手卡住的地方——证书没信任,抓到的一堆都是乱码。
之后在浏览器访问抖音用户主页,Charles里就会出现完整的请求记录。按“www.douyin.com”过滤,重点看几个带aweme字样的请求,它们就是核心接口。实际找接口时有个省事技巧:先清空Charles的会话,然后在抖音页面里只操作一次“往下翻一页”,这样新增请求里就能精准定位到“加载下一页作品列表”的那个接口,比在几十条请求里乱翻高效得多。
2.2 a_bogus与msToken:抖音Web端的签名机制
拿到接口地址后,你会发现URL里跟着一堆参数,常用的有sec_user_id、max_cursor、count,还有两个很关键的反爬参数:msToken和a_bogus(不同版本可能叫x-bogus、X-Bogus)。抖音服务端会校验这些签名参数,如果缺失或错误,接口会返回“参数错误”或直接拒绝访问。
msToken大多时候可以从抖音页面的Cookie里拿到,有效期相对较长,可以在请求前手动复制一次。a_bogus则是一次性生成的值,和请求URL、User-Agent、Cookie都有关系,不能直接写死。好在社区里已经有很多现成的a_bogus生成实现,搜索“a_bogus Python”就能找到相应的开源代码,把它封装成一个函数,每次请求前按规则生成即可。
这里要提醒大家:抖音的签名算法会不定期更新,一个月前能用的代码,下个月可能就失效。这是抖音数据抓取的常态,并不是你的代码写错了。遇到接口突然大面积报错,先看看是不是签名算法变了。
2.3 抖音号转UID的两种实用方法
“抖音号转UID”是很多人都问过的需求。这里的抖音号指的是用户主页显示的那段自定义ID,比如“abc123”;而接口里真正用的ID是sec_uid或uid。一个抖音号可以绑定一个uid,但对外展示的是抖音号,所以需要转换。
第一种方法,通过分享链接解析。在抖音App里点用户主页的分享按钮,复制出来的链接通常是短链,短链开头是v.douyin.com。用requests请求这个短链,不自动跟随重定向,从响应头的Location里就能看到用户主页地址,地址里就有sec_uid参数。
第二种方法,通过网页版搜索接口。如果只有抖音号,没有分享链接,可以调抖音网页版的用户搜索接口,入参是关键词,返回值里会包含用户的sec_uid。这个接口同样需要签名,但思路和主页接口一致。
下面是用分享链接解析sec_uid的代码:
import requests headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36", "Referer": "https://www.douyin.com/" } short_url = "https://v.douyin.com/xxxxx/" resp = requests.get(short_url, headers=headers, allow_redirects=False) location = resp.headers.get("Location", "") sec_uid = "" if "sec_uid=" in location: sec_uid = location.split("sec_uid=")[1].split("&")[0] elif "/user/" in location: sec_uid = location.split("/user/")[1].split("?")[0] print("sec_uid:", sec_uid)拿到sec_uid之后,主页数据接口和详情接口就都能调了。
2.4 无水印视频下载的实现思路
“抖音无水印下载”这个需求热度一直很高。实现思路其实不复杂:当你在抖音网页版看到一个视频,真正播放的视频地址带着类似“playwm”的标识,这是播放器用来标记水印版本视频的。把URL里的“playwm”替换成“play”,请求到的就是无水印版本。
具体操作链路是:先通过主页列表接口拿到每条视频的aweme_id,然后请求视频详情接口获取play_addr,再对play_addr做一次替换和一次重定向跟随。我写过一个简化版的下载函数:
def get_no_watermark_url(play_addr): # play_addr 形如 https://.../playwm/?video_id=xxx if "playwm" in play_addr: play_addr = play_addr.replace("playwm", "play") resp = requests.get(play_addr, headers=headers, allow_redirects=True) return resp.url这里的关键是allow_redirects=True一定要打开。因为原始地址会重定向到真实CDN地址,重定向后的URL才是可以直接下载的视频源。实际使用中还需要带上Referer和User-Agent,否则CDN可能返回403。顺带说一句,直播回放和普通视频的下载逻辑稍微不同,核心差异在流媒体地址的解析上,不在本文讨论范围内。
3. 实操记录:从零写一个抖音视频列表抓取脚本
3.1 环境准备与依赖安装
我建议用Python 3.9以上版本,新建一个干净的虚拟环境,避免依赖冲突。核心依赖其实很少,requests和pandas足够;如果后面做并发,再加一个httpx。
python -m venv douyin_env source douyin_env/bin/activate # Windows: douyin_env\Scripts\activate pip install requests pandas httpx存储方面,个人项目直接用CSV就行,字段少、看得直观;如果数据量到十几万条,建议换成SQLite。我前期的习惯是先落CSV,中途用pandas做简单统计,等确认数据结构没问题再考虑入库。虚拟环境这一步很多人会跳过,但实际项目里依赖一多就会出事,还是建议养成习惯。
3.2 抓取用户主页视频列表的完整代码
下面这个脚本的核心逻辑是:用sec_uid请求主页作品列表接口,解析返回的JSON,把每条视频的关键字段写入CSV,然后通过max_cursor翻页,直到抓完所有作品。
import requests import pandas as pd HEADERS = { "User-Agent": "Mozilla/5.0 ...", "Referer": "https://www.douyin.com/", "Cookie": "你的cookie" } def gen_a_bogus(url: str) -> str: # 社区开源的a_bogus生成函数 # 具体实现请根据你找到的开源库替换 return "A_BOGUS_STRING" def fetch_user_posts(sec_uid, max_cursor=0): base_url = "https://www.douyin.com/aweme/v1/web/aweme/post/" params = { "sec_user_id": sec_uid, "max_cursor": max_cursor, "count": 18, "a_bogus": gen_a_bogus(""), } resp = requests.get(base_url, headers=HEADERS, params=params) return resp.json() rows = [] sec_uid = "你的sec_uid" cursor = 0 has_more = True while has_more: data = fetch_user_posts(sec_uid, cursor) aweme_list = data.get("aweme_list", []) for item in aweme_list: rows.append({ "aweme_id": item.get("aweme_id"), "desc": item.get("desc"), "create_time": item.get("create_time"), "like_count": item.get("statistics", {}).get("digg_count"), "comment_count": item.get("statistics", {}).get("comment_count"), "share_count": item.get("statistics", {}).get("share_count"), }) cursor = data.get("max_cursor", 0) has_more = data.get("has_more", 0) == 1 df = pd.DataFrame(rows) df.to_csv("douyin_posts.csv", index=False, encoding="utf-8-sig")这段代码是能跑的主干版本。落地之后你可以加上超时、重试、日志,再慢慢迭代成自己的工具。另外,count这个参数不建议一次性拉太多,我试过拉50条甚至100条,响应体非常大且容易触发风控,默认的18到20条反而稳定。
3.3 详情数据与评论数据怎么补
主页列表接口已经包含很多统计字段,但有些信息需要单独调详情接口才能拿到,比如视频的完整话题列表、关联音乐、视频标签等。详情接口的名称是aweme/v1/web/aweme/detail/,入参主要是aweme_id,返回的结构和列表里的单条视频基本一致,只是字段更完整。
评论接口同样按aweme_id拉取,地址是aweme/v1/web/comment/list/,分页参数是cursor和count。需要注意评论接口返回的字段里包含脱敏后的用户标识、昵称、评论内容、点赞数,这些数据对评论区分析很有用,但也意味着需要更谨慎地处理隐私合规问题。
我实际做下来的体会是:详情接口可以按需调用,不必每条视频都拉;评论接口建议配合延时,并且只处理自己业务真正需要的字段,不要一股脑全存下来。像评论区情感分析、争议话题监控这类需求,评论字段里的点赞数和回复数往往比评论内容本身更有分析价值。
3.4 并发设计:协程、信号量与限速的经验
数据量一大,单线程爬太慢,这时候就需要并发。“爬虫并发设计到底哪个好”是很多人纠结的问题。我的结论是:在抖音这类有风控的网站面前,并发不是越快越好,而是“越快越容易被盯上”。
线程池的好处是简单,ThreadPoolExecutor加map就能跑,但Python的GIL在IO密集型任务下影响不大,问题是线程多了一旦触发风控,整个账号的Cookie都会被标记。协程是更优雅的方案,asyncio加httpx,单线程内做异步IO,配合信号量限制并发数,速度和稳定性都很好。
我常用的限制策略是:
- 并发数控制在3到5,不要超过10;
- 每抓完一页随机sleep 1到3秒;
- 同一个账号连续抓取超过1000条数据后,强制休息5分钟。
这些数字不是拍脑袋定的,是我在实际运行中逐步试出来的。太快会触发验证码,太慢又失去了并发的意义,找到一个平衡点就好。
4. 踩坑实录:抖音爬虫常见问题与排查方法
4.1 高频报错与风控应对
跑抖音爬虫,遇到最多的是“当前访问人数过多,请稍后再试”,这基本就是触发风控了。遇到这种情况,我一般按这个顺序处理:先停掉所有请求,等几分钟;然后把Cookie换掉,重新登录抖音网页版获取新的Cookie;再降低请求频率,把并发数调低,sleep拉长。
另一个高频问题是接口返回OK但数据为空,比如aweme_list是空数组。这种情况多半是sec_user_id填错了,或者该账号设置了隐私保护、不是公开主页。可以先在浏览器里打开这个用户的主页,如果浏览器都看不到作品列表,那接口拿不到也是正常的。
还有一类问题是签名报错,response里带“verify”或“risk”字样的字段。处理办法是先检查a_bogus生成逻辑是否正常,再把User-Agent和Cookie一起传给签名函数,因为签名是和这些参数绑定的,不能只传URL。
4.2 排查流程与调试工具
我的排查习惯是从外到内:先确认网络和Cookie没问题,再看请求参数,最后才怀疑代码逻辑。
具体流程是:打开浏览器开发者工具,在Network面板里手动点一次翻页,把新生成的完整请求复制成cURL;然后在本地用同样的URL和header发一次请求,对比返回结果。如果本地和浏览器结果一致,说明接口逻辑没问题,问题出在代码处理上;如果不一致,就去比较浏览器请求和本地请求的差异,重点看Cookie、a_bogus、sec_user_id这些参数。
调试时我还会在关键步骤加日志,不要用print一把梭。简单用logging模块,把每次请求的URL、状态码、返回大小记录下来,方便事后排查是不是某次请求触发了风控。另外强烈建议准备一个好用的接口调试工具,直接对比header,比在代码里反复试错高效。
4.3 关于合规,我给自己定的三条红线
做数据抓取,可以没有技术洁癖,但不能没有合规意识。我自己实操的时候,给自己定了三条红线:
第一,只采集公开数据。没有公开的主页、没有公开的作品列表,就不碰;涉及用户私密信息的一律跳过。
第二,控制频率,不搞压力测试。我既不做大规模分布式爬虫,也不做数据倒卖,抓的数据只用于自己的分析和学习。
第三,尊重平台的异常检测规则。一旦接口开始报风控,立刻停止,而不是换账号继续硬刚。
把这三条红线写在代码注释里,既是提醒自己,也是给后来看代码的人一个交代。尤其现在数据合规越来越严格,数据和隐私边界问题一不小心就会惹上麻烦,保持克制不是胆小,是长期主义的做事方式。
4.4 常见问题速查表
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| 返回“当前访问人数过多” | 触发风控 | 停请求、换Cookie、降频 |
| aweme_list为空 | sec_user_id错误或主页不公开 | 浏览器里手动验证 |
| 签名错误/参数错误 | a_bogus过期或生成参数不完整 | 重跑签名函数,检查UA与Cookie |
| 下载视频403 | 缺少Referer或User-Agent | 补全请求头,跟随重定向 |
| 评论分页拉不全 | 参数名或游标字段用错 | 对比浏览器里的真实请求参数 |
| Cookie失效 | 登录态过期 | 重新登录网页版换Cookie |
这张表是我在多个项目里反复用到的排查清单。遇到新问题,先看表里有没有对应项,没有再去抓包对比,效率会高很多。
最后说点实在话。我在做抖音数据抓取项目时最大的体会是:真正难的从来不是写代码,而是对接口变化的适应能力和对数据边界的把控。签名算法会变、接口会变、风控策略会变,但“先抓包分析再构造请求最后解析数据”这套方法论是固定的。每次启动脚本之前,我都会重新用浏览器验证一遍接口是否还活着,再决定要不要跑全量。另外再分享一个小技巧:把常用接口的请求参数写成一个配置文件,每次接口变动时只改配置,不用重写代码,能省下不少维护时间。希望这套完整的方案能帮你在做抖音数据分析时少走几条弯路。