Two teams publish videos about similar products but evaluate them manually using different signals: one remembers view counts, another focuses on comments, and a third saves links in a chat. A week later, it is difficult to tell which formats recur among competitors, which posts receive notable reactions, and what has changed since the last review.
The solution is to collect a limited, reproducible sample of public videos using predefined keywords, save the original JSON, and build a table with consistent fields. This lets you compare videos in a single workflow rather than mixing random observations with conclusions about the market. Below is a framework for lightweight competitor monitoring in Python with the jsonscraper TikTok Scraper API.
Start by defining the analysis question
Data collection is useful when you already know what decision you will make based on the results. For example, a marketing team at a sports bottle manufacturer wants to understand how competitors showcase their products in short videos: through demonstrations, unboxings, comparisons, or user reactions. You do not need to copy all of TikTok. You need a few targeted search queries and repeatable observations of the videos returned by the API for those queries.
Frame the question so it can be tested: “Which description and demonstration formats appear among videos published for our five key phrases, and which of the videos found currently have the highest views and reaction counts?” This wording separates two layers of analysis. Text and descriptions help group creative approaches; view, like, comment, and share counts provide a snapshot of public reactions at the time of collection.
This snapshot is useful for prioritizing manual review and tracking changes. It describes the videos returned for specific queries, parameters, and run times. So a sound conclusion would be “unboxing videos are common in our sample” or “this group of videos currently has a higher median view count.” This scope of analysis helps a team choose hypotheses for its own content.
Design a small data pipeline
Divide the work into four stages: collection, raw-response storage, normalization, and analysis. Collection calls the API for each approved keyword. The full raw response is saved for rechecking. Normalization turns the nested JSON structure into table rows, and analytics calculates metrics and groups videos by keyword or manually assigned format.
- Collection. Request a specified number of results for each keyword using the same region and sort order.
- Archive. Save the original JSON and retrieval time so you can verify the extracted values.
- Normalization. Keep the video ID, link, description, author, publication time, and available counters.
- Quality control. Deduplicate by ID, flag missing values, and retain the keyword that surfaced the video.
- Comparison. Calculate your own metrics only from available source fields and compare groups within a single snapshot.
This approach is better than a list of links because it preserves each row’s provenance and lets you distinguish a video appearing again from a new piece of content. Also store the collection date: public counters can change, so a value in today’s response should not be treated as a permanent characteristic of a video.
What the TikTok Scraper API supports
For searching videos by text query, the jsonscraper documentation describes the GET route /searchVideo at the base address https://tiktok.evelode.com. The required parameter is keyword; other available parameters include count, cursor, region, date_posted, sort_by, and cache_timeout. The documentation lists most-liked, relevance, and date-posted as sort options, and yesterday, this-week, this-month, last-3-months, last-6-months, and all-time as publication periods. Check parameter names and purposes in the TikTok route reference.
The key is passed in the license_key query parameter. For a working script, put it in the JSONSCRAPER_TIKTOK_LICENSE_KEY environment variable rather than in the source file, a spreadsheet, or a run log. In the response, check the top-level status, then parse the tiktok object. For search, the structure may contain an aweme_list list as well as search_item_list, where video details are nested inside aweme_info. The code below supports both forms.
Confirmed observations of search responses include a video ID, description, publication time, link, author details, and metrics such as views, likes, comments, saves, and shares. Fields may be absent from a particular record. Leave such values blank in the table rather than replacing them with zero: zero means a measured value, while a blank field means the value was missing from the object received.
A minimal Python example
The script below makes one request for each keyword, saves the full response to a JSON file, and creates a CSV for a spreadsheet or further processing. It is designed for Python 3.8+ and uses only the standard library. Set the environment variable containing your license before running it. Adjust the keyword list and result count to suit your use case.
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 returned a non-ok status")
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("Saved", len(records), "video rows to tiktok_creatives.csv")
The script stores the keyword alongside each video, which matters if the same video is found through multiple queries. Here, deduplication takes place within each keyword, so the same ID can have a separate row for different queries. For a unified table without duplicates, replace the key (keyword, video_id) with video_id, and accumulate keywords in a list or a separate relationship table.
The code deliberately makes one request per term and does not automatically page through results. If you need the next page, the documentation defines cursor as the value for paging through results, obtained from the previous response. Pass it through as an opaque value; do not calculate it yourself. For your specific workflow, verify the pagination schema using a test response and stop when there are no new results or no next cursor.
How to create a comparable table
You can open the CSV in a spreadsheet, load it into a database, or use Python to calculate aggregates. For an initial report, group rows by collection date, keyword, and video format. It is best to label formats after reviewing the description and video itself—for example, “demonstration,” “unboxing,” “review,” or “comparison.” You can classify a small sample manually or ask a model to draft labels, then have a person verify them.
If you automate text labeling, provide the model only the fields it needs—for example, the description and ID—and ask it to return one label from a predefined, approved list. Keep the original description separate from the classification result. This lets an analyst check a disputed label and recalculate the report after changing the taxonomy, without making another API request.
For comparative metrics, use values collected at the same time. For example, the like-to-view ratio can be calculated as likes / views when both fields are present and views are greater than zero. You can similarly calculate the share of comments or shares. These ratios describe the current public counter for a given video; they do not measure reach among a particular audience, completion rates, or conversions. For business decisions, compare them with your own campaign and use them as a guide for choosing ideas, not as a promised outcome from repeating someone else’s format.
The median is often more useful than the mean when a sample includes a few very large videos. Compare median views and median reactions across groups only when the collection rules are consistent: the same set of queries, region, time filter, and a similar snapshot time. Record the run parameters along with the date. If changes to the search or sample alter which rows are included, a difference between two reports may also reflect a change in method.
Reruns and change tracking
To turn a one-off analysis into monitoring, run the script on a schedule—for example, daily or weekly—and add the collection date to each record. For every video, store pairs of “snapshot time — counter value.” This lets you see how public metrics have changed between observations while preserving each measurement separately. Do not overwrite old counters with current ones, or the trend will disappear.
In production, move raw JSON from the local folder to object storage or another archiving system, and put normalized rows in a database with a unique key, such as the combination of video ID and collection time. Add an error log that does not include the secret key, a retry limit, and a notification for failed requests. You can retry a temporary error after a delay; for a structural error, it is better to save the response and flag the run for review so that a failure does not quietly produce an empty report.
Do not use cursor as a permanent research identifier: it is a marker for continuing a result set. Store it with the parameters for the corresponding request, use it only to continue that result set, and do not mix pages from different keywords. Also record the actual number of results processed: the requested count sets the desired response size; it does not guarantee complete coverage of all matching posts.
How to extend the workflow
When a search sample is too noisy, you can separately monitor specific public profiles or hashtags. The documentation includes the /getUserFeed and /getHashtagFeed routes with query and pagination parameters. These are different samples with their own continuation rules: the profile feed uses max_cursor, while the hashtag feed uses cursor. Add them as separate sources and record the source type and parameters so you do not mix keyword search results with posts from a profile or hashtag.
For a team without its own infrastructure, you can send a CSV to Google Sheets through its API: the Google Sheets API documentation describes reading and writing cell values. Add separate sheets for raw runs, normalized videos, and summaries. This option is convenient for collaborative labeling; when the history becomes cumbersome in a spreadsheet, move the rows to SQL storage while preserving the same fields and data provenance.
A sensible role for an AI agent is to prepare a concise summary of already collected and normalized records: group descriptions, suggest formats, and flag unusual values. Do not ask the agent to make claims about the entire platform based on a small result set. Provide the period, query list, and number of rows; ask it to reference video IDs and return uncertain or missing fields as blank. An analyst can then check examples using the saved links.
What you will get
Once configured, the team gets a reproducible register of videos found, including IDs, links, descriptions, authors, publication times, and available public counters. The saved raw response makes it possible to verify the transformation, while the CSV lets you quickly sort videos, filter by keyword, and compare snapshots by date. From this, you can create a weekly summary: which formats recurred, which posts merit a closer look, and which creative hypotheses are worth testing in your own content.
The practical result is not a universal competitor ranking, but a disciplined sample with clear provenance. Its quality depends on the queries, filters, and observation period selected. So keep the collection parameters, manually check individual videos, and distinguish available metrics from interpretation. This process helps replace scattered bookmarks with comparable data and makes it possible to discuss creative work using concrete examples.
You can check the search route and parameters in the official jsonscraper documentation, and find the service description and access on the TikTok Scraper API page. Start with a few search phrases, save one test response, and check its structure before scheduling runs.