关于 browser-screenshot
browser-screenshot 是用于 Agent Browser 的技能,可截取网页指定区域,而非整页内容。它覆盖页面调研、区域定位和裁剪截图,并使用专用持久化 Chrome 配置文件。
主要功能
- 01调研并验证候选网页
- 02使用专用 Chrome 配置文件导航
- 03通过 DOM 选择器定位区域
- 04截取裁剪后的屏幕图像
- 05避免输出全屏截图
适用场景
- 01截取特定 Reddit 帖子或推文
- 02为文章配图,截取段落或图表
- 03选择并截取相关网页区域
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ zc277584121/ marketing-skills --skill browser-screenshot安装后在 Claude Code 中输入/browser-screenshot确认已加载。
详细介绍
技能:浏览器截图
截取网页中特定区域的清晰截图,例如 Reddit 帖子、推文、文章章节、图表等,而不只是输出整页截图。
前提条件:必须已安装
agent-browser。此技能使用专用的有头 Chrome 和持久化配置文件;用户日常使用的 Chrome 无需启用远程调试。
专用浏览器配置文件
使用与 my-chrome-automation 相同的专用持久化配置文件约定:
PROFILE_DIR="${AGENT_BROWSER_PROFILE:-$HOME/.agent-browser-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\.1:[0-9]+$/ {split($9, parts, ":"); print parts[2]; exit}')"
ab() {
agent-browser --namespace "$NAMESPACE" --session "$SESSION" --cdp "$CDP_PORT" "$@"
}
连接前,必须确认 PROFILE_PID 和 CDP_PORT 均非空且无歧义。如果无法验证,请让用户关闭专用自动化窗口后重试。不要关闭仅供此任务连接使用的浏览器。
概述
此技能涵盖完整流程:
- 调研适合截图的网页(网页搜索、抓取)
- 导航至浏览器中的正确网页
- 定位页面上的目标元素或区域
- 截取仅包含该区域的清晰裁剪截图
硬性规则:禁止全屏截图
最终结果绝不能输出未经裁剪的全视口或整页截图。 全屏截图包含太多干扰信息(导航栏、侧边栏、广告、无关内容),不适合作为文章插图。每张截图都必须裁剪至重点区域。
步骤 0:调研——打开浏览器前查找并验证来源
浏览器用于截图,而非浏览网页。 在 Chrome 中打开任何内容之前,先使用基于文本的工具(WebSearch、WebFetch)查找候选网页、阅读其内容,并判断哪些网页确实值得截图。
先调研的工作流程
- 使用 WebSearch 查找与主题相关的候选网页
- 使用 WebFetch 阅读每个候选网页的文本内容——确认其中是否包含所需信息或视觉素材
- 评估:这个网页值得截图吗?它是否有清晰、集中的区域可用作插图?
- 只有完成以上步骤后,才打开浏览器截取截图
这样可以节省大量时间——大多数候选网页都不值得截图,因此可以避免浏览器导航的开销,直接将其排除。
何时应优先使用浏览器
遇到以下情况时,跳过 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 仓库 | 直接访问:huggingface.co/<org>/<model> |
| API 服务 | API 文档页面 | WebSearch "<service name>" API docs |
注意:此表列出了常见的主题类型,但并非详尽无遗。对任何主题类型都应采用同样的研究优先策略 — 为当前主题找到最权威、视觉效果最清晰的来源页面。
优质截图来源的特点
核心原则:少即是多。聚焦内容,而非页面框架。
优质截图来源应包含一段重点明确、内容完整的信息 — 一段文字、一句关键引述、一张数据表或一幅图表。页面不应充斥按钮、导航、侧边栏和交互元素。
- 优先选择:标题明确且包含 1-2 段文字的博客文章章节。单张图表或示意图。包含名称和描述的模型卡页眉。引述或关键发现。
- 避免选择:带有行动号召按钮(CTA)和导航栏的完整落地页。包含多个面板的仪表盘视图。页面主要由 UI 控件(按钮、下拉菜单、表单)构成,而非可读内容。
- 官方博客文章是理想选择:它们通常有主视觉图片、醒目的标题和简洁的描述,适合分享。
- 产品落地页也可以,但只能截取主视觉区域 — 忽略其余部分。
- HuggingFace 模型卡是查找开源模型的可靠来源:布局统一,模型名称和描述始终位于顶部。
- API 文档可作为备选:展示产品名称和关键规格。
经验法则:如果计划截取的区域中,交互式 UI 元素(按钮、链接、导航项)多于可读文本内容,那么这个裁剪区域就不理想。请寻找内容更丰富的区域,或改选其他页面。
打开前验证 URL
在浏览器中打开 URL 之前,使用 WebFetch(轻量级 HEAD/GET)进行验证,避免浪费时间处理 404 或重定向:
WebFetch: <candidate-url>
→ Check status code, title, and content snippet
→ If 404 or redirect to unrelated page, try next candidate
区域选择策略
思考一下文章读者需要在这张截图中看到什么:
| 文章上下文 | 要截取的内容 | 目标区域 |
|---|---|---|
| 介绍阵容中的某个模型 | 模型名称 + 关键标语/描述 | 博客主视觉区域或 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 格式 |
|---|---|
https://www.reddit.com/search/?q=<query> | |
| X / Twitter | https://x.com/search?q=<query> |
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 选择器
-
截取带注释的截图,了解页面布局:
ab screenshot --annotate -
获取快照,查看页面的无障碍树:
ab snapshot -i -
识别目标容器元素。查找:
- 语义化 HTML 容器:
<article>、<main>、<section> - 平台专用组件(见平台选择器)
- 数据属性:
[data-testid="..."]、[data-id="..."]
- 语义化 HTML 容器:
-
使用
get box验证,确认元素的边界框大小合理:ab get box "<selector>"这会返回
{ x, y, width, height }。请进行合理性检查:- 宽度应 > 100px 且 < 视口宽度
- 高度应 > 50px
- 如果边界框覆盖整个页面,说明选择器范围过宽——请进一步精确定位
-
如果难以找到选择器,使用
eval探查 DOM:ab eval "document.querySelector('article')?.getBoundingClientRect()"
平台选择器
常见平台的容器选择器:
| 平台 | 目标 | 常用选择器 |
|---|---|---|
| 帖子 | shreddit-post、[data-testid="post-container"] | |
| X / Twitter | 推文 | article[data-testid="tweet"] |
| 信息流帖子 | .feed-shared-update-v2 | |
| Hacker News | 故事 + 评论 | #hnmain .fatitem |
| GitHub | 仓库卡片 | [data-hpc]、.repository-content |
| YouTube | 视频播放器区域 | #player-container-outer |
| 通用文章 | 主要内容 | article、main、[role="main"]、.post-content、.article-body |
这些选择器可能会随时间变化。使用前请务必通过
get box验证。
多个匹配元素
如果选择器匹配多个元素(例如,时间线上有多条推文),请进一步缩小范围:
# Count matches
ab get count "article[data-testid='tweet']"
# Use nth-child or :first-of-type, or a more specific selector
# Or use eval to find the right one by text content:
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:滚动 + 视口截图(适用于视口大小的目标)
适用于目标元素能完整显示在视口中的情况。
# Scroll the target into view
ab scrollintoview "<selector>"
ab wait 500
# Take viewport screenshot
ab screenshot /tmp/browser-screenshot-raw.png
然后使用边界框裁剪(参见裁剪)。
方法 B:整页截图 + 裁剪(适用于任意大小的目标)
适用于目标可能大于视口,或需要精确裁剪的情况。
# Take full-page screenshot
ab screenshot --full /tmp/browser-screenshot-full.png
# Get the target element's bounding box
ab get box "<selector>"
# Output: { x: 200, y: 450, width: 680, height: 520 }
然后进行裁剪(参见裁剪)。
裁剪
使用 ImageMagick(IMv7 中为 magick,convert 已弃用)将屏幕截图裁剪到目标区域。添加留白,让画面看起来更舒展。
Retina 显示屏处理
至关重要:在 macOS Retina 显示屏上,屏幕截图会以 2 倍分辨率捕获。1728x940 的视口会生成 3456x1880 的图像。你必须考虑这一点:
-
检测缩放系数:比较视口大小与实际图像尺寸:
# Check actual image dimensions magick identify /tmp/screenshot.png # → 3456x1880 means 2x scale on a 1728x940 viewport -
裁剪前,将
get box坐标乘以缩放系数:# get box returns viewport coordinates: { x: 200, y: 450, width: 680, height: 520 } # For 2x Retina, actual image coordinates are: 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(视口 px)。如果目标有明显的视觉边界(卡片、有边框的框),可增加到约 30px。如果用户希望紧密裁剪,则使用 0。
输出路径
- 如果用户指定了输出路径,就使用该路径
- 否则,将文件保存到当前目录,并使用描述性名称,例如
reddit-post-screenshot.png、tweet-screenshot.png
第 4 步:验证结果
裁剪后,读取输出图像,确认截取到了正确的内容:
# Use the Read tool to visually inspect the cropped screenshot
如果裁剪有误(遗漏内容、留白过多、元素不正确),请调整选择器或边界框,然后重试。
后备方案:视觉高亮确认
当无法确定基于 DOM 的定位是否准确时——选择器可能有误、存在多个候选项,或目标不明确——请使用 JS 注入高亮,在裁剪前进行目视确认。
工作原理
-
为候选元素注入高亮边框:
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 -
截取屏幕截图并目视检查:
ab screenshot /tmp/highlight-check.png读取屏幕截图,确认红色边框是否框住了正确的内容。
-
如果正确,移除高亮并继续裁剪:
ab eval "document.querySelector('<selector>').style.outline = ''; document.querySelector('<selector>').style.outlineOffset = '';" -
如果不正确,尝试下一个候选项或优化选择器,重新高亮并再次检查。
何时使用此后备方案
- 页面包含复杂或嵌套的组件,你不确定哪个容器才是正确的
- 存在多个相似元素,需要选出正确的一个
- 用户的描述比较模糊(“页面中间的那个图表”)
get box的结果看起来可疑(过大、过小或尺寸为零)
页面准备:捕获前清理页面
拍摄最终屏幕截图前,先清理页面以获得更好的效果:
# Dismiss cookie banners, popups, overlays
ab eval --stdin <<'EOF'
(function() {
// Common cookie/popup selectors
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();
});
});
// Hide fixed/sticky elements that overlay content (nav bars, banners)
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)位于 shadow DOM 或 iframe 中,无法通过 JS click() 或 remove() 关闭。不要浪费时间反复尝试 JS。请改用以下方法:
- 将其裁剪掉——如果横幅位于顶部或底部,只需调整裁剪区域将其排除。这是最快、最可靠的方法。
- 滚动避开——在捕获前滚动页面,让目标内容离开横幅所在区域。
视口尺寸
为确保屏幕截图一致且质量良好,请在捕获前设置视口:
# Standard desktop viewport
ab set viewport 1280 800
# Wider for dashboard/data-heavy pages
ab set viewport 1440 900
# Narrower for mobile-like content (social media posts)
ab set viewport 800 600
选择合适的视口宽度,让目标内容清晰呈现——既不要太挤,也不要拉伸过度。
故障排除
get box 返回 null 或尺寸为零
- 选择器未匹配到任何元素。使用
get count "<selector>"进行验证。 - 元素可能处于隐藏状态或尚未渲染。尝试执行
wait 2000,然后重试。
裁剪后的图像为空白或区域不正确
- 整页截图的坐标可能与视口坐标不同。将
screenshot --full与get box配合使用(它们使用相同的坐标系)。 - 检查页面是否有水平滚动——
get box的 x 值可能会偏移。
目标元素位于 iframe 内
get box和snapshot -i无法查看 iframe 内部。- 使用
eval访问 iframe 内容:
注意:仅适用于同源 iframe。ab eval "document.querySelector('iframe').contentDocument.querySelector('<sel>').getBoundingClientRect()"
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