一键导入
threads-search
Threads (threads.com) search & data extraction — cari post, extract comments/replies, scroll pagination. Browser-based via Patchright MCP.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Threads (threads.com) search & data extraction — cari post, extract comments/replies, scroll pagination. Browser-based via Patchright MCP.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
X.com (Twitter) search & data extraction — cari post dengan advanced operators, extract replies, scroll pagination. Browser-based via Patchright MCP.
Instagram automation via Patchright MCP — DM, like, comment, search, profile, get images. Browser-based (not API). Part of social-media-browser umbrella.
Threads (threads.com) automation via Patchright MCP — post, reply, like, repost, DM, search, profile. Column-based web UI, English. Part of social-media-browser umbrella.
X.com (Twitter) automation via Patchright MCP — post, reply, like, retweet, DM, search, profile. English UI. Part of social-media-browser umbrella.
基于 SOC 职业分类
| name | threads-search |
| description | Threads (threads.com) search & data extraction — cari post, extract comments/replies, scroll pagination. Browser-based via Patchright MCP. |
| triggers | ["cari di threads","search threads","threads search","research threads","cari data threads","threads-research","cari post threads","cari utas"] |
| tools | ["mcp_patchright_browser_navigate","mcp_patchright_browser_click","mcp_patchright_browser_snapshot","mcp_patchright_browser_run_code","mcp_patchright_browser_wait_for","mcp_patchright_browser_scroll","mcp_patchright_browser_tabs"] |
Search Threads (threads.com), extract posts with full reply/comment threads via Patchright MCP.
rickicode| Filter | URL |
|---|---|
| Top (default) | https://www.threads.com/search?q={query} |
| Recent | https://www.threads.com/search?q={query}&filter=recent |
| Profiles | https://www.threads.com/search?q={query}&filter=profiles |
| Author posts | https://www.threads.com/search?from_author={username} |
| Tag search | https://www.threads.com/search?q={tag}&serp_type=tags&tag_id={id} |
async (page) => {
await page.waitForTimeout(5000);
return await page.evaluate(() => {
const results = [];
const seen = new Set();
const links = document.querySelectorAll('a[href*="/post/"]');
for (const link of links) {
const postUrl = link.href;
if (seen.has(postUrl)) continue;
seen.add(postUrl);
// Walk up to find the containing post block
const container = link.closest('[data-pressable-container]') || link.closest('div[class*="post"]') || link.parentElement?.parentElement?.parentElement;
if (!container) continue;
const text = container.innerText || '';
const timeEl = container.querySelector('time');
const authorLink = container.querySelector('a[href^="/@"]');
results.push({
authorHandle: authorLink?.textContent?.trim() || '',
authorUrl: authorLink?.href || '',
text: text.split('\n').filter(l => l.length > 10).slice(0, 3).join(' | ').substring(0, 500),
postUrl,
timestamp: timeEl?.getAttribute('datetime') || '',
timeDisplay: timeEl?.textContent?.trim() || '',
});
}
return results;
});
}
Navigate to post URL, then extract:
async (page, postUrl) => {
await page.goto(postUrl);
await page.waitForTimeout(5000);
// Scroll to load more replies
for (let i = 0; i < 3; i++) {
await page.evaluate(() => window.scrollBy(0, 800));
await page.waitForTimeout(2000);
}
return await page.evaluate(() => {
const results = { post: null, replies: [] };
const items = document.querySelectorAll('[data-pressable-container="true"]');
for (const item of items) {
const links = item.querySelectorAll('a');
const authorLink = [...links].find(a => a.href?.includes('/@') && !a.href?.includes('/post/'));
const timeEl = item.querySelector('time');
const text = item.innerText || '';
// Parse engagement counts from buttons
const btns = item.querySelectorAll('button');
let likeCount = '', replyCount = '', repostCount = '';
for (const btn of btns) {
const label = btn.getAttribute('aria-label') || btn.textContent || '';
if (label.startsWith('Like') && /\d/.test(label)) likeCount = label.replace('Like', '').trim();
if (label.startsWith('Reply') && /\d/.test(label)) replyCount = label.replace('Reply', '').trim();
if (label.startsWith('Repost') && /\d/.test(label)) repostCount = label.replace('Repost', '').trim();
}
const entry = {
authorHandle: authorLink?.textContent?.trim() || '',
authorUrl: authorLink?.href || '',
text: text.split('\n').filter(l => l.length > 10).slice(0, 3).join(' | ').substring(0, 500),
postUrl: [...links].find(a => a.href?.includes('/post/'))?.href || '',
timestamp: timeEl?.getAttribute('datetime') || '',
timeDisplay: timeEl?.textContent?.trim() || '',
likes: likeCount,
replies: replyCount,
reposts: repostCount,
};
if (!results.post) {
results.post = entry;
} else if (entry.text.length > 5) {
results.replies.push(entry);
}
}
return results;
});
}
async (page, username) => {
await page.goto(`https://www.threads.com/@${username}`);
await page.waitForTimeout(5000);
for (let i = 0; i < 3; i++) {
await page.evaluate(() => window.scrollBy(0, 1000));
await page.waitForTimeout(2000);
}
return await page.evaluate(() => {
const followerText = document.body.innerText.match(/(\d[\d,.]*[KMB]?)\s*followers/i);
const postLinks = document.querySelectorAll('a[href*="/post/"]');
const seen = new Set();
const posts = [];
for (const link of postLinks) {
if (seen.has(link.href)) continue;
seen.add(link.href);
const container = link.closest('[data-pressable-container]') || link.parentElement?.parentElement;
const text = container?.innerText || '';
const timeEl = container?.querySelector('time');
posts.push({
text: text.split('\n').filter(l => l.length > 10).slice(0, 3).join(' | ').substring(0, 300),
postUrl: link.href,
timestamp: timeEl?.getAttribute('datetime') || '',
timeDisplay: timeEl?.textContent?.trim() || '',
});
}
return {
followers: followerText?.[1] || '',
posts
};
});
}
async (page, maxScrolls = 5) => {
let allPosts = [];
const seen = new Set();
for (let i = 0; i < maxScrolls; i++) {
const posts = await page.evaluate(() => {
const links = document.querySelectorAll('a[href*="/post/"]');
return [...links].map(l => ({
url: l.href,
text: (l.closest('[data-pressable-container]')?.innerText || '').substring(0, 100),
}));
});
for (const p of posts) {
if (p.url && !seen.has(p.url)) {
seen.add(p.url);
allPosts.push(p);
}
}
await page.evaluate(() => window.scrollBy(0, 1500));
await page.waitForTimeout(2000);
}
return { total: allPosts.length, posts: allPosts };
}
/@{user}/post/{shortcode}./messages/t/{thread_id}/ — different from Instagram.waitForTimeout(3000) between navigations.[data-pressable-container="true"] — first item is main post, rest are replies.cdninstagram domain.waitForTimeout(5000)./post/xxx/media URLs — deduplicate by base post URL./workspaces/patchright-browser/. Copy files there before uploading.fileChooser.setFiles() sometimes uploads 2 copies. Check Remove button count after upload.mcp.client.streamable_http.streamablehttp_client + ClientSession — NOT raw HTTP POST. See patchright-browser skill for full pattern.