mcpField
X
xsearch

xsearch

github.com/monotykamary生产力471 次安装今天更新
代码仓库

关于 xsearch

xsearch 是通过 browser-harness-js 搜索 X 并提取结构化帖子结果的命令行技能,适合浏览器中已登录 X 的用户。支持格式化或 JSON 输出,也可并行搜索。

主要功能

  1. 01搜索 X 中的匹配帖子
  2. 02提取作者、账号、文本、链接和时间
  3. 03输出格式化结果或 JSON
  4. 04并行运行多项搜索
  5. 05通过 browser-harness-js 打开帖子链接

适用场景

  1. 01收集符合查询条件的近期帖子
  2. 02获取推文详情供后续处理
  3. 03并发搜索多个主题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/monotykamary/browser-harness-js --skill xsearch

安装后在 Claude Code 中输入/xsearch确认已加载。

附带文件scripts/setupscripts/xsearch

详细介绍

X 搜索

⚠️ 你必须在浏览器中登录 X。 X 的搜索页面不会向未登录的访客显示结果——而是会将其重定向到登录页面。browser-harness-js 使用的浏览器会话必须处于已登录 X 的状态。

通过 CDP 搜索 X(Twitter)并提取结构化结果。除提供 CDP 会话的 browser-harness-js 外,无需其他外部依赖。每次调用都会打开自己的标签页和 WebSocket 会话,可安全并行使用。

快速搜索

xsearch "your query"             # pretty-printed, up to 10 results
xsearch "your query" 5           # 5 results, pretty-printed
xsearch --json "your query" 3   # raw JSON

并行使用

每次 xsearch 调用都会复用共享 WebSocket,但会通过专属的 sessionId 连接到自己的标签页。标签页通过 Target.closeTarget 以即发即忘的方式关闭,因此调用方无需等待清理完成。

xsearch "rust async" 3 &
xsearch "go channels" 3 &
wait

结果格式

每条结果的格式为 { author, handle, text, url, time }:

[
  {
    "author": "Zane Chee",
    "handle": "@injaneity",
    "text": "people of pi, i'm excited to finally introduce browser use in pi-computer-use!",
    "url": "https://x.com/injaneity/status/2065110712511500620",
    "time": "2026-06-11T16:35:36.000Z"
  }
]

通过 URL 查看 X 帖子

直接通过 browser-harness-js 打开结果 url(或任何 x.com/<handle>/status/<id> 永久链接)——无需重新搜索。流程与临时搜索相同:连接、创建标签页并执行评估;复用 [data-testid="tweet"] 选择器,但只获取焦点帖子(在永久链接页面上,它是 DOM 顺序中的第一个):

browser-harness-js <<'EOF'
if (!session.isConnected()) {
  try { await session.connect() } catch (e) { throw new Error("Cannot connect: " + e.message) }
}

const url = "https://x.com/injaneity/status/2065110712511500620"
const t = await session.Target.createTarget({ url: "about:blank", background: true })
const { sessionId } = await session.Target.attachToTarget({ targetId: t.targetId, flatten: true })

try {
  await cdp(sessionId, "Page.enable", {})
  // Required — without this Chrome emits zero Page.lifecycleEvent, so networkIdle
  // would never fire.
  await cdp(sessionId, "Page.setLifecycleEventsEnabled", { enabled: true })
  // Arm the wait BEFORE Page.navigate: lifecycle events fire once, and a fast
  // load can fire networkIdle between navigate returning and the listener subscribing.
  const ready = session.waitFor({ method: 'Page.lifecycleEvent', sessionId, predicate: (p) => p.name === 'networkIdle', timeoutMs: 30_000 })
  await cdp(sessionId, "Page.navigate", { url })
  await ready
  await new Promise(r => setTimeout(r, 4000))   // React hydration; see Traps

  const result = await cdp(sessionId, "Runtime.evaluate", {
    expression: `(() => {
      const el = document.querySelector('[data-testid="tweet"]')
      if (!el) return JSON.stringify(null)
      const textEl = el.querySelector('[data-testid="tweetText"]')
      const timeEl = el.querySelector('time')
      const userNamesEl = el.querySelector('[data-testid="User-Name"]')
      const nameLinks = userNamesEl ? [...userNamesEl.querySelectorAll('a[role="link"]')] : []
      return JSON.stringify({
        author: nameLinks[0]?.textContent?.trim() || "",
        handle: nameLinks[1]?.textContent?.trim() || "",
        text: textEl?.innerText?.trim() || "",
        time: timeEl?.getAttribute('datetime') || ""
      })
    })()`,
    returnByValue: true
  })
  return result.result.value
} finally {
  session.closeTab(t.targetId, sessionId).catch(() => {})
}
EOF
  • 焦点帖子(即永久链接指向的帖子)是 DOM 顺序中第一个 [data-testid="tweet"];回复和引用的帖子会显示在它下方。
  • 4s 的水合等待时间与 xsearch 相同——networkIdle 会在 React 渲染帖子之前触发。未登录的访客访问永久链接时也会遇到登录页面,与搜索时相同。
  • 等待策略。 示例会等待 networkIdle(网络请求静默 500ms)——这是 X 页面和大多数内容网站的合适默认选项:它会在 load 之后触发,因此返回的内容至少一样多;而且不会像 loadEventFired 那样被一直挂起的广告/分析信标阻塞。针对特定页面类型的替代方案:
    • networkAlmostIdle(静默 250ms)——适用于持续进行 XHR 轮询、无法达到完整 500ms 静默的页面。
    • loadEventFired——确实需要加载所有子资源时使用(文本提取很少需要)。
    • 就绪后短暂等待:在 evaluate 之前执行 await new Promise(r => setTimeout(r, 1000))——适用于在 networkIdle 之后才延迟渲染内容的页面。

工作原理

步骤CDP 调用功能
1session.connect()(一次)将共享 WebSocket 连接到浏览器
2Target.createTarget({ background: true })创建一个隔离的后台标签页
3Target.attachToTarget获取用于标签页路由的专属 sessionId
4cdp(sessionId, "Page.enable", …)订阅页面事件
5cdp(sessionId, "Page.setLifecycleEventsEnabled", …)启用生命周期事件——若不启用,networkIdle 不会触发
6session.waitFor('Page.lifecycleEvent' networkIdle) 在 cdp(sessionId, "Page.navigate", …) 之前启动修复竞态:在导航前启动 networkIdle 等待(避免页面已加载的竞态),然后前往 x.com/search?q=…&src=typed_query&f=top
7setTimeout(4000)等待 React 水合并渲染帖子
8滚动循环(如果 count > 6)滚动以加载更多帖子(每次约 3 条)
9cdp(sessionId, "Runtime.evaluate", …)通过 data-testid 选择器进行单次 DOM 查询
10closeTab(即发即忘)拆除标签页,不阻塞响应

使用的 DOM 选择器

X 的搜索页面使用稳定的 data-testid 属性:

选择器用途
[data-testid="tweet"]帖子容器
[data-testid="tweetText"]帖子正文
[data-testid="User-Name"]作者名称/账号名区块
a[role="link"],位于 User-Name 内链接:[0]=名称,[1]=账号名,[2]=永久链接+时间戳
time通过 datetime 属性获取 ISO 时间戳

注意事项

  • 必须已登录。 X 不会向未登录的访客显示搜索结果,而是将其重定向到登录提示页面。浏览器会话必须处于已登录 X 的状态。
  • React 水合延迟。 networkIdle 会在 React 渲染推文之前触发。收到就绪信号后,必须等待 4s,初始批次(约 6 条推文)才会显示。
  • 滚动以加载更多结果。 X 使用无限滚动。初始视图包含约 6 条推文。当 count > 6 时,脚本会向下滚动(每次滚动加载约 3 条推文,并延迟 1.5s)。
  • 每个新标签页都必须调用 Page.enable() 和 Page.setLifecycleEventsEnabled({ enabled: true })。 后者是 Chrome 触发任何 Page.lifecycleEvent 的必要条件;如果不调用,networkIdle 等待每次都会超时。
  • networkIdle 等待的超时时间为 30s — 使用 session.waitFor() 而不是原始 promise,因此页面挂起时不会导致标签页泄漏。X 会持续加载,但实际使用中,初始网络请求通常会在约 2–3s 内平静下来;如果页面始终无法达到 500ms 的静默窗口,请改用 networkAlmostIdle(参见上文的等待策略说明)。
  • 结果数量可能少于请求数量 — X 可能没有足够的匹配推文,或者滚动循环未能及时加载这些推文。
  • 推文文本可能被截断 — 对于较长的推文,X 会显示“显示更多”按钮。提取的 text 是点击“显示更多”之前可见的文本。
  • 不依赖 jq — URI 编码在 JS 中使用 encodeURIComponent(),输出格式则通过 heredoc 中的 .map().join() 完成。

评论

支持 Markdown