两支团队发布了关于类似产品的视频,却用不同标准进行人工评估:一支团队记下播放量,另一支关注评论,还有一支只把链接保存在聊天记录里。一周后,团队很难判断竞争对手反复采用了哪些形式、哪些帖子获得了显著互动,以及与上次查看相比发生了什么变化。
解决办法是根据预先设定的关键词,收集范围有限且可重复获取的公开视频样本,保存原始 JSON,并用统一字段生成表格。这样就能在同一工作流程中比较视频,而不是把零散观察与市场结论混为一谈。下面介绍一种使用 Python 和 jsonscraper TikTok Scraper API 进行小规模竞争监测的方法。
先明确分析问题
只有事先明确将依据数据作出什么决策,数据收集才有意义。例如,一家运动水瓶制造商的营销团队想了解竞争对手如何在短视频中展示产品:通过使用演示、开箱、对比还是用户反应。为此无需抓取整个 TikTok。只需要几个目标搜索词,并持续观察 API 根据这些搜索词返回的视频。
把问题表述成可以检验的形式:“在我们设置的五个关键词下,已发布的视频中常见哪些描述和演示形式?在找到的视频中,哪些目前的播放量和互动数最高?”这种表述将分析分成两个层面。文本和描述有助于归纳创意方式;播放、点赞、评论和转发计数则反映收集时的公开互动快照。
这份快照有助于确定人工审看的优先级并跟踪变化。它描述的是 API 针对特定查询、参数和运行时间返回的视频。因此,恰当的结论应是“我们的样本中开箱视频很常见”或“这组视频目前的播放量中位数较高”。这种分析范围能帮助团队为自有内容选择待验证的假设。
设计一个小型数据流水线
将工作分为四个阶段:收集、保存原始响应、规范化和分析。收集阶段针对每个已批准的关键词调用 API。完整保存原始响应,以便日后复核。规范化阶段将嵌套的 JSON 结构转换为表格行;分析阶段则计算指标,并按关键词或人工标注的内容形式对视频分组。
- 收集。 使用相同地区和排序方式,为每个关键词请求指定数量的结果。
- 归档。 保存原始 JSON 和获取时间,以便核对提取出的值。
- 规范化。 保留视频 ID、链接、描述、作者、发布时间和可用计数。
- 质量控制。 按 ID 去重,标记缺失值,并保留带来该视频的关键词。
- 比较。 仅使用可用的原始字段计算自定义指标,并在同一快照范围内比较各组。
与链接列表相比,这种做法更好,因为它保留了每行数据的来源,并能区分同一视频再次出现与新内容。还应保存收集日期:公开计数可能变化,因此不能把今天响应中的数值视为视频的固定特征。
TikTok Scraper API 支持的功能
jsonscraper 文档介绍了用于按文本查询搜索视频的 GET 路由 /searchVideo,其基础地址为 https://tiktok.evelode.com。必需参数是 keyword;此外还支持 count、cursor、region、date_posted、sort_by 和 cache_timeout。文档列出的排序选项包括 most-liked、relevance 和 date-posted;发布时间范围包括 yesterday、this-week、this-month、last-3-months、last-6-months 和 all-time。请对照TikTok 路由参考文档核实参数名称和用途。
密钥通过查询参数 license_key 传递。运行脚本时,请将密钥放在环境变量 JSONSCRAPER_TIKTOK_LICENSE_KEY 中,而不要写入源文件、表格或运行日志。检查响应中的顶层 status,然后解析 tiktok 对象。搜索响应可能包含 aweme_list 列表,也可能包含 search_item_list,视频信息位于其中的 aweme_info 内。下面的代码兼容这两种结构。
已确认的搜索响应中包含视频 ID、描述、发布时间、链接、作者信息,以及播放、点赞、评论、收藏和转发等指标。具体记录可能缺少某些字段。在表格中应将此类值留空,而不是改成零:零表示测得的数值,空白则表示收到的对象中没有该值。
Python 最小示例
下面的脚本对每个关键词执行一次查询,将完整响应保存为 JSON 文件,并生成可供表格或后续处理使用的 CSV。脚本适用于 Python 3.8 及以上版本,仅使用标准库。运行前请设置许可证环境变量。请根据自己的场景调整关键词列表和结果数量。
import csv
import json
import os
import time
from datetime import datetime, timezone
from pathlib import Path
from urllib.parse import urlencode
from urllib.request import urlopen
BASE_URL = "https://tiktok.evelode.com/searchVideo"
LICENSE_KEY = os.environ["JSONSCRAPER_TIKTOK_LICENSE_KEY"]
KEYWORDS = ["保温水瓶", "运动水瓶", "水瓶开箱"]
REGION = "US"
COUNT = 20
def request_search(keyword):
params = {
"license_key": LICENSE_KEY,
"keyword": keyword,
"count": COUNT,
"region": REGION,
"sort_by": "relevance",
"date_posted": "this-month",
}
url = BASE_URL + "?" + urlencode(params)
with urlopen(url, timeout=60) as response:
return json.loads(response.read().decode("utf-8"))
def extract_videos(payload):
if payload.get("status") != "ok":
raise RuntimeError("API 返回了非 ok 状态")
tiktok = payload.get("tiktok") or {}
rows = []
for item in tiktok.get("aweme_list") or []:
if isinstance(item, dict):
rows.append(item)
for item in tiktok.get("search_item_list") or []:
video = item.get("aweme_info") if isinstance(item, dict) else None
if isinstance(video, dict):
rows.append(video)
return rows, tiktok
Path("raw").mkdir(exist_ok=True)
seen = set()
records = []
for keyword in KEYWORDS:
payload = request_search(keyword)
collected_at = datetime.now(timezone.utc).isoformat()
safe_name = keyword.replace(" ", "_")
raw_path = Path("raw") / (safe_name + ".json")
raw_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
videos, tiktok = extract_videos(payload)
for video in videos:
video_id = video.get("aweme_id")
if not video_id:
continue
dedupe_key = (keyword, video_id)
if dedupe_key in seen:
continue
seen.add(dedupe_key)
stats = video.get("statistics") or {}
author = video.get("author") or {}
records.append({
"collected_at": collected_at,
"keyword": keyword,
"video_id": video_id,
"url": video.get("share_url"),
"created_at_epoch": video.get("create_time"),
"description": video.get("desc"),
"author": author.get("nickname"),
"username": author.get("unique_id"),
"views": stats.get("play_count"),
"likes": stats.get("digg_count"),
"comments": stats.get("comment_count"),
"shares": stats.get("share_count"),
"saves": stats.get("collect_count"),
"cursor": tiktok.get("cursor"),
})
time.sleep(1)
fieldnames = [
"collected_at", "keyword", "video_id", "url", "created_at_epoch",
"description", "author", "username", "views", "likes", "comments",
"shares", "saves", "cursor",
]
with open("tiktok_creatives.csv", "w", newline="", encoding="utf-8") as output:
writer = csv.DictWriter(output, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(records)
print("已将", len(records), "条视频记录保存到 tiktok_creatives.csv")
脚本会将关键词与视频一并保存:如果同一视频被多个查询发现,这一点很重要。这里的去重只在每个关键词内部进行,因此同一 ID 可能会因不同查询各占一行。如需生成不重复视频的统一表格,可将键 (keyword, video_id) 改为 video_id,并将关键词汇总到列表或单独的关联表中。
代码有意对每个关键词只发起一次请求,不会自动翻页。如果需要获取下一页,文档将 cursor 定义为从上一响应中获取、用于继续滚动结果的值。应将其作为不透明值传递,不要自行计算。请针对具体工作流程用测试响应核实分页结构,并在没有新结果或后续游标时停止。
如何生成可比较的表格
CSV 可以在电子表格中打开、导入数据库,或在 Python 中用于计算汇总数据。初步报告可按收集日期、关键词和视频形式对记录分组。最好在查看描述和视频后再标注形式,例如“演示”“开箱”“评价”“对比”。小样本可以人工分类;也可以让模型提供初步标签,再由人工检查。
如果要自动标注文本,只需向模型提供必要字段,例如描述和 ID,并要求它从预先批准的标签列表中返回一个标签。将原始描述与分类结果分开保存。这样分析师就能复核有争议的标签,并在分类体系发生变化后重新计算报告,无需再次调用 API。
比较不同指标时,应使用同一收集时间点的数值。例如,在两个字段都存在且播放量大于零时,可以用 likes / views 计算点赞与播放量之比。类似地,也可以计算评论或转发占比。这些比率描述的是该视频当前的公开计数;它们并不衡量特定受众的触达量、完整观看率或转化率。制定业务决策时,应将其与自己的营销活动进行对照,并把它们作为选择创意方向的参考,而不是复刻他人形式就能取得相同效果的保证。
如果样本中有几条播放量特别高的视频,中位数往往比平均值更有参考价值。只有在采集规则一致的情况下,才比较各组的播放量和互动量中位数:使用相同的查询集、地区和时间筛选条件,并尽量在相近时间获取快照。请记录每次运行的参数和日期。如果搜索方式或样本发生变化,两份报告之间的差异也可能是方法变化造成的。
重复运行与变化跟踪
要将一次性分析变成持续监测,可以按计划运行脚本,例如每天或每周一次,并为每条记录添加收集日期。对每条视频保存“快照时间—计数值”数据对。这样就能查看公开指标在不同观察时间点之间的变化,并保留每个独立测量值。不要用最新计数覆盖旧值,否则变化趋势会消失。
在生产环境中,可将原始 JSON 从本地目录迁移到对象存储或其他归档系统,并将规范化后的记录写入数据库,使用视频 ID 与收集时间的组合等作为唯一键。添加不含密钥的错误日志、重试次数上限,并在请求失败时发出通知。发生临时错误时,可以延迟后重试;如果是结构性错误,最好保存响应并将本次运行标记为需要检查,避免失败悄无声息地生成空报告。
不要将 cursor 当作研究的永久标识符:它只是继续获取结果的标记。请将其与对应请求的参数一同保存,仅用于继续该次结果集,也不要混用不同关键词的分页结果。同时记录实际处理的结果数量:请求中的 count 设定的是期望响应数量,并不保证覆盖所有符合条件的帖子。
如何扩展工作流程
如果搜索样本噪声太大,可以另外跟踪特定公开账号或话题标签。文档中提供了 /getUserFeed 和 /getHashtagFeed 路由,以及相应的查询和分页参数。这些路由对应不同样本集,也有各自的续取规则:账号动态使用 max_cursor,话题标签动态使用 cursor。应将它们作为独立来源接入,并保存来源类型和参数,以免把关键词搜索结果与账号或话题标签的帖子混在一起。
对于没有自有基础设施的团队,可以通过 API 将 CSV 数据传入 Google Sheets:Google Sheets API 文档介绍了单元格值的读取和写入。可以分别为原始运行记录、规范化视频和汇总数据建立工作表。这种方式便于多人协同标注;当历史数据量大到难以在表格中管理时,可将记录迁移到 SQL 存储,同时保留相同字段和数据来源信息。
对于 AI 代理,合理的任务是对已收集并规范化的记录生成简要摘要:归纳描述、提出形式分类,并指出异常数值。不要让代理根据少量搜索结果概括整个 TikTok 平台的事实。应向它提供时间范围、查询列表和记录数量,要求它引用视频 ID,并将不确定或缺失的字段留空。随后由分析师通过已保存的链接检查示例。
最终能得到什么
完成设置后,团队将获得一份可重复生成的视频索引,其中包含找到的视频 ID、链接、描述、作者、发布时间和可用的公开计数。保存下来的原始响应便于核查数据转换,CSV 则支持快速排序、按关键词筛选,并按日期比较不同快照。团队可以据此制作每周简报:哪些形式反复出现、哪些帖子值得查看,以及哪些创意假设值得在自有内容中测试。
实际成果并不是一份通用的竞争对手排名,而是一组来源清晰、规范严谨的样本。样本质量取决于所选查询、筛选条件和观察周期。因此,应保存采集参数,人工核查具体视频,并将可用指标与解释区分开来。这样的流程能以可比较的数据取代零散收藏,并让团队围绕具体案例讨论创意。
可以在jsonscraper 官方文档中核对搜索路由和参数,也可以在 TikTok Scraper API 页面查看服务说明和访问方式。先从几个搜索词开始,保存一份测试响应并检查其结构,再设置定时运行。