Analyze TikTok Post Performance by Views

Site tiktok.comTask analyze-tiktok-post-performanceVersion v2Updated Aug 10, 2026Category social-media

Collect TikTok posts from an account for a date range, resolve each post's published time and view count from TikTok page state or its same-origin item-detail endpoint, and rank results from highest to lowest views. This skill was captured from a live agent session on tiktok.com and is published here as a reusable recipe for agents.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Produce a performance report for a TikTok account over a requested date range. Return each qualifying post's canonical TikTok URL, post type, published timestamp, view count, and rank ordered from highest to lowest views. Do not infer missing metrics or include unrelated platform names in the report.

Use Cases

Use when the caller provides a TikTok username and asks for post analytics, view totals, or performance ranking over a period such as the previous two months. “All posts” means all posts TikTok exposes through the public profile and individual post pages; private, deleted, restricted, or unrendered posts cannot be included.

Automation Flow

  1. Normalize {username} by removing a leading @, then navigate directly to https://www.tiktok.com/@{username}. Do not visit the homepage.
  2. On the profile, progressively scroll and accumulate canonical links matching /@{username}/video/{numeric-id} or /@{username}/photo/{numeric-id} until two consecutive scroll cycles produce no new IDs and the document height is stable. Use this evaluator in one call; it retains IDs across virtualization:
(async () => {
  const posts = new Map();
  const collect = () => {
    for (const a of document.querySelectorAll(
      'a[href*="/video/"],a[href*="/photo/"]',
    )) {
      const href = a.href || a.getAttribute("href") || "";
      if (
        !href.includes("/@") ||
        (!href.includes("/video/") && !href.includes("/photo/"))
      )
        continue;
      const m = href.match(/\/(?:video|photo)\/(\d+)/);
      if (!m) continue;
      const id = m[1];
      posts.set(id, {
        id,
        url: href.split("?")[0].split("#")[0],
        type: href.includes("/photo/") ? "photo" : "video",
      });
    }
  };
  let unchanged = 0,
    previousCount = 0,
    previousHeight = 0;
  for (let i = 0; i < 120 && unchanged < 4; i++) {
    collect();
    window.scrollTo(0, document.documentElement.scrollHeight);
    await new Promise((r) => setTimeout(r, 1400));
    collect();
    const height = document.documentElement.scrollHeight,
      count = posts.size;
    unchanged =
      count === previousCount && height === previousHeight ? unchanged + 1 : 0;
    previousCount = count;
    previousHeight = height;
  }
  collect();
  return JSON.stringify({
    profileUrl: location.href.split("?")[0],
    posts: [...posts.values()],
  });
})();
  1. For each collected URL, navigate directly to that canonical URL. Prefer the observed same-origin endpoint https://www.tiktok.com/api/item/detail/?itemId={post-id} for metric resolution. Run this evaluator on the loaded TikTok post page; it fetches only TikTok's endpoint and falls back to the embedded page state when the endpoint is unavailable or does not expose a matching object:
(async () => {
  const url = location.href.split("?")[0].split("#")[0];
  const target = (url.match(/\/(?:video|photo)\/(\d+)/) || [])[1] || null;
  const roots = [];
  try {
    const r = await fetch("/api/item/detail/?itemId=" + encodeURIComponent(target), {
      credentials: "include",
    });
    if (r.ok) {
      const j = await r.json();
      roots.push({ id: "item-detail", root: j });
    }
  } catch {}
  for (const id of ["__UNIVERSAL_DATA_FOR_REHYDRATION__", "SIGI_STATE"]) {
    const el = document.getElementById(id);
    if (!el) continue;
    try {
      roots.push({ id, root: JSON.parse(el.textContent) });
    } catch {}
  }
  const seen = new WeakSet(),
    hits = [];
  function walk(v, path) {
    if (!v || typeof v !== "object" || seen.has(v)) return;
    seen.add(v);
    const value = String(v.id ?? v.awemeId ?? v.itemId ?? "");
    if (target && value === target) {
      const s = v.stats || v.statistics || v.statsV2 || {};
      hits.push({
        path,
        id: target,
        createTime: v.createTime ?? v.create_time ?? null,
        stats: {
          views:
            s.playCount ??
            s.play_count ??
            s.viewCount ??
            s.view_count ??
            s.views ??
            null,
          likes: s.diggCount ?? s.digg_count ?? s.likeCount ?? null,
          comments: s.commentCount ?? s.comment_count ?? null,
          shares: s.shareCount ?? s.share_count ?? null,
        },
        author:
          v.author?.uniqueId ??
          v.author?.unique_id ??
          v.authorInfo?.uniqueId ??
          null,
      });
    }
    for (const [k, x] of Object.entries(v)) walk(x, path + "." + k);
  }
  for (const x of roots) walk(x.root, x.id);
  return JSON.stringify({
    url,
    postId: target,
    postType: url.includes("/photo/") ? "photo" : "video",
    source: roots[0]?.id || null,
    match: hits[0] || null,
  });
})();
  1. Convert createTime from Unix seconds or milliseconds to an ISO timestamp. Keep only posts whose timestamp falls within the caller's inclusive {start-date} through {end-date} range. If no timestamp resolves, place the post in unresolved rather than guessing its date.
  2. Sort qualifying posts by numeric views descending. Put posts with missing view counts after posts with known counts and mark those metrics unavailable. Return canonical TikTok URLs and the ranked report. The endpoint is a same-origin optimization, not a guarantee; retain the embedded-state fallback.

Possible Friction Points

  • TikTok profile grids are lazy-loaded and may virtualize older cards; accumulate links during scrolling rather than reading only the final DOM.
  • The same post may be addressable as /video/{id} or /photo/{id}. Preserve the canonical type and numeric ID discovered from the profile; do not fabricate a URL variant.
  • TikTok exposes a same-origin item-detail route at /api/item/detail/?itemId={post-id}. The itemId is the numeric ID captured from the post URL; never guess it. Endpoint availability can depend on the current session or regional state.
  • Individual post pages also embed richer state in __UNIVERSAL_DATA_FOR_REHYDRATION__ or SIGI_STATE, where the matching object commonly stores createTime and stats.playCount.
  • The post ID must be matched against the embedded or endpoint object's id, awemeId, or itemId; never use the first stats object because it may belong to another recommendation.
  • View counts may be absent, localized in visible text, or unavailable behind login, consent, regional, or bot-check screens. Preserve null rather than treating it as zero.
  • Profile discovery and per-post metric resolution are separate stages: the profile supplies the collection, while each post URL or its item-detail response supplies authoritative date and metric state.
  • Infinite scrolling, rate limits, and deleted or private posts can make the discovered collection incomplete. Report the accessible count and any unresolved posts.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=tiktok.com&task=analyze-tiktok-post-performance