TikTok 是网络上最大的公开短视频数据来源之一,它呈现的趋势、话题标签和互动数字对市场研究、内容策略和趋势追踪都有切实价值。与此同时,它也是以编程方式读取难度较高的平台之一:页面通过 JavaScript 在客户端渲染,平台会快速识别自动化流量,对搜索或主页链接发出的普通 HTTP 请求,通常只会返回一个几乎不含任何数据的空壳页面,而非你在浏览器中看到的内容。
本指南将向你展示如何以真正可行的方式使用 Python 抓取 TikTok 公开数据,同时严格限定在公开聚合信息的范围之内。本文涵盖的内容均为公开搜索结果、公开话题标签信息流和公开主页视频:视频说明文字、点赞数、评论数、分享数、视频链接和发布日期。不涉及登录后才能访问的内容、私密账户,也不涉及个人身份数据。在将代码应用于实际场景之前,请务必阅读文末的合法性章节。对于生产级应用,建议优先使用 TikTok 的官方 API。
你将构建什么
一个简短的 Python 脚本,接受 TikTok 的公开搜索或话题标签 URL,通过 Crawling API 使用 JavaScript token 获取完整渲染后的页面,并从每个视频卡片中解析若干公开聚合字段:
- Caption 视频卡片上显示的公开文字。
- Like、comment 和 share 数 卡片上显示的聚合互动数字,而非背后的具体用户。
- Video URL 每个视频的公开固定链接。
- Posted date 卡片上显示的上传日期。
- Hashtags 每个视频所附带的公开标签。
注意这里有意缺失的内容:没有粉丝列表、没有评论者身份、没有私密账户内容、没有联系方式。这些属于个人身份信息,超出本文的讨论范围,是有意为之的边界。用户名仅作为公开视频的附属背景,而非用于扩充的档案。
为什么普通请求在 TikTok 上会失败
用普通 HTTP 客户端请求 TikTok 的搜索或话题标签 URL,你会得到一个技术上成功却毫无用处的响应。返回的内容是一个 JavaScript 外壳:真实内容、视频卡片、说明文字和数据,只有在页面脚本在浏览器中运行并从内部端点获取数据后才会出现。此外,TikTok 还会快速标记自动化流量。数据中心 IP 段、缺失浏览器行为的请求模式,在任何有价值的内容加载之前就会遭到质询或被限速。
因此,一个能正常工作的 TikTok 爬虫在同一次请求中需要具备两点:一个真正渲染页面的浏览器,以及一个平台认为是普通访客的 IP 地址。你可以用无头浏览器加轮换住宅代理池自行搭建,但维护这套系统才是主要工作量。Crawling API 将两者整合为一次调用:你使用 JavaScript token 发送 URL,它在受信任的住宅 IP 后面渲染页面,并返回你可以解析的完整 HTML。如需更多背景知识,请参阅我们的如何抓取 JavaScript 网站指南。
Crawlbase 提供两种 token 类型。普通 token 获取静态 HTML;JavaScript(JS)token 会先在真实浏览器中渲染页面。TikTok 是客户端渲染的,因此这里需要 JS token。普通 token 只会返回与普通 fetch 一样的空壳页面,没有任何可解析的有用内容。
前提条件
在开始之前,需要准备以下几样东西,都不会花太长时间。
基础 Python 知识。 你应该能够运行脚本并使用 pip 安装包。如果你对 HTML 解析不熟悉,我们的如何在 Python 中使用 BeautifulSoup 入门文章涵盖了提取的方法。
Python 3.8 或更高版本。 使用 python --version 和 pip --version 确认版本。如果尚未安装,请从 python.org 安装。
Crawlbase 账户和 JS token。 注册后,打开控制台,复制你的 JavaScript(JS)token。前 1,000 次请求免费,无需信用卡。请像密码一样保管 token:它用于验证你的请求,不要提交到版本控制系统中。
设置项目
创建一个独立的虚拟环境,然后安装爬虫所需的库。
python --version python -m venv tiktok_env source tiktok_env/bin/activate pip install crawlbase beautifulsoup4
在 Windows 上,用 tiktok_env\Scripts\activate 代替 source 那行命令。两个依赖完成主要工作:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML,让你可以通过选择器提取各个字段。
步骤 1:获取渲染后的页面
首先获取完整渲染的页面。导入 CrawlingAPI,使用你的 JS token 初始化它,并请求一个公开的搜索或话题标签 URL。TikTok 异步加载内容,因此传入 ajax_wait 和 page_wait,让页面稳定后再进行捕获。在解析之前检查状态,可以让失败情况清晰可见而非悄无声息。
from crawlbase import CrawlingAPI import urllib.parse api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) options = { "ajax_wait": "true", "page_wait": 10000, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", } def crawl(page_url): response = api.get(page_url, options) if response["headers"]["pc_status"] == "200": return response["body"].decode("utf-8") print(f"Request failed. Crawlbase status: {response['headers']['pc_status']}") return None if __name__ == "__main__": query = urllib.parse.quote("cooking recipes") url = f"https://www.tiktok.com/search/video?q={query}" html = crawl(url) print(html[:500] if html else "No HTML returned")
等待选项对于客户端渲染的目标至关重要。ajax_wait 告知 API 等待异步内容加载完成,page_wait 在加载后再固定等待若干毫秒,让后渲染的卡片在页面被捕获之前出现。TikTok 的起始等待时间设为 10 秒较为合理;如果卡片返回为空,可以适当延长。示例选择的是话题(烹饪食谱),正是因为它是无个人信息的公开内容。运行脚本后,你应该能看到真实的页面标记,这证明渲染功能在你编写任何选择器之前已经正常工作。
TikTok 需要在一次调用中同时具备渲染页面和受信任 IP 的能力。Crawling API 接受 JS token,在真实浏览器中运行页面以便 ajax_wait 和 page_wait 有内容可等待,在服务器端轮换住宅 IP,并返回完整 HTML,让你无需自行运行无头浏览器集群和代理池。先在免费层级用公开搜索词试用。
步骤 2:在搜索列表中定位视频卡片
拿到渲染后的 HTML 后,将其加载到 BeautifulSoup 中,并定位搜索列表,即包含页面上所有结果的容器。TikTok 用 data-e2e 属性标记关键元素,这比其层层嵌套、频繁改名的 CSS 类稳定得多。搜索结果位于 div[data-e2e='search_video-item-list'] 下,每个直接子元素对应一个视频卡片。
from bs4 import BeautifulSoup def find_video_cards(html): soup = BeautifulSoup(html, "html.parser") return soup.select("div[data-e2e='search_video-item-list'] > div")
这将返回一个卡片元素列表。每个卡片是自包含的:说明文字、互动数量、视频链接、发布日期和话题标签都在其内部,因此后续解析逐一对每张卡片进行操作。
步骤 3:解析公开视频字段
从每张卡片中提取公开聚合字段。说明文字位于 data-e2e='search-card-video-caption' 下,视频链接位于 data-e2e='search_video-item' 下,发布日期位于 class 包含 DivTimeTag 的元素中,互动数量位于 data-e2e='search-card-like-container' 下。下方每个选择器都做了防护处理,在元素缺失时返回 None 而非崩溃,因为 TikTok 并非每张卡片都渲染所有字段。
def text_of(card, selector): el = card.select_one(selector) return el.text.strip() if el else None def scrape_video_details(card): link = card.select_one("div[data-e2e='search_video-item'] a") return { "caption": text_of(card, "div[data-e2e='search-card-video-caption'] > div > span"), "video_url": link["href"].strip() if link and link.has_attr("href") else None, "posted_date": text_of(card, "div[class*='DivTimeTag']"), "like_count": text_of(card, "div[data-e2e='search-card-like-container'] > strong"), }
这里只提取聚合的非个人字段:说明文字、公开视频 URL、发布日期和公开点赞数。点赞数、评论数和分享数都是数字;背后的具体用户不在采集范围之内。我们不读取单条评论,也不读取是谁点赞了该视频,这种克制正是让工作站得住脚的关键。
TikTok 会在不通知的情况下修改标记,这正是代码依赖 data-e2e 属性而非脆弱嵌套类的原因。当某个字段返回 None 时,在浏览器开发者工具中重新检查实时页面并更新选择器。对于任何生产级爬虫,定期维护选择器都是正常操作,而非出了问题的迹象。
步骤 4:解析话题标签
话题标签是描述视频主题的公开标签,是页面上最有价值的聚合趋势信号。它们位于 data-e2e='search-common-link' 下各自的锚点元素中,将每张卡片的话题标签收集到一个列表中。
def scrape_hashtags(card): tags = card.select("a[data-e2e='search-common-link'] > strong") return {"hashtags": [t.text.strip() for t in tags]}
对搜索结果或话题标签信息流中的话题标签进行聚合,可以得出话题分布,而无需触及任何可识别的个人。这正是本方法所适用的分析类型:数量、趋势和共现关系,而非个人档案。
步骤 5:整合完整脚本
现在将获取和解析整合为一个可运行的脚本。它获取一个公开搜索页面,遍历每张视频卡片,将视频字段与话题标签合并,并以整洁的 JSON 格式输出列表。
from crawlbase import CrawlingAPI from bs4 import BeautifulSoup import urllib.parse import json api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) options = { "ajax_wait": "true", "page_wait": 10000, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", } def crawl(page_url): response = api.get(page_url, options) if response["headers"]["pc_status"] == "200": return response["body"].decode("utf-8") print(f"Request failed. Crawlbase status: {response['headers']['pc_status']}") return None def text_of(card, selector): el = card.select_one(selector) return el.text.strip() if el else None def scrape_video_details(card): link = card.select_one("div[data-e2e='search_video-item'] a") return { "caption": text_of(card, "div[data-e2e='search-card-video-caption'] > div > span"), "video_url": link["href"].strip() if link and link.has_attr("href") else None, "posted_date": text_of(card, "div[class*='DivTimeTag']"), "like_count": text_of(card, "div[data-e2e='search-card-like-container'] > strong"), } def scrape_hashtags(card): tags = card.select("a[data-e2e='search-common-link'] > strong") return {"hashtags": [t.text.strip() for t in tags]} def scrape_search(url): html = crawl(url) if not html: return [] soup = BeautifulSoup(html, "html.parser") cards = soup.select("div[data-e2e='search_video-item-list'] > div") results = [] for card in cards: video = scrape_video_details(card) video.update(scrape_hashtags(card)) results.append(video) return results def main(): query = urllib.parse.quote("cooking recipes") url = f"https://www.tiktok.com/search/video?q={query}" results = scrape_search(url) print(json.dumps(results, indent=2, ensure_ascii=False)) if __name__ == "__main__": main()
同一脚本同样适用于公开话题标签信息流:将搜索 URL 替换为话题标签 URL(例如 https://www.tiktok.com/tag/cooking),如果页面结构不同,相应调整卡片选择器即可。输出的结构保持一致,这正是将解析逻辑放在单张卡片级别的意义所在。
输出结果示例
运行完整脚本后,你将得到每个视频公开字段的整洁记录,可直接写入 JSON、CSV 或数据库。
[ { "caption": "Crispy potato snacks recipe", "video_url": "https://www.tiktok.com/@artofcooking.example/video/7344763014572182789", "posted_date": "3-10", "like_count": "8.7M", "hashtags": ["#potatosnacks", "#snacks", "#foryou", "#fyp"] }, { "caption": "Crispy potato bread rolls", "video_url": "https://www.tiktok.com/@recipesoftheworld.example/video/7155082128521186587", "posted_date": "2022-10-16", "like_count": "6.6M", "hashtags": ["#breadroll", "#snacks", "#foodie", "#streetfood"] } ]
数量以 8.7M 这样的显示字符串形式返回,而非原始整数,因为这正是 TikTok 渲染的格式。如果需要将其转为数字进行聚合计算,可在存储前进行小步骤的后处理(展开 K 和 M 后缀)。
处理分页
TikTok 使用基于滚动的分页:新卡片在用户向下滚动时加载,而非通过编号页面翻页。Crawling API 可以为你模拟滚动。将 scroll 设为 true,并可选地设置 scroll_interval 控制每次滚动之间等待的时长(单位:毫秒)。这会在 HTML 返回之前加载更多卡片,使单次请求获得更深度的结果集。
options = { "ajax_wait": "true", "page_wait": 10000, "scroll": "true", "scroll_interval": 10000, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", }
保持 scroll_interval 较为宽裕。在防护严密的目标上激进滚动,是触发限速最快的方式。获取合理的样本量后停止,不要试图在单次运行中滚遍整个信息流。
保存为 CSV
获得记录后,将其写入 CSV 可以方便地在电子表格或 Notebook 中进行聚合分析。将话题标签列表展开为单个分隔字符串,使每行保持一行记录。
import csv def save_to_csv(rows, filename): fieldnames = ["caption", "video_url", "posted_date", "like_count", "hashtags"] with open(filename, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() for row in rows: row = {**row, "hashtags": " ".join(row.get("hashtags", []))} writer.writerow(row)
使用主脚本中获得的列表调用 save_to_csv(results, "tiktok_data.csv"),你将得到一张整洁的公开视频元数据表格,可用于趋势分析,无需存储任何个人信息。
保持畅通运行
即使有 Crawling API 处理渲染,TikTok 仍会监测爬虫形态的流量。养成以下习惯可以让运行保持健康,这对任何防护严密的目标都适用。
- 控制请求速率。 在紧密循环中频繁请求页面是被限速最快的方式。在请求之间添加真实延迟,不要急于并行化。
- 善用轮换。 住宅 IP 池将请求分散到众多真实用户地址,不让单个地址触发限速。Crawling API 为你处理这一切;如果自建栈,这是需要重点关注的部分。
- 关注状态码。 运行开始返回质询或错误响应,说明当前的速率或 IP 级别已不够用,应退后而非继续施压。
- 保持低请求量和多样化目标。 聚合趋势研究不需要抓取话题标签的完整历史。采样所需内容后停止。
更广泛的操作手册,请参阅如何在不被封锁的情况下抓取网站。如果你只需要公开互动文字而非视频元数据,我们的如何抓取 TikTok 评论指南涵盖了这一场景;如果你更倾向于选用现成工具,我们的最佳 TikTok 爬虫对比文章列举了各种选项。
抓取 TikTok 合法吗?
这是你在编写生产代码之前必读的章节。TikTok 的服务条款限制自动化访问和数据采集,无论工具多么谨慎,抓取行为都可能违反这些条款。上述代码并不改变这一现实,它只是让技术部分得以实现。请阅读 TikTok 的服务条款及其 robots.txt,遵守这些信号所隐含的速率限制,并将两者视为采集范围的边界。抓取行为在法律上处于灰色地带,结论很大程度上取决于你采集什么数据以及如何使用,因此对于商业或大规模项目,请寻求专业法律意见。
必须坚守的诚实原则:只采集公开聚合数据,即任何人无需登录即可看到的公开说明文字、公开点赞数、评论数和分享数、公开视频 URL、发布日期以及话题标签。绝不抓取私密账户、需要登录才能访问的内容、私信或粉丝列表。不要为可识别的个人建立档案:将用户名、账号和用户撰写的评论视为个人数据,尽量聚合(数量、趋势、话题标签分布),不要将个人内容与其身份关联后重新发布。涉及个人数据时,GDPR 和 CCPA 等隐私法规适用:你需要合法依据进行处理,并须响应删除请求。这些都是不可逾越的红线,本指南的设计始终在聚合、公开数据的这一侧。
对于任何实际或商业用途,正确的工具是 TikTok 官方 API。TikTok 提供面向开发者的 API,用于获得授权的内容和指标访问,具有可靠的结构和可遵循的条款。本文仅是一篇范围严格限定于公开聚合数据的技术教程,不构成对大规模个人数据采集的背书,也不涉及任何登录后的内容。如果你的项目需要超出少量公开字段样本的内容,官方 API 或正式的数据合作协议才是正确路径,而非更精巧的爬虫。
核心要点
- TikTok 是客户端渲染且有反爬虫防护的。 普通请求只会返回空壳,因此必须在解析之前先渲染页面。
-
渲染与受信任 IP 应在一次调用中完成。 带 JS token 的 Crawling API 两者兼顾;
ajax_wait和page_wait控制等待内容的时长,scroll处理 TikTok 的无限信息流。 -
解析稳定的信号。 TikTok 的
data-e2e属性比其频繁改名的嵌套类稳定得多。 - 只采集公开聚合数据。 提取说明文字、点赞数、评论数和分享数、视频 URL、发布日期和话题标签;绝不涉及私密内容、粉丝列表或个人档案。
- 控制速率、轮换 IP,并优先使用官方 API。 保持低请求量,依赖住宅 IP 轮换,对于任何实际或商业用途使用 TikTok 官方 API。
常见问题
为什么普通请求从 TikTok 获取不到数据?
因为 TikTok 使用 JavaScript 在客户端渲染其搜索、话题标签和主页内容。初始 HTML 是一个外壳,只有在页面脚本在浏览器中运行后才会填充内容,因此原始 HTTP 请求返回的正文几乎为空。要获取真实的公开数据,必须先渲染页面,这正是 Crawling API 的 JS token 为你处理的工作。
抓取 TikTok 需要普通 token 还是 JS token?
需要 JS token。普通 token 获取静态 HTML,在 TikTok 上这与普通请求返回的空壳相同。JS token 在交还 HTML 之前先在真实浏览器中渲染页面,这样 BeautifulSoup 解析时公开的视频卡片就已存在。
哪些 TikTok 数据适合抓取?
只有公开聚合数据:公开说明文字、以数字形式呈现的公开点赞数、评论数和分享数、公开视频 URL、发布日期,以及任何人无需登录即可看到的话题标签。私密账户、需要登录才能访问的内容、私信、粉丝列表以及个人的身份或内容均不在范围之内。这些属于个人数据,采集它们违反 TikTok 的服务条款,并在许多地区违反隐私法规。
如何处理 TikTok 的无限滚动?
将 Crawling API 的 scroll 选项设为 true,并调整 scroll_interval 控制每次滚动之间的等待时长。API 模拟向下滚动页面,使更多视频卡片加载到 HTML 中后再返回。保持间隔宽裕,采样合理数量后停止,不要试图在单次请求中滚遍整个信息流。
我应该使用 TikTok 官方 API 还是直接抓取网站?
对于任何实际、持续或商业用途,请使用 TikTok 官方 API。那是被认可的渠道,提供可靠的结构,并让你保持在 TikTok 的服务条款之内。本文介绍的抓取少量公开聚合字段的方法,适用于无法获取 API 访问权限时的轻量级公开数据研究,但必须遵守服务条款、robots.txt 和速率限制。
如何避免在抓取 TikTok 时被封锁?
保持每个 IP 的低请求速率,在请求之间添加真实延迟,通过轮换住宅 IP 路由流量以避免单个地址触发限速,并多样化目标而非持续抓取同一话题标签的完整历史。Crawling API 为你管理轮换和受信任 IP 池。关注状态码,一旦开始看到质询响应就立即退后。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
