基于 ego-browser 的 X(Twitter)时间线导航与帖子提取实战指南
基于 ego-browser 的 XTwitter时间线导航与帖子提取实战指南【免费下载链接】ego-liteThe fastest browser for AI agents to run browser automation, built for sharing your logged-in browser state with your AI agents, like Codex or Claude Code, without disturbing you. Zero cost, zero config.项目地址: https://gitcode.com/GitHub_Trending/eg/ego-lite导读本文以 ego-browser 项目中针对 XTwitter的站点学习笔记 timeline.md 为核心骨架系统讲解如何在真实浏览器中完成 X 时间线的滚动加载、帖子批量提取与置顶帖过滤。你将掌握 X 页面的稳定测试选择器data-testid体系、浏览器端一次性遍历的提取范式以及该笔记如何通过站点学习Site Learning机制被自动注入到 ego-browser 的工具链中直接调用。一、认识这份站点学习笔记它在 ego-browser 中扮演什么角色ego-browser 为 AI Agent 提供了一套名为站点学习Learnings的机制针对特定网站沉淀的 DOM 结构经验、常用选择器和可直接调用的工具会被组织成一个个站点技能Site Skill目录。XTwitter的技能目录位于 skills/ego-browser/learnings/x-com/其清单文件 manifest.json 声明了domainsx.com、*.x.com、twitter.com、*.twitter.com用于将 URL 自动匹配到本技能notesnotes/overview.md页面整体结构与notes/timeline.md时间线导航专项即本文主角nodeToolsget_timeline_postsNode 侧工具对应 tools/timeline.jsbrowserToolspost_from_active_element浏览器侧工具对应 browser-tools/extract-post.js。timeline.md 记录的是 X 时间线这一最具代表性场景的实操经验从滚动到提取再到置顶帖过滤自成一套完整方法论。二、时间线滚动惰性加载与虚拟列表下的正确姿势timeline.md 第一条经验是滚动时间线Scrolling timeline它给出三个关键事实时间线位于[data-testidprimaryColumn]这是 X 主内容列的统一容器登录后首页、关注流、话题页的时间线都在其中帖子随滚动惰性加载X 采用滚动加载策略需要反复滚动才能拉取更多内容。笔记给出的方式是page.mouse.wheel(0, 1000)连续触发真实鼠标滚轮事件虚拟列表渲染DOM 中只渲染可视区域的 article 加上缓冲区buffer远未滚动到的帖子并不存在于 DOM 中因此先滚动、再提取是必须的顺序。在 ego-browser 的运行时中这一步有更顺手的封装。根据 SKILL.md 的 Scroll / mouse 一节滚动有三类方式// 方式一DOM 滚动scrollBy适合普通页面 await scrollBy(900) // 方式二带终止条件的滚动滚动到条件满足为止 await scrollToBottomUntil( async () await js(String.rawdocument.querySelectorAll(article).length) 20, { step: 900, wait: 1, maxSteps: 20 } ) // 方式三真实滚轮事件scroll与笔记中 page.mouse.wheel 等价对惰性加载更友好 await scroll({ dy: 900 })scrollToBottomUntil特别适合 X 时间线这种不知道要滚多远的场景你可以把终止条件设置为已加载到目标数量的帖子例如累计出现 20 个article元素滚动器会以step: 900的步长、每步等待wait: 1秒、最多执行maxSteps: 20步直到条件成立。注意 ego-browser 中wait与timeout的单位是秒只有参数名以Ms结尾的才是毫秒。三、帖子提取data-testid 选择器体系与字段映射timeline.md 的核心章节是 Extracting posts给出了 X 帖子 DOM 的关键锚点数据选择器说明帖子容器[data-testidtweet]每个帖子含置顶帖、广告、推荐帖都是一个 tweet 节点正文内容[data-testidtweetText]帖文纯文本通常取innerText作者信息[data-testidUser-Name]内部含用户名span与主页链接a时间戳time[datetime]X 的time元素带 ISO 8601 格式的datetime属性这些锚点与同目录的 overview.md 互为印证overview.md 还补充了搜索框[data-testidSearchBox_Search_Input]、发帖文本框[data-testidtwaCreateTweetTextarea]等其他常用选择器。3.1 实践落地getTimelinePosts 工具源码逐行解读timeline.js 将笔记中的选择器经验直接固化为可复用工具其实现展示了标准范式function boundedInteger(value, fallback, max) { const number value undefined ? fallback : Number(value); if (!Number.isFinite(number)) return fallback; return Math.max(1, Math.min(max, Math.trunc(number))); } export async function getTimelinePosts(ctx, args {}) { const maxPosts boundedInteger(args.maxPosts, 50, 100); const posts await ctx.page .locator([data-testidtweet]) .evaluateAll((articles, limit) { return articles.slice(0, limit).map((el) ({ text: el.querySelector([data-testidtweetText])?.innerText?.trim() || , author: el.querySelector([data-testidUser-Name]) ?.querySelector(span)?.innerText?.trim() || , handle: el.querySelector([data-testidUser-Name]) ?.querySelector(a)?.getAttribute(href) || , timestamp: el.querySelector(time)?.getAttribute(datetime) || , })); }, maxPosts); return posts; }值得注意的实现细节参数边界约束maxPosts默认 50上限 100经boundedInteger清洗后必须是1 ~ 100的整数非法输入回退到默认值。在 manifest.json 中该参数被声明为可选required: false的integer类型工具调用方可按需传入字段兜底每个字段都使用可选链?.加|| 兜底避免广告帖、媒体帖等 DOM 结构不完整时抛异常一次提取locator.evaluateAll在浏览器端一次性完成全部 DOM 查询Node 侧只拿结果数组这正是笔记中单次遍历提取原则的工程化体现。3.2 单条提取从聚焦元素出发的 browserTool当只需要当前聚焦的那一条帖子时extract-post.js 提供了更轻量的方案async function(args) { const el document.querySelector([data-testidtweet]:focus) || document.querySelector([data-testidtweet]); if (!el) return { error: no active tweet found }; return { text: el.querySelector([data-testidtweetText])?.innerText?.trim() || , author: el.querySelector([data-testidUser-Name] span)?.innerText?.trim() || , timestamp: el.querySelector(time)?.getAttribute(datetime) || , }; }它优先取带:focus状态的 tweet通常是 Agent 刚点击过的那条找不到聚焦元素时回退到页面上第一个 tweet。该文件在 manifest 中注册为browserTools.post_from_active_element由 learning/index.ts 的loadBrowserToolSource读取源码、wrapBrowserTool包装为自执行函数注入页面执行。四、置顶帖过滤理解第一个元素的边界条件timeline.md 的 Pinned post 一节指出一个易踩的坑置顶帖是时间线中第一个[data-testidtweet]过滤置顶帖时只有当时间线位于顶部时才能跳过第一个元素。原因在于 X 的虚拟列表滚动之后 DOM 会重新渲染置顶帖可能已被移除出渲染范围此时第一个 tweet未必是置顶帖。因此正确的判断逻辑是先确认滚动位置处于时间线顶端例如通过scrollBy(0)或检查滚动容器的scrollTop再决定是否对提取结果slice(1)。如果只是简单地永远跳过第一个在滚动后的时间线上会错误地丢弃一条真实帖子。五、串联实战在 heredoc 中完成一次完整的时间线采集将上述经验组合进 ego-browser 的 heredoc 工作流详见 SKILL.md一次打开 X 首页 → 滚动加载 → 提取帖子 → 过滤置顶帖的完整任务可以这样写ego-browser nodejs EOF // 复用/创建任务空间跨轮次保持页面状态 const task await useOrCreateTaskSpace(scan x timeline) cliLog(task space id: task.id) // 打开首页并等待加载 await openOrReuseTab(https://x.com/home, { wait: true, timeout: 20 }) // 1) 滚动加载真实滚轮事件 终止条件加载出 20 条帖子为止 await scrollToBottomUntil( async () await js(String.rawdocument.querySelectorAll([data-testidtweet]).length) 20, { step: 1000, wait: 1, maxSteps: 30 } ) // 2) 单次遍历提取全部逻辑收敛在浏览器端一个闭包内 const rawPosts await js(String.raw(() { return [...document.querySelectorAll([data-testidtweet])].map(el ({ text: el.querySelector([data-testidtweetText])?.innerText?.trim() || , author: el.querySelector([data-testidUser-Name] span)?.innerText?.trim() || , timestamp: el.querySelector(time)?.getAttribute(datetime) || , })) })()) // 3) 置顶帖过滤仅在时间线顶部时跳过第一个元素 const posts rawPosts.length 1 ? rawPosts.slice(1) : rawPosts cliLog(JSON.stringify(posts, null, 2)) EOF这里有三条与笔记强相关的工程约束js() 只接受字符串推荐显式(() { ... })()自执行闭包、一次return不要像 Puppeteer/Playwright 的page.evaluate(fn, args)那样传函数与参数SKILL.md 的 js 一节对此有明确说明代码在 heredoc 体内运行于 Node.jsdocument/window及页面选择器必须放进js(...)字符串中输出统一走cliLog(...)它是 heredoc 内唯一的结果输出通道。任务结束后记得用独立的 heredoc 调用await completeTaskSpace(task.id, { keep: false })收尾。六、底层机制这份笔记如何被加载与校验timeline.md 之所以能直接指导 Agent 行为靠的是 ego-browser 学习子系统的加载与校验链路域名匹配check-domain-learning.ts 的siteSkillsForUrl遍历全部站点学习目录读取各manifest.json用domainMatches将当前 URL 的 hostname 与domains里的通配模式*.x.com匹配foo.x.com比对命中即返回该站点技能笔记加载learning/index.ts 的loadLearnedContext只接受位于notes/*.md的笔记文件isLearningNotePath限定两层路径结构读取出内容作为LearnedKnowledgeNote注入上下文同时对每个工具生成调用签名Node 工具形如await site.runTool(x-com, get_timeline_posts, {...})浏览器工具形如await site.runBrowserTool(x-com, post_from_active_element, {...})格式校验validate-learning-format.ts 会在提交时校验整个学习目录id必须与目录名一致、domains非空、笔记必须是notes/*.md、Node 工具路径必须是tools/*.js且声明的callable真实存在、浏览器工具路径必须是browser-tools/*.js它还会用正则(?:\d\b|\bref\d\b)拒绝笔记与工具文件中的临时快照引用N/refN强制使用稳定的locator选择器——这正是 timeline.md 全部使用data-testid稳定选择器、而非快照编号的原因。七、局限与适用前提本文所述选择器primaryColumn、tweet、tweetText、User-Name基于 X 当前 DOM 结构沉淀X 改版时需同步更新 overview.md 与 timeline.md时间线采集依赖登录态ego-browser 的任务空间默认继承用户登录状态见 SKILL.md 的 Task spaces 一节因此上述流程直接在有登录态的浏览器环境中运行即可maxPosts上限 100 是 timeline.js 内置的防呆边界需要更多数据时应先滚动加载而不是盲目加大提取量。【免费下载链接】ego-liteThe fastest browser for AI agents to run browser automation, built for sharing your logged-in browser state with your AI agents, like Codex or Claude Code, without disturbing you. Zero cost, zero config.项目地址: https://gitcode.com/GitHub_Trending/eg/ego-lite创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考