By tracking TikTok competitors, you can identify the topics, formats, and hooks that get attention in your niche and use that evidence to decide what to create next. To build a TikTok competitor tracker you can rerun, you need a steady sample of recent videos and a ranking that is easy to justify. This walkthrough uses Tapline's Python client to pull five videos per account, compute a public engagement score, and export the output as a timestamped CSV.
The example relies on the public duolingo and babbel accounts. When you run the script, replace those handles with the accounts you want to compare.
Because the official client sends the Tapline request for you, this workflow requires no TikTok cookies, browser automation, or proxy setup.
Sample output
Each video is ranked by the tracker using likes + comments * 5 + shares * 10. Below are the first five rows of the captured CSV.
| Rank | Account | Post | Posted | Views | Score |
|---|---|---|---|---|---|
| 1 | @duolingo | dressed up like jinshi but people keep mistaking me for ah-duo… | 2026-10-23 | 1,300,000 | 890,230 |
| 2 | @duolingo | leave everything in the past except ur streak | 2026-10-26 | 997,900 | 355,805 |
| 3 | @duolingo | make my own group, call it K/DUO | 2026-10-26 | 411,600 | 333,720 |
| 4 | @duolingo | ima start a streak, im a streakstarter | 2026-10-27 | 313,900 | 215,485 |
| 5 | @duolingo | could've been us if u did ur lesson | 2026-10-26 | 282,700 | 86,335 |
The scoring stays intentionally simple: comments and shares signal stronger public interaction than a view, so they carry more weight. Keep the raw columns in the CSV so you can adjust the weighting later without issuing another request. The tested Python script is available for download alongside it.
Prerequisites
You need Python 3.10 or later, a Tapline account with an API key, and the exact tested client version tapline==0.2.18. The only dependency the script requires is the official Tapline package.
pip install "tapline==0.2.18"
Send the Tapline request
The get_profile_videos method retrieves a user's public videos. Provide a single handle or numeric user ID, then pass the returned max_cursor in a follow-up request while has_more remains true. Before keeping its five newest videos, the script orders the first page by UTC timestamp, which prevents a pinned item from pushing out a recent video.
import osfrom tapline import SyncTaplineClientwith SyncTaplineClient(api_key=os.environ["TAPLINE_API_KEY"]) as tapline:page = tapline.tiktok.get_profile_videos(handle="duolingo",sort_by="latest",region="US",)posts = page.videos
Each video carries the typed statistics object the ranking relies on, plus the video URL, UTC timestamp, description caption, and a stable video ID. Any count TikTok leaves out arrives as None, so the script reads it as zero.
Assemble the script
Store this as track_tiktok_competitors.py. For each account it requests the same five-video window, orders by UTC timestamp, catches a Tapline error per account so one unavailable profile does not wipe out the rest, and writes rows using a single consistent schema.
"""Rank recent public TikTok posts from several accounts."""from __future__ import annotationsimport argparseimport csvimport osimport sysfrom dataclasses import dataclassfrom datetime import datetime, timezonefrom pathlib import Pathfrom tapline import SyncTaplineClient, TaplineErrorfrom tapline.tiktok import TikTokStatistics, TikTokVideoPOSTS_PER_ACCOUNT = 5DEFAULT_ACCOUNTS = ("duolingo", "babbel")OUTPUT_PREFIX = "tiktok-competitors"@dataclass(frozen=True)class RankedPost:account: strpost_id: strposted_at: strcaption: strurl: strviews: intlikes: intcomments: intshares: intengagement_score: intdef read_statistics(post: TikTokVideo) -> TikTokStatistics:"""Counts TikTok leaves out read as zero, so one sparse post can't break the ranking."""return post.statistics or TikTokStatistics()def engagement_score(stats: TikTokStatistics) -> int:"""Weight comments and shares more heavily than passive views or likes."""return (stats.digg_count or 0) + (stats.comment_count or 0) * 5 + (stats.share_count or 0) * 10def collect_account(tapline: SyncTaplineClient, account: str, limit: int = POSTS_PER_ACCOUNT) -> list[RankedPost]:page = tapline.tiktok.get_profile_videos(handle=account, sort_by="latest", region="US")recent_posts = sorted(page.videos, key=lambda post: post.create_time or 0, reverse=True)ranked: list[RankedPost] = []for post in recent_posts[:limit]:stats = read_statistics(post)ranked.append(RankedPost(account=account,post_id=post.aweme_id or "",posted_at=post.create_time_utc or "",caption=post.description or "",url=post.url or "",views=stats.play_count or 0,likes=stats.digg_count or 0,comments=stats.comment_count or 0,shares=stats.share_count or 0,engagement_score=engagement_score(stats),))return rankeddef write_csv(posts: list[RankedPost], output_dir: Path) -> Path:output_dir.mkdir(parents=True, exist_ok=True)timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")output_path = output_dir / f"{OUTPUT_PREFIX}-{timestamp}.csv"fields = ["rank","account","post_id","posted_at","caption","url","views","likes","comments","shares","engagement_score",]with output_path.open("w", newline="", encoding="utf-8") as output:writer = csv.DictWriter(output, fieldnames=fields)writer.writeheader()for rank, post in enumerate(sorted(posts, key=lambda item: item.engagement_score, reverse=True), start=1):writer.writerow({"rank": rank, **post.__dict__})return output_pathdef parse_args() -> argparse.Namespace:parser = argparse.ArgumentParser(description=__doc__)parser.add_argument("accounts", nargs="*", help="TikTok handles without the @")parser.add_argument("--output-dir",type=Path,default=Path("."),help="Directory for the timestamped CSV (default: current directory)",)return parser.parse_args()def main() -> int:args = parse_args()accounts = tuple(args.accounts) or DEFAULT_ACCOUNTSposts: list[RankedPost] = []with SyncTaplineClient(api_key=os.environ["TAPLINE_API_KEY"]) as tapline:for account in accounts:try:posts.extend(collect_account(tapline, account))except TaplineError as exc:print(f"Skipping @{account}: {type(exc).__name__}", file=sys.stderr)if not posts:raise SystemExit("No posts were returned. Check the handles and API key.")output_path = write_csv(posts, args.output_dir)print(f"Wrote {len(posts)} posts to {output_path}")return 0if __name__ == "__main__":raise SystemExit(main())
Execute the script
Place your key in TAPLINE_API_KEY and then run the downloaded file. Optional handles override the defaults, and --output-dir sets where the timestamped CSV is saved.
pip install "tapline==0.2.18"export TAPLINE_API_KEY="your-api-key"python track_tiktok_competitors.py
python track_tiktok_competitors.py duolingo babbel --output-dir ./data
Work with the results
Start with the top-ranked URLs and sort them by topic, format, recurring character, or call to action. Then compare those groupings across accounts and pick the patterns worth examining more closely in TikTok and in your own content plan.
Keep the ranking understandable
Views, likes, comments, shares, and the computed score sit side by side in the CSV. That lets you sort by any single metric, experiment with a different weighting, or add another score without re-fetching the videos.
Practical constraints
Each profile-videos page holds up to 35 videos and consumes one credit. This script deliberately uses the first page, orders it by create_time, and keeps five rows per account. To reach older videos, follow max_cursor while has_more is true; every extra page counts as another request. Public counts of 10,000 or higher may be rounded, and private, deleted, or unavailable videos are excluded from results.
Track TikTok competitors
You now have a typed Tapline request, a full Python tracker, and a timestamped CSV ready for review or scheduling. Create a Tapline account, open the TikTok API docs, and run the example with your own list of accounts.