Find Viral TikTok Videos by Topic

Site tiktok.comTask find-viral-tiktok-videos-by-topicVersion v13Updated Sep 25, 2026Category social-media

Find surfaced public TikTok videos by hashtag, keyword, or the For You feed, excluding visibly restricted content, and extract canonical links plus visible metadata without interacting with posts. 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.

Collect a small set of relevant, publicly addressable TikTok videos or photo posts for a topic. Use direct hashtag, search, or For You feed URLs, accumulate lazy-loaded results, exclude cards visibly marked restricted or unavailable, and return canonical links with observed visible metadata. Treat surfaced ordering as TikTok's available ordering; do not claim global popularity or infer relevance from hidden state or audiovisual interpretation.

Use Cases

Use when the caller wants public TikTok short-form content for a hashtag, keyword topic, or the For You feed, including requests to browse visible content without interacting with posts. Inputs may be {hashtag}, {query}, {topic}, and optional {limit}. A saved browser profile or stealth mode may be supplied by the caller as session configuration; it does not change the navigation or extraction recipe.

Automation Flow

  1. Build the shortest direct destination:
    • Hashtag: https://www.tiktok.com/tag/{encodeURIComponent(hashtag)}
    • Keyword search: https://www.tiktok.com/search?q={encodeURIComponent(query)}
    • For You feed: https://www.tiktok.com/foryou Skip the homepage, search-box interaction, and post navigation unless the caller explicitly requests inspection of an individual post. Dismiss only a blocking consent, login, or modal overlay.
  2. On a hashtag or search page, wait about 3–4 seconds for client-rendered content. For a For You request, use the feed scroll container #column-list-container when present. TikTok recommendation cards normally use article[data-e2e="recommend-list-item-container"]; some rendered For You layouts expose cards as section[id^="media-card-"], so retain that fallback. Accumulate visible links across bounded scrolling rather than reading only the initial or final DOM.
  3. Run this evaluator once, replacing limit with the requested limit. It preserves visible metadata, deduplicates numeric post IDs, filters explicit restriction markers, and handles both observed For You card structures. It collects a bounded surplus (target) rather than stopping at limit, because topic relevance is judged in step 4 on the returned cards: on a For You or search page the first {limit} posts can all be off-topic, and stopping there would leave nothing to select from:
(async()=>{
const limit=8,target=Math.min(limit*3,limit+40),container=document.querySelector('#column-list-container'),videos=new Map();
const restricted=/\b(?:age[ -]?restricted|restricted content|under review|private (?:video|account|post)|(?:video|photo|post|content|this account) (?:is )?(?:unavailable|not available)|currently unavailable|no longer available)\b/i;
const clean=s=>(s||'').replace(/\s+/g,' ').trim();
const visible=e=>{if(!e)return false;const r=e.getBoundingClientRect(),s=getComputedStyle(e);return r.width>0&&r.height>0&&r.bottom>0&&r.top<innerHeight&&s.display!=='none'&&s.visibility!=='hidden'&&s.opacity!=='0'};
const isFeed=/\/foryou\/?$/i.test(location.pathname);
const countIn=s=>{const m=clean(s).match(/\d[\d.,]*\s*[KMBkmb]?/);return m?m[0].trim():null};
const metric=(card,e2e,labelSel)=>countIn(card.querySelector('[data-e2e="'+e2e+'"],[data-e2e$="'+e2e+'"]')?.innerText)||countIn(card.querySelector(labelSel)?.getAttribute('aria-label'))||null;
const collect=()=>{
 const cards=isFeed
  ? [...document.querySelectorAll('article[data-e2e="recommend-list-item-container"],section[id^="media-card-"]')].filter(visible)
  : [...document.querySelectorAll('a[href*="/video/"],a[href*="/photo/"]')].filter(visible).map(a=>a.closest('[data-e2e="challenge-item"],article,li,section')||a.parentElement?.parentElement||a.parentElement);
 for(const card of cards){
  const text=clean(card.innerText||card.textContent);
  for(const a of [...card.querySelectorAll('a[href*="/video/"],a[href*="/photo/"]')].filter(visible)){
   const href=a.href||a.getAttribute('href')||'',m=href.match(/\/(?:video|photo)\/(\d+)/);if(!m)continue;
   const metadata=clean([a.getAttribute('aria-label')||'',a.getAttribute('title')||'',a.querySelector('img')?.alt||''].join(' '));
   if(restricted.test(text+' '+metadata))continue;
   const author=[...card.querySelectorAll('a[href^="/@"],a[href*="tiktok.com/@"]')].find(visible),v=card.querySelector('video'),id=m[1];
   videos.set(id,{id,url:href.split('?')[0].split('#')[0],type:href.includes('/photo/')?'photo':'video',creator:clean(author?.innerText||author?.getAttribute('href')||'')||null,creatorUrl:author?.href?.split('?')[0]||null,caption:clean(card.querySelector('[data-e2e="challenge-item-desc"]')?.innerText||a.querySelector('img')?.alt||a.getAttribute('title')||''),cardText:text.slice(0,1200),linkText:clean(a.innerText||a.textContent).slice(0,160),ariaLabel:a.getAttribute('aria-label')||null,likes:metric(card,'like-count','[aria-label*="Like video" i]'),comments:metric(card,'comment-count','[aria-label*="comment" i]'),shares:metric(card,'share-count','[aria-label*="Share video" i]'),hashtags:[...card.querySelectorAll('a[href*="/tag/"]')].map(x=>clean(x.innerText||x.textContent)).filter(Boolean).slice(0,10),visibleImageAlts:[...card.querySelectorAll('img[alt]')].map(x=>clean(x.alt)).filter(Boolean).slice(0,5),video:v?{duration:Number.isFinite(v.duration)?Math.round(v.duration*10)/10:null,currentTime:Number.isFinite(v.currentTime)?Math.round(v.currentTime*10)/10:null,paused:v.paused,width:v.videoWidth||null,height:v.videoHeight||null}:null});
  }
 }
};
let unchanged=0,lastCount=0,lastTop=-1;
for(let i=0;i<30&&videos.size<target&&unchanged<3;i++){
 collect();
 if(isFeed&&container)container.scrollTop=Math.min(container.scrollTop+Math.max(650,container.clientHeight*.8),container.scrollHeight);else window.scrollTo(0,document.documentElement.scrollHeight);
 await new Promise(r=>setTimeout(r,1200));collect();
 const top=isFeed&&container?container.scrollTop:window.scrollY,count=videos.size;unchanged=count===lastCount&&top===lastTop?unchanged+1:0;lastCount=count;lastTop=top;
}
collect();
return JSON.stringify({sourceUrl:location.href.split('?')[0].split('#')[0],hashtag:location.pathname.match(/^\/tag\/([^/]+)/)?.[1]||null,query:new URL(location.href).searchParams.get('q'),requested:limit,collected:Math.min(videos.size,target),count:Math.min(videos.size,target),containerFound:!!container,videos:[...videos.values()].slice(0,target),filter:'excluded cards visibly marked restricted, private, unavailable, age-restricted, under review, or content unavailable'});
})()
  1. Select up to {limit} results whose visible caption, card text, creator context, link text, hashtag, or image alt is relevant to {topic}. For a For You request, relevance must be based on visible text or metadata supplied by the caller; do not infer it from hidden state or audiovisual interpretation. Preserve raw evidence and state that relevance is a visible-text or metadata signal.

    Tag and search cards often render no like or view count at all, so whether a post is actually viral cannot be settled from this page. To confirm view counts for the selected posts, use step 3 of Analyze TikTok Post Performance by Views on each returned url.

  2. If no qualifying public links are exposed, return an empty videos array and record the observed limitation. Do not reconstruct URLs from usernames, captions, wrapper IDs, or other non-URL content.

Possible Friction Points

  • TikTok topic pages are directly addressable through /tag/{hashtag} and /search?q={query}; the For You feed is directly addressable at /foryou.
  • Hashtags containing emoji or non-ASCII characters must be encoded with encodeURIComponent.
  • Search, hashtag, and For You results lazy-load and may virtualize cards. Accumulate canonical /video/{numeric-id} and /photo/{numeric-id} links during scrolling.
  • The For You feed commonly uses the scrollable #column-list-container and recommendation cards marked article[data-e2e="recommend-list-item-container"]. Some visible layouts additionally expose section[id^="media-card-"]; use it as a fallback when recommendation articles are absent.
  • Cards may expose /video/{numeric-id} or /photo/{numeric-id} links. Preserve the complete observed canonical URL after removing query strings and fragments.
  • Restriction filtering is evidence-based. Exclude only cards whose visible card text, title, image alt, or accessibility label contains an explicit restriction marker. Match status phrasing such as "age-restricted", "private video", or "content unavailable" rather than bare words: a public post captioned "private jet" or "unavailable this weekend" is otherwise dropped as restricted.
  • Engagement counts are read from TikTok's data-e2e attributes (like-count, comment-count, share-count) first, with the English aria-label kept only as a fallback. Matching Like video or Share video alone returns null on every non-English UI. A count is parsed out of whichever source is found, so a label carrying no digits yields null rather than the action name itself. Absent metrics remain null and must not be treated as zero.
  • The first surfaced cards are not guaranteed to be globally popular or representative. Report surfaced ordering and visible evidence only.
  • Consent, login prompts, regional restrictions, bot checks, or incomplete rendering can reduce discovery. Report the limitation rather than bypassing it.
  • Do not infer relevance from unavailable video content, hidden comments, hidden application state, or guessed URLs. Do not fabricate creator handles, IDs, or metrics.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=tiktok.com&task=find-viral-tiktok-videos-by-topic