尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Python模拟抖音扫码登录:Referer与Token校验避坑指南

发布时间:2026/9/20 23:05:36

资讯中心
01
ARTICLE

Python模拟抖音扫码登录:Referer与Token校验避坑指南

Python模拟抖音扫码登录:Referer与Token校验避坑指南
说个实话用 Python 模拟抖音扫码登录这件事难倒大多数人的根本不是二维码生成也不是最基本的请求构造而是扫码成功后带着 Token 去访问业务接口时满屏的 403、签名校验失败、Referer 校验不过。我最初踩这个坑时光是“Referer”和“Token”这两个词就在报错日志里翻来覆去出现了几十遍。这篇避坑指南就专门围绕这两个点展开把我实际调试中遇到的那些校验问题、排查思路和最终能跑通的写法整理出来给正在做抖音自动化脚本、登录态研究或者个人账号管理的你省点时间。文章不会只在原理上打转后面会直接给出可落的 Python 代码流程从生成二维码、轮询扫码状态到换 Token、访问业务接口每一步都解释清楚“为什么这么做”重点分析 Referer 和 Token 在校验链路里的位置。适合有一定 Python 基础、但没怎么摸过 Web 模拟登录的人也适合已经被抖音风控搞到头大的老手查漏补缺。1. 先把扫码登录的完整流程盘清楚很多人在模拟登录时一上来就找登录接口结果发现抖音这类产品根本没有简单的“用户名密码登录”。原因很简单扫码登录是依赖 App 端已完成登录态的账号授权服务端只需要验证二维码状态和后续的 Token 交换整个流程更接近 OAuth 授权而不是常规的账号密码认证。1.1 为什么选择扫码登录从自动化角度讲扫码登录比密码登录有天然优势不用处理滑块验证、短信验证码这类强交互验证只需要你手机上的抖音账号保持登录状态即可扫码授权后得到的登录态通常比密码登录更完整包括后续访问 Web 端接口所需的 Cookie 和 Token但代价是整个流程是“多步状态流转”每一步都可能被风控拦截。尤其是二维码的获取、状态轮询的频次、Token 交换时的请求头这三个环节是重灾区。我见过不少人卡在最后一步“登录成功但请求接口全 403”大概率不是登录本身失败而是后续请求的 Referer 和 Token 没处理好。1.2 一次完整登录的状态流转假设你想从零写一个抖音扫码登录脚本核心状态流转理论上长这样访问二维码接口拿到二维码内容通常是一个 URL和对应的唯一标识把二维码内容生成图片展示给用户扫码App 端扫码并确认登录后Web 端通过轮询二维码状态接口发现状态从“等待扫码”变成“已扫码待确认”再变成“已确认”拿到服务端返回的临时凭证用这个凭证换取正式的登录 Token之后的业务请求全部带上这个 Token 以及必要的请求头访问在第 4、5 步之间就是 Referer 和 Token 最容易出问题的地方。举个例子你用临时凭证去换正式 Token 时如果请求头里的 Referer 为空或者带了一个微信 URL服务端很大概率直接拒绝换到正式 Token 后如果访问某个用户主页时没有带 Referer服务端同样可能拒绝。1.3 Referer 和 Token 为什么是两道坎通俗解释一下这两者分别管什么Referer 表示“你从哪个页面跳过来的”服务端用它来判断请求来源是否正常。正常浏览器访问抖音页面时所有请求的 Referer 都是抖音自己的域名下的某个页面如果你的脚本请求 Referer 为空或不匹配服务端会认为这不是一次正常的浏览器行为。Token 表示“你是谁”是登录后的身份证。但 Token 不是只有一个有的 Token 是会话级有的是请求级有的需要定期刷新有的还需要跟其他参数绑定。如果用了错误的 Token 类型或者 Token 过期了服务端直接返回失败。这两道坎叠加在一起构成了很多脚本作者口中的“玄学报错”。实际上只要你把请求头、Token 传递链路、刷新机制都按正确的顺序处理大部分问题都可以稳定复现并解决。2. Referer 校验最常见的“请求来源”门禁抖音 Web 端几乎所有接口都会校验证 Referer这在 web 应用里不算稀奇但抖音做得比较严格。你可能会想Referer 不是请求头里的一个字段而已吗我随便带一个不行吗行但你带的不对照样被拒。2.1 服务端校验 Referer 的底层逻辑服务端收到一个请求时会检查 Referer 头是否在白名单域名内。对于抖音的接口来说正常请求的 Referer 基本都应该是https://www.douyin.com/这个域名或者它下面的具体页面路径。如果 Referer 缺失、域名对不上、甚至协议不是 HTTPS风控系统都可能直接拦截。这里有一个容易忽略的点抖音部分接口对 Referer 的校验不是只看主域名而是看完整路径。比如访问https://www.douyin.com/user/xxx这个用户主页的接口服务端可能要求 Referer 是https://www.douyin.com/user/xxx本身或者是首页/而不是随便某个路径都能过。我刚开始就吃过这个亏所有接口统一带https://www.douyin.com/结果部分接口正常部分接口仍然 403后来才反应过来需要按接口类型设置不同层级的 Referer。2.2 实操请求头里该怎么带 Referer在 Python 中用 requests 模拟登录时最简单的做法是建立一个统一的 headers 模板把浏览器常见字段都带上其中 Referer 根据业务接口动态调整。下面这个是我实测过的基础头import requests headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9, Referer: https://www.douyin.com/, Origin: https://www.douyin.com, Sec-Fetch-Site: same-origin, Sec-Fetch-Mode: cors, Sec-Fetch-Dest: empty, }需要注意“Origin”和“Referer”是两个不同字段。部分接口会同时校验这两个值Origin 通常要求是https://www.douyin.com而 Referer 是具体的跳转来源页。如果你只是做简单的请求模拟先统一用首页作为 Referer成功后逐个接口调整。2.3 不携带或带错 Referer 的典型表现Referer 校验失败常见的有这几种表现直接返回 403提示Forbidden或带verify相关字样返回 200但 JSON 里的status_code不是 0而是类似 219 这种风控码返回 HTML 而不是 JSON内容是一个空页面或验证页面如果你发现请求能通但数据字段是空的先别去调 Token优先检查是不是 Referer 和 Cookie 搭配不对。按我的习惯所有接口统一先验证一遍能不能用浏览器复制出来的完整请求头直接访问如果能再把请求头逐步精简定位到是哪几个字段在起关键作用。3. Token 校验扫码换 Token 与 Token 生命周期如果说 Referer 是门卫那 Token 就是你的工牌。二维码扫码成功后你并不是立刻拿到可以无限使用的“永久身份”而是先拿到一个临时凭证再通过这个凭证去换取正式 Token。这个过程中Token 的类型、有效期、换取的时机都会影响后面的请求。3.1 搞清几种 Token 之间的区别在抖音 Web 扫码登录流程里你可能会遇到这些和 Token 沾边的字段名称出现时机作用二维码标识获取二维码时返回标识当前二维码会话轮询时使用临时凭证扫码确认后返回证明“这个二维码被某个账号授权了”用于换取正式登录态会话 Token换取登录态后得到后续业务请求的身份凭证刷新 Token与登录 Token 一起下发用来自动续期避免登录态过期接口临时校验值部分关键接口动态生成由页面脚本计算和 Token 绑定很多人把“二维码标识”当成 Token 用轮询成功后拿着它直接访问业务接口当然会失败。正确逻辑是轮询确认后拿到临时凭证再通过另一个接口把它换成正式登录态这个过程通常还会返回 Cookie。3.2 实操扫码成功后如何换取并保存 Token扫码成功后服务端返回的内容通常包含一个凭证你需要在下一个请求里把它作为参数传过去。我这里用伪代码展示核心思路具体接口路径以你抓包到的实际版本为准# 假设扫码确认后status_response 里的结果是扫码成功 # 返回结果中有一个 code 或者 token 字段作为临时凭证 temp_token status_response.get(data, {}).get(code) # 下一步去换取正式登录态 exchange_url https://www.douyin.com/passport/web/account/info/ exchange_params { code: temp_token, # 有的版本还需要 service 和 type 参数按抓包补充 } session requests.Session() session.headers.update(headers) exchange_resp session.post(exchange_url, paramsexchange_params) login_data exchange_resp.json()登录成功后建议用session.cookies保存 Cookie同时把接口返回的 Token 字段存到本地文件或数据库。不要每次请求都重新扫码因为频繁扫码容易触发设备风控。3.3 Token 过期和刷新机制抖音的登录 Token 不是永久的过期时间可能在几小时到几天不等取决于账号活跃度、设备环境、是否频繁变更 IP 等。当你发现接口开始报“登录已过期”或 Token 相关错误时有两种处理方式重新扫码登录对脚本来说最直接但体验差用刷新 Token 续期前提是你在登录时保存了刷新 Token而且接口支持刷新关于刷新 Token我踩过最大的坑是刷新接口对请求头的要求和登录接口不一样尤其是 Referer 和 Content-Type。如果你在刷新时漏掉了某个请求头服务端可能返回一个模糊的“参数错误”让你误以为是 Token 本身过期排查半天才发现是头的问题。3.4 经典报错“token exchange failed”到底在说什么很多人会在日志里看到类似token exchange failed: token endpoint returned status 403的报错第一反应是“我 Token 写错了”。其实这个错误通常发生在 OAuth Token 交换阶段也就是你用临时凭证换正式 Token 的那一刻。报错的根因有几种临时凭证已经过期或者已经被使用过一次不能重复换取换取 Token 时带的参数名不对比如字段叫code你传了token环境或 IP 被判异常服务端拒绝本次交换请求头缺少关键字段比如 Referer 或验证参数遇到这类报错正确的排查顺序是先确认临时凭证是否新鲜再确认换 Token 的参数名和请求方法最后检查请求头。切记不要反复重试同一个凭证否则即使凭证本来没问题也可能因为频繁重试被临时风控。4. 完整实操Python 模拟扫码登录的核心环节前面把原理和容易踩的坑讲了一遍这部分给出一个能跑通思路的完整流程。由于抖音接口和风控策略会随版本调整下面的代码重点是结构和逻辑接口路径和参数请以你抓包的版本为准但整体流程在绝大多数版本里是通用的。4.1 准备工作与依赖环境要求很简单Python 3.8 以上requests 库qrcode 库用于把二维码内容生成图片Pillowqrcode 依赖安装命令pip install requests qrcode[pil]我建议在虚拟环境里安装避免污染系统 Python。用 venv 的话python -m venv douyin_env douyin_env\Scripts\activate # Windows source douyin_env/bin/activate # Linux/macOS4.2 生成二维码与状态轮询首先请求二维码接口拿到二维码内容。这里我以常见的接口结构为例import requests import qrcode import time import json session requests.Session() base_headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept: application/json, text/plain, */*, Referer: https://www.douyin.com/, Origin: https://www.douyin.com, } # 步骤1获取二维码 qr_url https://www.douyin.com/passport/web/get_qr_code/ qr_resp session.get(qr_url, headersbase_headers) qr_data qr_resp.json() # 实际的返回结构根据版本可能不一样 qr_id qr_data[data][qr_id] qr_content qr_data[data][url] # 生成二维码图片 img qrcode.make(qr_content) img.save(douyin_login_qr.png) print(二维码已生成请使用抖音App扫描 douyin_login_qr.png)生成二维码后用qr_id轮询状态。轮询间隔一般建议 1.5 秒到 3 秒一次太频繁容易触发风控太慢用户体验差。# 步骤2轮询扫码状态 status_url https://www.douyin.com/passport/web/check_qr_connect/ poll_headers dict(base_headers) poll_headers[Referer] https://www.douyin.com/ # 保证和扫码页一致 for i in range(60): status_resp session.get( status_url, params{qr_id: qr_id, tw: int(time.time() * 1000)}, headerspoll_headers, ) status_json status_resp.json() status status_json.get(data, {}).get(status) # 不同版本状态码不同通常 1等待扫码 2已扫码 3已确认 if status in (2, 3): print(扫码成功当前状态:, status) # 保存返回值里面可能包含临时凭证 with open(qr_status.json, w, encodingutf-8) as f: json.dump(status_json, f, ensure_asciiFalse) break time.sleep(2)这里有几个实际经验tw参数是时间戳有些版本必须带有些版本无所谓统一带上更保险轮询超过 60 次还没回应建议重新生成二维码不要把整个 status_json 打印到终端里面有些字段很长容易把控制台刷爆4.3 扫码确认后的 Token 处理扫码成功后的结果需要仔细看。有的版本会在data里直接返回一个重定向地址里面带着code或token参数有的版本需要你再调一个接口换取登录态。这里按最常见的流程演示# 步骤3从轮询结果中取出临时凭证并换 Token poll_data json.load(open(qr_status.json, encodingutf-8)) temp_code poll_data.get(data, {}).get(code) if not temp_code: # 有的接口会返回一个 url凭证在 url 参数里 redirect_url poll_data.get(data, {}).get(url, ) from urllib.parse import urlparse, parse_qs query_params parse_qs(urlparse(redirect_url).query) temp_code query_params.get(code, [])[0] # 使用临时凭证换取 Cookie 和 Token exchange_url https://www.douyin.com/passport/web/login/ exchange_params { code: temp_code, service: https://www.douyin.com/, type: qrcode_login, } exchange_resp session.post(exchange_url, paramsexchange_params, headersbase_headers) exchange_json exchange_resp.json() print(登录结果, exchange_json.get(status_code))登录成功后session 对象会持有 Cookie。为了持久化可以把 Cookie 导出cookies session.cookies.get_dict() with open(douyin_cookies.json, w, encodingutf-8) as f: json.dump(cookies, f, ensure_asciiFalse, indent2)下次登录时直接加载 Cookie避免频繁扫码。但注意 Cookie 有有效期失效后还是要重新扫码。4.4 携带 Token 访问业务接口登录态搞定之后访问业务接口仍不能掉以轻心。拿访问用户主页举例请求头要更完整尤其注意 Referer 要指向用户主页本身。# 步骤4携带登录态访问业务接口 user_url https://www.douyin.com/aweme/v1/web/user/profile/other/ def get_user_profile(sec_uid): headers { User-Agent: base_headers[User-Agent], Accept: application/json, text/plain, */*, Referer: fhttps://www.douyin.com/user/{sec_uid}?from_tab_namemain, Origin: https://www.douyin.com, } params { sec_user_id: sec_uid, publish_video_strategy_type: 2, } resp session.get(user_url, paramsparams, headersheaders) return resp.json()这里最关键的就是Referer带上了sec_uid对应的主页地址。你可以理解为服务端认为你是从用户主页发起的请求而不是脚本凭空构造的风控压力会小很多。5. 常见问题与排查技巧实录这一部分是我自己反复踩坑后整理出的速查思路希望能帮你少走弯路。我不打算写一堆“可能原因”而是把最常见的现象、根因和处理方法直接列成表方便你对照排查。5.1 从报错信息反查是哪一层校验挂了拿到一个报错时不要盲目改代码先看报错来自哪个阶段生成二维码阶段报错大概率是请求头不完整或接口路径不对优先检查是否缺少基础头轮询扫码状态阶段报错检查二维码是否过期、轮询频率是否太高换取 Token 阶段报错优先检查临时凭证是否有效、参数名是否正确、Referer 是否匹配业务接口请求报错把关注点放在 Token 生命周期和 Referer 是否跟随具体页面一个很实用的技巧把浏览器的开发者工具打开手动扫码登录一次把所有相关请求的请求头完整复制下来用 Python 原样发一遍。如果原样请求能通再逐个删减字段很快就能定位到是哪几个头在起作用。这个方法比我对着文档猜效率高得多。5.2 现象、原因与处理对照表我把实践中最高频的几个现象整理如下现象常见原因处理思路返回 403页面被拦截Referer 缺失或域名不匹配检查请求头的 Referer 是否指向抖音域名及相关页面返回 JSON 但 status_code 非 0请求过于频繁或缺少参数降低请求频率补充完整参数检查是否需要签名参数轮询一直是“等待扫码”二维码过期或二维码生成接口被风控重新生成二维码控制生成频率扫码确认后没有回调App 端风控或二维码会话失效重新生成二维码用更稳定的网络环境重试换 Token 报 token exchange failed临时凭证重复使用或已过期换新凭证检查换取接口的参数和请求头接口提示登录过期Token 生命周期结束使用刷新 Token 续期或重新扫码登录需要提醒的是抖音的返回值结构在不同版本之间差异很大上面的“status_code 非 0”只是一个泛指具体判断依据要看当时接口的响应结构。5.3 我在多次复现中总结的避坑经验第一统一走 Session。不要每个请求都新建一个 requests.Session登录产生的 Cookie 需要在整个会话周期内保持一致否则容易出现“这一步登录成功下一步又变成游客”的诡异情况。第二不要一股脑把浏览器所有请求头都搬过来。有人为了“保险”把所有请求头全部设置一遍包括Accept-Encoding: gzip结果响应内容被压缩后忘了解开反而拿到一堆乱码。优先保留基础头缺什么补什么。第三Token 不要到处乱放。我见过有人把 Token 写在日志里结果日志被上传到公开服务等于把账号凭证泄露了。建议 Token 和 Cookie 直接存到本地私有文件不要在公共日志中出现完整值。第四注意重试策略。扫码轮询失败后不要立刻重试最好加一个退避逻辑第一次失败等 2 秒连续失败几次后等更长的时间。这样既对服务端友好也能降低风控误伤的概率。第五不要尝试模拟真实用户去频繁刷接口。做技术验证和学习是一回事批量高频请求是另一回事。如果你确实有批量数据需求请在合规前提下评估是否需要官方 API 或其他授权途径而不是在模拟登录这个方向上压榨到极限。我个人在实际操作中最深的体会是模拟抖音扫码登录真正难的永远是“如何在正确的时间、用正确的身份、以正确的方式请求接口”。Referer 和 Token 只是这条链路上最显眼的两块石头把它们处理顺了后面的路就会顺畅很多。最后再分享一个小技巧每当你觉得“这次报错也太玄学”的时候先打开浏览器手动操作一遍完整流程把所有请求头截图保存再对比脚本里的请求头百分之八十的问题都能在这一步找到答案。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。