browser-screenshot

browser-screenshot

使用专用的有头Chrome浏览器配置文件(持久化登录状态),从网页中截取聚焦于特定区域的截图。根据用户上下文(URL、搜索查询、社交媒体帖子)导航到正确的页面,通过DOM选择器定位目标区域,并裁剪出干净、聚焦的截图,无需附加到用户的日常浏览器。

0Star
0Fork
更新于 2026/7/22
SKILL.md
readonly只读
name
browser-screenshot
description

使用专用的有头Chrome浏览器配置文件(持久化登录状态),从网页中截取聚焦于特定区域的截图。根据用户上下文(URL、搜索查询、社交媒体帖子)导航到正确的页面,通过DOM选择器定位目标区域,并裁剪出干净、聚焦的截图,无需附加到用户的日常浏览器。

技能:浏览器截图

截取网页上特定区域的聚焦截图——Reddit帖子、推文、文章段落、图表等——而不仅仅是全页面截图。

前提条件:必须安装 agent-browser。此技能使用其自己的有头Chrome浏览器和专用持久化配置文件;用户的日常Chrome不需要远程调试。

专用浏览器配置文件

使用与 my-chrome-automation 相同的专用持久化配置文件约定:

PROFILE_DIR="${AGENT_BROWSER_PROFILE:-$HOME/.agent-browser-recorder-chrome}"
NAMESPACE="${NAMESPACE:-browser-screenshot}"
SESSION="${SESSION:-focused-capture}"

ab() {
  agent-browser --namespace "$NAMESPACE" --session "$SESSION" --headed --profile "$PROFILE_DIR" "$@"
}

所有浏览器命令都通过 ab 运行。不要使用 --auto-connect、通用CDP发现或用户的日常Chrome配置文件。专用配置文件在任务之间保留Cookie和登录状态,同时允许用户的正常Chrome保持打开且不受影响。

在可行的情况下,为任务选择一个描述性的 SESSION。不要同时对同一个配置文件运行多个任务;按顺序重用以避免配置文件锁定冲突。

如果目标网站需要登录,使用 ab 打开它,暂停让用户在专用的有头窗口中完成登录或验证,然后继续使用相同的配置文件和会话。

如果专用配置文件已在运行

使用相同配置文件启动第二个Chrome可能会在创建DevTools端点之前退出。切勿回退到任何恰好打开的Chrome。要么重用此配置文件的已知Agent Browser会话,要么仅显式附加到命令行中包含确切 --user-data-dir=$PROFILE_DIR 值的Chrome进程。

在macOS上,像这样识别该专用进程及其本地CDP端口:

PROFILE_PID="$(ps -axo pid=,command= | awk -v profile="--user-data-dir=$PROFILE_DIR" 'index($0, profile) && index($0, "--remote-debugging-port=") && $0 !~ /Helper/ {print $1; exit}')"
CDP_PORT="$(lsof -nP -a -p "$PROFILE_PID" -iTCP -sTCP:LISTEN | awk 'NR > 1 && $9 ~ /^127\.0\.0\.[0-9]+:[0-9]+$/ {split($9, parts, ":"); print parts[2]; exit}')"

ab() {
  agent-browser --namespace "$NAMESPACE" --session "$SESSION" --cdp "$CDP_PORT" "$@"
}

在附加之前,要求非空且明确的 PROFILE_PIDCDP_PORT 值。如果无法验证,请用户关闭专用自动化窗口并重试。不要关闭此任务仅附加到的浏览器。


概述

此技能处理完整的流程:

  1. 研究最适合截图的页面(网络搜索、获取)
  2. 导航到浏览器中的正确页面
  3. 定位页面上的目标元素/区域
  4. 捕获该区域的聚焦裁剪截图

硬性规则:禁止全屏截图

切勿将未裁剪的全视口或全页面截图作为最终结果输出。 全屏截图包含太多噪音(导航栏、侧边栏、广告、无关内容),不适合作为文章插图。每个截图必须裁剪到聚焦区域。


步骤0:研究——在打开浏览器之前查找并验证来源

浏览器用于捕获,而非浏览。 在Chrome中打开任何内容之前,使用基于文本的工具(WebSearch、WebFetch)查找候选页面,阅读其内容,并决定哪些页面值得截图。

研究优先的工作流程

  1. WebSearch 查找主题的候选页面
  2. WebFetch 每个候选页面以阅读其文本内容——检查它是否包含所需的信息/视觉元素
  3. 评估:这个页面值得截图吗?它是否有清晰、聚焦的区域可以作为插图?
  4. 然后打开浏览器捕获截图

这可以节省大量时间——大多数候选页面不值得截图,你可以在没有浏览器导航开销的情况下排除它们。

何时使用浏览器优先

在以下情况下跳过WebSearch/WebFetch阶段,直接进入Chrome浏览:

  • 目标平台需要登录——Reddit、LinkedIn、X/Twitter和其他社交平台通常将内容隐藏在登录墙后。直接使用专用配置文件,以便重用其保存的登录状态。
  • 用户指定了具有明确搜索需求的平台——例如,“查找关于X的Reddit帖子”或“截取关于Y的推文”。直接在Chrome中进入平台的搜索。
  • WebFetch返回被阻止或不完整的内容——某些网站积极阻止非浏览器请求。如果收到403、CAPTCHA页面或剥离的内容,切换到Chrome。

在这些情况下,Chrome浏览取代WebSearch——导航到平台的搜索页面,浏览结果,并在决定截图内容之前视觉评估页面。

页面选择策略

正确的页面取决于文章的上下文以及主题的新近度/知名度:

主题类型 最佳查找页面 如何查找
新模型/功能发布(< 6个月) 官方博客文章宣布它 WebSearch "<model name>" site:<vendor-domain> blog
成熟产品(> 6个月) 产品登陆页面或文档概述 WebSearch "<model name>" official page
开源模型 HuggingFace模型卡或GitHub仓库 直接URL:huggingface.co/<org>/<model>
API服务 API文档页面 WebSearch "<service name>" API docs

注意:此表列出了常见的主题类型,但并非详尽无遗。对任何主题类型应用相同的研究优先策略——为手头主题找到最权威且视觉上最干净的源页面。

好的截图来源的特征

核心原则:少即是多。聚焦内容,而非界面。

好的截图来源包含聚焦、自包含的信息片段——一段文本、一个关键引用、一个数据表、一个图表。它不应该是充满按钮、导航、侧边栏和交互元素的繁忙页面。

  • 首选:博客文章的一个段落,带有清晰的标题和1-2段文本。单个图表或图示。模型卡标题,包含名称和描述。引用或关键发现。
  • 避免:带有CTA和导航的完整登陆页面。包含多个面板的仪表板视图。由UI控件(按钮、下拉菜单、表单)主导而非可读内容的页面。
  • 官方博客文章是理想的:它们有英雄图像、突出的标题和为分享设计的简洁描述。
  • 产品登陆页面可以工作,但仅当裁剪到英雄部分时——忽略其余部分。
  • HuggingFace模型卡对于开源模型是可靠的:布局一致,模型名称+描述始终在顶部。
  • API文档是可接受的备选:显示产品名称和关键规格。

经验法则:如果你计划捕获的区域包含的交互式UI元素(按钮、链接、导航项)多于可读文本内容,则这是一个糟糕的裁剪。寻找内容更丰富的区域,或选择完全不同的页面。

预检URL验证

在浏览器中打开之前,使用WebFetch(轻量级HEAD/GET)验证URL,避免浪费时间在404或重定向上:

WebFetch: <candidate-url>
→ 检查状态码、标题和内容片段
→ 如果404或重定向到无关页面,尝试下一个候选

区域选择策略

思考文章读者需要在此截图中看到什么

文章上下文 要捕获的内容 目标区域
在系列中介绍模型 模型名称+关键标语/描述 博客英雄部分或HF模型卡标题
比较能力 功能亮点或规格表 显示规格/功能的博客部分
讨论特定功能 功能描述 相关部分标题+1-2段
展示产品/服务 品牌标识+价值主张 登陆页面英雄(标题+副标题+视觉元素)

截图应让读者想到“啊,这就是这个模型/产品”——而不是“我在看什么?”


步骤1:导航到目标页面

始终从列出标签页开始

ab tab list

检查页面是否已在专用会话中打开。当它们具有正确的登录和页面状态时,重用其现有标签页。

按输入类型导航

用户提供 策略
直接URL ab open <url>
搜索查询 ab open https://www.google.com/search?q=<encoded-query> → 查找并点击最佳结果
平台+主题 构建平台搜索URL(见下文)→ 定位目标内容
模糊描述 Google搜索→评估结果→导航到最佳匹配

特定平台的搜索URL

平台 搜索URL模式
Reddit https://www.reddit.com/search/?q=<query>
X / Twitter https://x.com/search?q=<query>
LinkedIn https://www.linkedin.com/search/results/content/?keywords=<query>
Hacker News https://hn.algolia.com/?q=<query>
GitHub https://github.com/search?q=<query>
YouTube https://www.youtube.com/results?search_query=<query>

等待页面加载

导航后,等待内容稳定:

ab wait --load networkidle

注意:某些网站(Reddit、X、LinkedIn)永远不会达到 networkidle。如果 open 的输出已显示页面标题,则跳过等待。使用 wait 2000 作为安全替代。


步骤2:定位目标区域

这是关键步骤。目标是找到一个CSS选择器,精确包裹要捕获的内容。

主要方法:DOM选择器发现

  1. 拍摄带注释的截图以了解页面布局:

    ab screenshot --annotate
    
  2. 拍摄快照以查看页面的无障碍树:

    ab snapshot -i
    
  3. 识别目标容器元素。查找:

    • 语义HTML容器:<article><main><section>
    • 特定平台的组件(见平台选择器
    • 数据属性:[data-testid="..."][data-id="..."]
  4. 使用 get box 验证以确认元素具有合理的边界框:

    ab get box "<selector>"
    

    返回 { x, y, width, height }。合理性检查:

    • 宽度应 > 100px 且 < 视口宽度
    • 高度应 > 50px
    • 如果框是整个页面,则选择器太宽泛——细化它
  5. 如果难以找到选择器,使用 eval 探索DOM:

    ab eval "document.querySelector('article')?.getBoundingClientRect()"
    

平台选择器

流行平台的常见容器选择器:

平台 目标 典型选择器
Reddit 帖子 shreddit-post[data-testid="post-container"]
X / Twitter 推文 article[data-testid="tweet"]
LinkedIn 动态帖子 .feed-shared-update-v2
Hacker News 故事+评论 #hnmain .fatitem
GitHub 仓库卡片 [data-hpc].repository-content
YouTube 视频播放器区域 #player-container-outer
通用文章 主要内容 articlemain[role="main"].post-content.article-body

这些选择器可能随时间变化。使用前始终用 get box 验证。

多个匹配元素

如果选择器匹配多个元素(例如,时间线上的多条推文),缩小范围:

# 计数匹配
ab get count "article[data-testid='tweet']"

# 使用nth-child或:first-of-type,或更具体的选择器
# 或使用eval按文本内容找到正确的元素:
ab eval --stdin <<'EOF'
const posts = document.querySelectorAll('article[data-testid="tweet"]');
for (let i = 0; i < posts.length; i++) {
  const text = posts[i].textContent.substring(0, 80);
  console.log(i, text);
}
EOF

然后使用 :nth-of-type(N) 或唯一的父选择器定位特定元素。


步骤3:捕获聚焦截图

方法A:滚动+视口截图(适用于视口大小的目标)

当目标元素适合视口时最佳。

# 将目标滚动到视图中
ab scrollintoview "<selector>"
ab wait 500

# 拍摄视口截图
ab screenshot /tmp/browser-screenshot-raw.png

然后使用边界框裁剪(见裁剪)。

方法B:全页面截图+裁剪(适用于任何大小的目标)

当目标可能大于视口或需要精确裁剪时最佳。

# 拍摄全页面截图
ab screenshot --full /tmp/browser-screenshot-full.png

# 获取目标元素的边界框
ab get box "<selector>"
# 输出:{ x: 200, y: 450, width: 680, height: 520 }

然后裁剪(见裁剪)。

裁剪

使用ImageMagick(IMv7使用 magickconvert 已弃用)将截图裁剪到目标区域。添加内边距以获得视觉呼吸空间。

Retina显示处理

关键:在macOS Retina显示屏上,截图以2倍分辨率捕获。1728x940的视口产生3456x1880的图像。你必须考虑这一点:

  1. 检测缩放因子:比较视口大小与实际图像尺寸:

    # 检查实际图像尺寸
    magick identify /tmp/screenshot.png
    # → 3456x1880 表示在1728x940视口上为2倍缩放
    
  2. 在裁剪前将 get box 坐标乘以缩放因子

    # get box 返回视口坐标:{ x: 200, y: 450, width: 680, height: 520 }
    # 对于2倍Retina,实际图像坐标为:
    SCALE=2
    X=$((200 * SCALE))
    Y=$((450 * SCALE))
    W=$((680 * SCALE))
    H=$((520 * SCALE))
    PADDING=$((16 * SCALE))
    
裁剪命令
magick /tmp/browser-screenshot-full.png \
  -crop $((W + PADDING*2))x$((H + PADDING*2))+$((X - PADDING))+$((Y - PADDING)) \
  +repage \
  <output-path>.png

重要get box 返回浮点值。在传递给ImageMagick之前将其四舍五入为整数。

内边距:使用12–20px(视口像素)。如果目标具有明显的视觉边界(卡片、带边框的框),增加到约30px。如果用户想要紧密裁剪,使用0。

输出路径

  • 如果用户指定了输出路径,使用该路径
  • 否则,保存到当前目录中的描述性名称,例如 reddit-post-screenshot.pngtweet-screenshot.png

步骤4:验证结果

裁剪后,读取输出图像以验证它捕获了正确的内容:

# 使用Read工具视觉检查裁剪后的截图

如果裁剪错误(遗漏内容、空白过多、元素错误),调整选择器或边界框并重试。


备选方案:视觉高亮确认

当基于DOM的定位不确定时——选择器可能错误、存在多个候选或目标模糊——使用JS注入的高亮在裁剪前进行视觉确认。

工作原理

  1. 在候选元素上注入高亮边框

    ab eval --stdin <<'EOF'
    (function() {
      const el = document.querySelector('<selector>');
      if (!el) { console.log('NOT_FOUND'); return; }
      el.style.outline = '4px solid red';
      el.style.outlineOffset = '2px';
      el.scrollIntoView({ block: 'center' });
    })();
    EOF
    
  2. 拍摄截图并视觉检查:

    ab screenshot /tmp/highlight-check.png
    

    读取截图以检查红色边框是否包围了正确的内容。

  3. 如果正确,移除高亮并继续裁剪:

    ab eval "document.querySelector('<selector>').style.outline = ''; document.querySelector('<selector>').style.outlineOffset = '';"
    
  4. 如果错误,尝试下一个候选或细化选择器,重新高亮并重新检查。

何时使用此备选方案

  • 页面具有复杂/嵌套的组件,你不确定哪个容器是正确的
  • 存在多个相似元素,你需要选择正确的那个
  • 用户的描述模糊(“页面中间的那个图表”)
  • get box 结果看起来可疑(太大、太小、零大小)

页面准备:捕获前清理

在拍摄最终截图之前,清理页面以获得更好的结果:

# 关闭Cookie横幅、弹窗、覆盖层
ab eval --stdin <<'EOF'
(function() {
  // 常见的Cookie/弹窗选择器
  const selectors = [
    '[class*="cookie"] button',
    '[class*="consent"] button',
    '[class*="banner"] [class*="close"]',
    '[class*="modal"] [class*="close"]',
    '[class*="popup"] [class*="close"]',
    '[aria-label="Close"]',
    '[data-testid="close"]'
  ];
  selectors.forEach(sel => {
    document.querySelectorAll(sel).forEach(el => {
      if (el.offsetParent !== null) el.click();
    });
  });

  // 隐藏覆盖内容的固定/粘性元素(导航栏、横幅)
  document.querySelectorAll('*').forEach(el => {
    const style = getComputedStyle(el);
    if ((style.position === 'fixed' || style.position === 'sticky') && el.tagName !== 'HTML' && el.tagName !== 'BODY') {
      el.style.display = 'none';
    }
  });
})();
EOF

谨慎使用:隐藏固定元素可能会移除重要上下文。仅当覆盖层明显遮挡目标区域时运行此操作。

无法关闭的Cookie横幅

某些Cookie同意横幅(例如Jina AI的Usercentrics)位于影子DOM或iframe中,无法通过JS click()remove() 关闭。不要浪费多次JS尝试的时间。相反:

  1. 裁剪掉它——如果横幅在顶部或底部,只需调整裁剪区域以排除它。这是最快且最可靠的方法。
  2. 滚动过去——在捕获前将目标内容滚动离开横幅区域。

视口大小设置

为了获得一致的高质量截图,在捕获前设置视口:

# 标准桌面视口
ab set viewport 1280 800

# 更宽,适用于仪表板/数据密集型页面
ab set viewport 1440 900

# 更窄,适用于类似移动端的内容(社交媒体帖子)
ab set viewport 800 600

选择使目标内容清晰渲染的视口宽度——不要太拥挤,也不要太拉伸。


故障排除

get box 返回null或零大小

  • 选择器不匹配任何元素。使用 get count "<selector>" 验证。
  • 元素可能隐藏或尚未渲染。尝试 wait 2000 并重试。

裁剪后的图像为空白或区域错误

  • 全页面截图坐标可能与视口坐标不同。使用 screenshot --full 配合 get box(它们使用相同的坐标系)。
  • 检查页面是否有水平滚动——get box 的x值可能偏移。

目标元素在iframe内

  • get boxsnapshot -i 无法看到iframe内部。
  • 使用 eval 访问iframe内容:
    ab eval "document.querySelector('iframe').contentDocument.querySelector('<sel>').getBoundingClientRect()"
    
    注意:仅适用于同源iframe。

open 成功但页面内容错误

  • 浏览器可能已切换到不同的标签页(例如,弹窗或重定向打开了新标签页)。导航后始终验证:
    ab eval "document.location.href"
    
  • 如果URL错误,使用 tab list 找到正确的标签页,并使用 tab goto <N> 切换。

截图命令在字体上超时

  • 某些页面(例如Google开发者文档)在 document.fonts.ready 上挂起。首先强制解析它:
    ab eval "document.fonts.ready.then(() => 'ok')"
    
    然后重试截图。

页面有懒加载内容

  • 在拍摄截图前向下滚动以触发加载:
    ab scroll down 1000
    ab wait 1500
    ab scroll up 1000