☰
Python实战:用TaoToken统一通道解析快手个人主页无水印视频下载
2026/10/5 17:24:58 网站建设 项目流程

1. 快手个人主页视频抓取的真实痛点与场景拆解

很多人第一次写快手个人主页视频下载脚本,卡住的地方往往不是 Python 语法,而是三件事:接口找不准、翻页游标接不上、请求凭证散落各处。我试过把 Cookie、userId、pcursor 全写死在代码里,结果换一个博主就要改一次文件,跑批量任务时更是到处复制粘贴,维护成本极高。

这篇内容聚焦一个具体场景:给定一个快手博主个人主页链接,用 Python 把该主页下的视频批量解析出无水印地址并保存到本地,同时把调用凭证统一交给 TaoToken 的 API 通道管理。适合已经会写基础 requests 请求、想跑通「主页链接 → 视频列表 → 无水印文件」完整链路的同学。

核心检索词先明确:快手个人主页视频无水印下载,本质是调用快手 Web 端的 GraphQL 接口visionProfilePhotoList,从返回的feeds数组里取出photo.photoUrl字段,这个字段就是无水印视频直链。整个链路分四步:定位接口、构造请求体、翻页拿游标、下载落盘。

为什么要把凭证管理单独拎出来?因为快手接口依赖登录态 Cookie,而 Cookie 会过期。如果你同时还在用其他模型或 API 服务,凭证一多就容易乱。TaoToken 提供统一 Key 和 API 通道,把这类调用凭证集中管理,脚本里只读环境变量,不硬编码敏感串,换机器、换账号都省事。

下面按「问题场景 → 凭证前置 → 可复制配置 → 验证请求 → 报错排查 → 收尾」的顺序展开,每一步都给能直接跑的代码和参数说明。你跟着做,单页视频批量下载在本地稳定跑通没问题。

先看接口定位。打开目标博主主页,按 F12 进 Network,筛选graphql,下拉页面触发加载,你会看到一个 POST 请求发往https://www.kuaishou.com/graphql。点开 Payload,里面是 JSON 结构,关键字段有四个:operationName固定为visionProfilePhotoList,query是一大段 GraphQL 查询语句(固定),variables.page固定为profile,variables.userId是博主唯一 ID,variables.pcursor是翻页游标。

userId 最好拿,主页 URL 里/profile/后面那串就是,比如3xteif7iwrqr7iy。pcursor 是重点:第一页传空字符串,接口返回的data.visionProfilePhotoList.pcursor就是下一页要用的游标。换句话说,翻页是「上一页的响应喂给下一页的请求」,串行依赖,不能并行。

请求头里必须带content-type: application/json,因为这是 POST JSON,不是表单。Cookie 必须是你自己登录后的完整 Cookie,否则接口返回空数据或直接 401。User-Agent 建议用桌面 Chrome 的,移动端 UA 有时会触发不同返回结构。

把这些理清楚,代码就好写了。下一节先讲 TaoToken 侧要准备什么,再进入可复制配置。

2. TaoToken 统一通道前置准备与凭证管理

在写快手抓取脚本之前,先把凭证管理这件事做干净。快手接口靠 Cookie,而如果你后续还要接模型对话、代码补全或其他 API,凭证会越来越多。TaoToken 的作用是把这些调用凭证收拢到一个统一通道里,脚本通过环境变量读取,不把敏感信息写进代码仓库。

你需要准备两样东西:一个 TaoToken 的 API Key,以及对应的接入地址。API 基址是https://taotoken.net/api,注意这个地址不带任何查询参数,直接作为请求前缀使用。Key 的获取入口在控制台的 API Keys 页面,登录后新建一个即可。

拿到 Key 之后,不要直接写进.py文件。推荐做法是写进系统环境变量,或者放在项目根目录的.env文件里,用python-dotenv加载。这样做的直接好处是:代码可以提交到 Git,凭证不会泄露;换机器时只改环境变量,不动代码。

具体操作:在项目根目录建一个.env文件,内容如下。

TAOTOKEN_API_KEY=sk-你的实际key TAOTOKEN_BASE_URL=https://taotoken.net/api KUAISHOU_COOKIE=你的快手登录Cookie KUAISHOU_USER_ID=3xteif7iwrqr7iy

然后在 Python 里这样读取。

import os from dotenv import load_dotenv load_dotenv() TAOTOKEN_API_KEY = os.getenv("TAOTOKEN_API_KEY") TAOTOKEN_BASE_URL = os.getenv("TAOTOKEN_BASE_URL") KUAISHOU_COOKIE = os.getenv("KUAISHOU_COOKIE") KUAISHOU_USER_ID = os.getenv("KUAISHOU_USER_ID") assert TAOTOKEN_API_KEY, "缺少 TAOTOKEN_API_KEY,请检查 .env" assert KUAISHOU_COOKIE, "缺少 KUAISHOU_COOKIE,请检查 .env"

这里有个细节:快手 Cookie 里包含kuaishou.server.web_st这种长串,直接放.env时不要加引号,否则读出来会带引号导致请求头异常。如果 Cookie 里有换行,先合并成一行再放。

TaoToken 的 Key 在这里的角色是「统一凭证入口」。你可以在同一个.env里管理多个服务的 Key,脚本按需读取。比如后面要调模型对话验证通道是否通,就用同一个TAOTOKEN_API_KEY去请求https://taotoken.net/api下的对话接口。这样一套凭证管所有,不用每个服务单独记一套。

如果你打算长期跑批量任务,建议再准备一个 Coding Plan,把编码类调用也纳入统一管理。入口在 Coding Plan 页面,适合需要持续调用、跑 Agent 任务的场景。快手抓取本身不消耗模型额度,但如果你在脚本里加了自动生成文件名、自动打标签这类模型调用,就可以走同一个 Key。

凭证准备好后,先做一次连通性验证,确认 Key 和基址没问题。用下面这段最小请求测试。

import requests resp = requests.get( f"{TAOTOKEN_BASE_URL}/models", headers={"Authorization": f"Bearer {TAOTOKEN_API_KEY}"}, timeout=10, ) print(resp.status_code) print(resp.text[:300])

如果返回 200 且能看到模型列表,说明通道正常。如果返回 401,检查 Key 是否复制完整、有没有多余空格。这一步过了,再进入快手接口的配置。

需要提醒的是,快手抓取和 TaoToken 通道是两条独立的链路:前者用 Cookie 访问快手,后者用 Key 访问 TaoToken。把两者放同一个.env只是为了集中管理,不要混淆用途。下一节给出完整的可复制配置。

3. 可复制配置:请求头、GraphQL 请求体与下载脚本

这一节是全文的核心,给出能直接跑的完整配置。先看请求头配置,再给 GraphQL 请求体的 JSON 结构,最后是下载脚本。所有敏感值都从环境变量读,路径和字段名与上一节保持一致。

请求头部分,关键是content-type和Cookie。content-type必须是application/json,因为快手这个接口收的是 POST JSON。Cookie从环境变量读,不要硬编码。

import os import json import time import requests from dotenv import load_dotenv load_dotenv() KUAISHOU_COOKIE = os.getenv("KUAISHOU_COOKIE") KUAISHOU_USER_ID = os.getenv("KUAISHOU_USER_ID") 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": "*/*", "accept-language": "zh-CN,zh;q=0.9", "content-type": "application/json", "Cookie": KUAISHOU_COOKIE, }

GraphQL 请求体里,operationName和query是固定值,variables里的page固定为profile,userId从环境变量读,pcursor由翻页逻辑传入。把query单独抽成常量,避免每次拼字符串出错。

GRAPHQL_QUERY = """ fragment photoContent on PhotoEntity { id duration caption originCaption likeCount viewCount commentCount realLikeCount coverUrl photoUrl photoH265Url manifest manifestH265 videoResource coverUrls { url __typename } timestamp expTag animatedCoverUrl distance videoRatio liked stereoType profileUserTopPhoto musicBlocked __typename } fragment feedContent on Feed { type author { id name headerUrl following headerUrls { url __typename } __typename } photo { ...photoContent __typename } canAddComment llsid status currentPcursor tags { type name __typename } __typename } query visionProfilePhotoList($pcursor: String, $userId: String, $page: String, $webPageArea: String) { visionProfilePhotoList(pcursor: $pcursor, userId: $userId, page: $page, webPageArea: $webPageArea) { result llsid webPageArea feeds { ...feedContent __typename } hostName pcursor __typename } } """ def build_payload(user_id: str, pcursor: str) -> dict: return { "operationName": "visionProfilePhotoList", "variables": { "page": "profile", "pcursor": pcursor, "userId": user_id, }, "query": GRAPHQL_QUERY, }

注意pcursor第一页传空字符串"",不要传None,否则 JSON 序列化后变成null,接口可能不认。翻页时把上一页返回的pcursor原样传入。

下载脚本部分,核心逻辑是:请求接口 → 解析feeds→ 取photo.photoUrl→ 下载保存 → 记录下一页游标。加time.sleep控制频率,避免请求过密。

import os SAVE_DIR = os.path.join(os.getcwd(), "kuaishou_videos") os.makedirs(SAVE_DIR, exist_ok=True) def fetch_page(user_id: str, pcursor: str) -> dict: url = "https://www.kuaishou.com/graphql" payload = build_payload(user_id, pcursor) resp = requests.post(url, headers=HEADERS, json=payload, timeout=15) resp.raise_for_status() return resp.json() def download_video(video_url: str, filepath: str) -> bool: try: r = requests.get(video_url, headers=HEADERS, timeout=30, stream=True) r.raise_for_status() with open(filepath, "wb") as f: for chunk in r.iter_content(chunk_size=8192): if chunk: f.write(chunk) return True except Exception as e: print(f"下载失败 {video_url} -> {e}") return False def crawl_profile(user_id: str, max_pages: int = 5): pcursor = "" for page in range(max_pages): print(f"当前第 {page + 1} 页,pcursor={pcursor!r}") data = fetch_page(user_id, pcursor) node = data.get("data", {}).get("visionProfilePhotoList") if not node: print("接口返回为空,可能 Cookie 失效或 userId 错误") break feeds = node.get("feeds") or [] if not feeds: print("本页无视频,结束") break for idx, feed in enumerate(feeds, start=1): photo = feed.get("photo") or {} video_url = photo.get("photoUrl") if not video_url: continue filename = f"page{page + 1}_{idx}.mp4" filepath = os.path.join(SAVE_DIR, filename) ok = download_video(video_url, filepath) print(f"{filename} {'保存完成' if ok else '保存失败'}") pcursor = node.get("pcursor") or "" if not pcursor: print("没有下一页游标,结束") break time.sleep(2) if __name__ == "__main__": crawl_profile(KUAISHOU_USER_ID, max_pages=5)

这段代码里,SAVE_DIR用os.getcwd()拼出来,避免写死 Windows 路径导致换系统跑不了。文件名用page{页码}_{序号}.mp4,方便对照。stream=True配合分块写入,大文件下载不会一次性占满内存。

如果你想把凭证管理再规范一点,可以把HEADERS里的Cookie换成从 TaoToken 通道拉取的动态值。不过快手 Cookie 和 TaoToken Key 是两套体系,这里保持从.env读即可。TaoToken 的 Key 主要用于你脚本里其他模型调用,比如自动生成视频标题。

配置给完了,下一节验证请求是否真的跑通。

4. 验证请求与成功结果:从响应到落盘

配置写好后,先别急着跑全量。分三步验证:先验证接口能返回数据,再验证photoUrl能取到,最后验证文件能落盘。每一步都有明确的成功标志。

第一步,单独调fetch_page,打印返回结构。用下面这段最小验证代码。

if __name__ == "__main__": data = fetch_page(KUAISHOU_USER_ID, "") node = data.get("data", {}).get("visionProfilePhotoList") print("result:", node.get("result") if node else None) print("pcursor:", node.get("pcursor") if node else None) print("feeds 数量:", len(node.get("feeds") or []) if node else 0)

成功的话,你会看到result为1(表示成功),pcursor是一串数字字符串,feeds 数量大于 0。如果result不是 1,或者feeds为空,先查 Cookie 和 userId。

第二步,从feeds里取第一条,打印photoUrl。

if __name__ == "__main__": data = fetch_page(KUAISHOU_USER_ID, "") feeds = data["data"]["visionProfilePhotoList"]["feeds"] first = feeds[0]["photo"] print("caption:", first.get("caption")) print("photoUrl:", first.get("photoUrl"))

photoUrl应该是一个以https://开头的直链,通常带txmov2.a.kwimgs.com之类的域名。这个链接就是无水印地址。你可以把它复制到浏览器直接打开,能播放就说明解析正确。

第三步,跑完整下载,检查本地文件夹。运行crawl_profile后,kuaishou_videos目录下会出现page1_1.mp4、page1_2.mp4等文件。用播放器打开,确认画面无水印、音画同步。

实测下来,单页通常返回 20 条左右的视频,翻页间隔 2 秒比较稳。如果一次跑 5 页,大概 100 个视频,耗时取决于网速和文件大小。下载过程中如果某个链接失败,脚本会打印失败信息并继续,不会中断整批任务。

这里有个验证技巧:把photoUrl和页面上的视频对比。快手网页端播放的视频有时带水印,但photoUrl字段返回的是原始无水印版本。如果你拿到的链接播放出来还有水印,检查是不是取错了字段,比如误取了coverUrl或manifest里的地址。

成功跑通后,你可以把max_pages调大,或者把userId换成其他博主。凭证都在.env里,换博主只改KUAISHOU_USER_ID一行。

如果你在脚本里加了 TaoToken 的模型调用,比如用模型给视频自动生成描述,验证方式类似:先单独调一次模型接口,确认返回正常,再嵌入主流程。TaoToken 的模型对话入口在模型对话页面,可以先用网页版试一条,确认 Key 有效。

验证通过后,下一节讲常见报错怎么排查。

5. 本篇常见错误排查:401、local proxy failed、reading choices、OAuth

跑快手抓取脚本时,报错集中在几类。下面按真实报错信息逐条对照,给出原因和修法。

第一类:401 Unauthorized或接口返回result: 2。这通常是 Cookie 失效。快手登录态有有效期,过期后接口不再返回数据。修法是重新登录快手网页版,F12 复制最新 Cookie,更新.env里的KUAISHOU_COOKIE。注意复制时包含kuaishou.server.web_st和kuaishou.server.web_ph这两个关键字段,缺一个都可能失败。

第二类:local proxy failed或连接超时。这类报错通常和网络环境有关。先确认你的网络能正常访问快手网页版,浏览器能打开主页,脚本才可能通。如果浏览器能开但脚本超时,检查requests是否走了系统代理,可以在请求里加proxies={"http": None, "https": None}显式禁用,或者检查环境变量HTTP_PROXY。另外,timeout设太短也会误报,建议 15 秒以上。

第三类:reading 'choices'或KeyError: 'choices'。这个报错一般出现在你调模型接口时,响应结构里没有choices字段。原因可能是请求体格式不对,或者模型 ID 写错。如果你用 TaoToken 通道调模型,先确认请求体里model字段是有效值,再确认messages是数组格式。用下面这段最小请求排查。

import requests, os from dotenv import load_dotenv load_dotenv() resp = requests.post( f"{os.getenv('TAOTOKEN_BASE_URL')}/chat/completions", headers={ "Authorization": f"Bearer {os.getenv('TAOTOKEN_API_KEY')}", "Content-Type": "application/json", }, json={ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "ping"}], }, timeout=20, ) print(resp.status_code) print(resp.text[:500])

如果返回 200 且能看到choices,说明通道正常。如果返回 401,检查 Key。如果返回 404,检查model字段。

第四类:OAuth相关报错,比如invalid_grant或token expired。这类一般出现在你用 OAuth 方式接入某些服务时。修法是重新走授权流程,拿新的 token。如果你用的是 TaoToken 的 Key 方式,不涉及 OAuth,遇到这类报错先确认是不是请求发到了错误的地址。

第五类:json.decoder.JSONDecodeError。快手接口返回的不是 JSON,通常是 HTML 错误页。原因可能是 Cookie 失效被重定向到登录页,或者请求头content-type写成了application/x-www-form-urlencoded。检查HEADERS里content-type是否为application/json,以及requests.post用的是json=payload而不是data=payload。

第六类:下载的视频文件大小为 0 或几 KB。这通常是photoUrl取到了但下载被拒。检查下载请求是否带了Cookie和User-Agent,有些 CDN 会校验 Referer。可以在下载请求头里加Referer: https://www.kuaishou.com/。

排查顺序建议:先看 HTTP 状态码,再看响应体前 300 字符,最后看本地文件。大部分问题集中在 Cookie 和请求头两处。把这两处固定好,脚本稳定性会高很多。

如果你在脚本里同时用了 TaoToken 通道和快手接口,报错时先区分是哪条链路。快手报错看 Cookie,TaoToken 报错看 Key 和基址。两条链路分开验证,定位更快。

6. 凭证统一管理与后续扩展

脚本跑通后,最后收一下凭证管理这条线。快手抓取依赖 Cookie,模型调用依赖 TaoToken Key,两者都放在.env里,通过环境变量读取。这样做的好处是代码干净、可迁移、不泄露敏感信息。

如果你后续要扩展功能,比如批量抓多个博主、自动去重、自动上传,凭证管理会更重要。建议把.env按服务分组,快手相关的前缀KUAISHOU_,TaoToken 相关的前缀TAOTOKEN_,避免命名冲突。

需要长期跑编码类任务或 Agent 的话,可以了解 Coding Plan,把持续调用的额度统一管理。入口在 Coding Plan 页面。如果只是偶尔验证模型是否可用,用模型对话页面手动试一条更直接。API Key 的创建和管理在 API Keys 页面,接入细节看接入文档。

快手这边的扩展方向:把userId列表化,循环调用crawl_profile;把已下载的视频 ID 记录到本地 JSON,避免重复下载;把pcursor持久化,支持断点续爬。这些都不难,核心链路已经跑通了。

最后提醒一句:抓取频率别太高,翻页间隔保持 2 秒以上,下载也适当限速。稳定跑通比一次抓完更重要。凭证放.env,代码里只读环境变量,这套习惯养成了,后面接什么服务都省心。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询