
Web Access
- 12.1k installs
- 71 repo stars
- Updated April 11, 2026
- eze-is/eze-skills
web-access is a CLI skill that provides browser automation and web scraping via smart channel selection (WebSearch, Jina, Browser CDP), with support for login flows and parallel multi-agent browsing
About
A skill for navigating the web programmatically, covering search, page scraping, login flows, and browser automation. Developers invoke it for any networked task - from API discovery to extracting dynamic content from protected pages. Differs from generic browser tools by emphasizing human-like browsing philosophy and channel-awareness - picking the lightest tool that reaches the target rather than over-using heavy browsers.
- Handles search, web scraping, login operations, and dynamic page rendering
- Supports browser CDP mode with parallel multi-agent browsing on different ports
- Smart channel selection: WebSearch for discovery, Jina for pages, CDP for dynamic/auth content
Web Access by the numbers
- 12,110 all-time installs (skills.sh)
- Ranked #29 of 550 CLI & Terminal skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Jul 13, 2026 (Skillselion catalog sync)
web-access capabilities & compatibility
- Works with
- chrome
- Use cases
- web scraping · web search · api development
- Runs
- Local or remote
What web-access says it does
所有联网操作必须通过此 skill 处理,包括:搜索、网页抓取、登录后操作、网络交互等
支持多 sub-agent 集群并行使用多个浏览器
npx skills add https://github.com/eze-is/eze-skills --skill web-accessAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 12.1k |
|---|---|
| repo stars | ★ 71 |
| Security audit | 0 / 3 scanners passed |
| Last updated | April 11, 2026 |
| Repository | eze-is/eze-skills ↗ |
How do coding agents browse the web safely?
Web scraping, browser automation, dynamic content fetching, authentication flows, and parallel research tasks across multiple websites
Who is it for?
Developers needing to extract dynamic content, handle login flows, automate multi-page workflows, or run parallel web research across sites
Skip if: Simple static HTML scraping (use lightweight fetch-based tools); real-time trading or high-frequency data collection
When should I use this skill?
User requests search, page content extraction, form filling, login operations, or social media scraping; agent needs browser interaction with session persistence
What you get
Configured agent web access with search results, scraped page content, persistent browser login sessions, and parallel sub-agent browser instances.
- configured web access
- scraped page content
- persistent login browser sessions
By the numbers
- Supports 20 requests/minute via Jina with JS rendering
- CDP headless mode spawns true Chrome process per agent
- Three core judgment gates: target info needed, is it enough, how to handle barriers
Files
web-access Skill
首次安装
用户首次使用时,执行以下流程:
Step 1:运行环境探测
bash ~/.claude/skills/web-access/scripts/check-deps.shStep 2:AI 根据输出处理缺失依赖
探测脚本只报告事实,安装决策由 AI 完成。缺什么装什么,Chrome 缺失时提示用户手动下载(无法自动安装)。
Step 3:安装完成后,向用户说明以下内容
web-access 已就绪。凡是联网的需求直接说就行,我会自动选最合适的方式:
- 只需要搜索结果 → 直接搜,最快
- 需要看完整页面 → 抓取页面内容,不启动浏览器
- 需要登录/动态页面/浏览器操作 → 自动启动浏览器,登录一次后持久保存。支持多 sub-agent 集群并行使用多个浏览器。
>
Windows 用户需要 Git Bash 环境(安装 Git for Windows 即可)。
浏览哲学
像人一样浏览,不像机器人一样执行程序。
人类浏览网页时不会在开始前列出完整步骤,而是带着目标进入,边看边判断,遇到阻碍就解决,发现内容不够就深入——全程围绕「我要拿到什么」做决策。这个 skill 的所有行为都应遵循这个逻辑。
三个核心判断:
① 我需要什么? — 任务驱动,先想清楚目标信息的性质,再选最轻且能直达的方式。不要用重型工具做轻量任务,也不要用轻量工具面对它覆盖不到的内容。
② 够了吗? — 拿到的信息能完成任务,就是够了。不过度采集,不为了"完整"而浪费代价。大概了解一个视频,几帧就够;理解一篇文章,读文字就够;不需要全页截图去做能用 accessibility tree 完成的事。
③ 遇到阻碍怎么办? — 在层内解决,不退回,不打扰用户。弹窗、登录墙、广告、加载失败——像人一样判断这个阻碍是否真的挡住了目标内容:挡住了就处理,没挡住就绕过去继续。只有在确认无法自行解决时才告知用户。UI 交互也是一种可绕过的间接层:翻页、展开、点击不是获取内容的唯一路径——内容有可能已经在网站里,交互只是展示手段。
信息获取通道选择
- 先评估任务,再选通道:根据「目标信息的性质、什么工具能直接拿到」决定起点,选最轻且能直达的方案。
- 确保信息的真实性,一手信息优于二手信息:搜索引擎和聚合平台是信息发现入口。当多次搜索尝试后没有质的改进时,升级到更根本的获取方式:定位一手来源(官网、官方平台、原始页面)。
| 场景 | 通道 |
|---|---|
| 只需搜索摘要或关键词结果,或需要发现信息来源 | WebSearch |
| URL 已知,读取页面内容 | Jina(默认,底层执行 JS 渲染);需要读取 HTML 源码中的结构化字段(meta、JSON-LD 等)时改用 WebFetch(不执行 JS 渲染) |
| URL 是 PDF | Jina |
| 非公开内容,或已知静态层无效的平台(小红书、微信公众号等公开内容也被反爬限制) | 浏览器 CDP(直接,跳过静态层) |
| 需要动态内容、登录态、交互操作,或需要像人一样在浏览器内自由导航探索 | 浏览器 CDP |
浏览器 CDP 不要求 URL 已知——可从任意入口出发,通过页面内搜索、点击、跳转等方式找到目标内容。
Jina:调用方式为 r.jina.ai/example.com(URL 前加前缀,不保留原网址 http 前缀)。限 20 RPM,更节省 AI 上下文。 底层用 Puppeteer 渲染页面(能处理 JS/SPA),再用 Readability 算法提取主文章内容转为 Markdown,会过滤导航、广告、侧边栏等噪声。适合文章、博客、文档、PDF 等以正文为核心的页面;对视频页、数据面板、商品页等非文章结构页面,可能提取到错误区块。拿到结果后判断内容是否符合任务预期,不符合则切换访问策略。
WebFetch:直接获取原始 HTML,不执行 JS。meta、JSON-LD 等结构化字段通常由服务端静态嵌入 HTML,WebFetch 可直接读取;若字段由 JS 动态注入,WebFetch 同样拿不到。请求时加 header Accept: text/markdown, text/html,支持该协议的网站直接返回 Markdown。
选择逻辑:默认用 Jina——Jina 底层执行 JS,能处理动态渲染页面。只有当任务可能需要读取 HTML 源码时,才用 WebFetch。Jina 和 WebFetch 均无法处理时(无法获取所需信息、报错、需登录)→ 升级浏览器层。
降级禁止:进入更重的通道后,不得回头用轻量工具完成同一目标——等同于重走已知不通的路。浏览器层遇到阻碍应在层内解决(如处理登录),而不是绕回。唯一例外:浏览器操作中衍生的新子目标,可重新选择通道。
进入浏览器层后,区分任务性质:
- 操作型(导航、填表、点击):用 accessibility tree 感知界面,无法识别时才截图辅助
- 内容型(读帖子、看资讯、分析页面):accessibility tree 读文字结构,同时判断图片是否承载核心信息——是则提取图片 URL 定向读取
图片判断:社交媒体、图文博客、截图类内容,默认图片有价值,主动去取;工具类、导航类页面,默认 accessibility tree 够用。
浏览器 CDP 模式
启动
# 单 agent 任务(默认端口 9222)
bash ~/.claude/skills/web-access/scripts/ensure-browser.sh
# 并行任务(端口由主 agent 在 task prompt 中指定,见「并行调研」章节)
bash ~/.claude/skills/web-access/scripts/ensure-browser.sh $PORT输出 Browser ready on port XXXX。启动后必须执行以下两步:
# 解析端口并设置 session(所有后续命令自动继承,无需重复传参)
PORT=<从输出解析的端口号>
export AGENT_BROWSER_SESSION="port-${PORT}"输出状态说明:
Browser ready on port XXXX→ 可直接用,设置 PORT 和 SESSION 后继续(任务结束后关闭)ERROR→ 执行bash ~/.claude/skills/web-access/scripts/close-browser.sh [端口]后重新运行
⚠️ 严禁降级:只用 agent-browser CDP 模式,不切换到其他浏览器工具/MCP。
常用命令
将 ensure-browser.sh 输出中的端口号记为 $PORT,后续命令统一用该端口:
agent-browser --cdp $PORT open <url> # 打开页面
agent-browser --cdp $PORT snapshot -i # 可交互元素(操作用)
agent-browser --cdp $PORT snapshot # 完整无障碍树(读文字用)
agent-browser --cdp $PORT click @ref-123 # 点击元素
agent-browser --cdp $PORT fill @ref-123 "内容" # 填写输入框
agent-browser --cdp $PORT wait load networkidle # 仅用于 click/fill 触发导航后;open 已内置等待,勿在 open 后使用
agent-browser --cdp $PORT scroll down 3000 # 触发懒加载
agent-browser --cdp $PORT screenshot /tmp/x.png
agent-browser --cdp $PORT screenshot --annotate # snapshot -i ref 失效时的升级方案,见 references/commands.md
agent-browser --cdp $PORT eval "<js>" # 执行 JS,用于提取 DOM 信息图片提取
判断内容在图片里时,用 eval 从 DOM 直接拿图片 URL,再定向打开截图读取——比全页截图精准得多。
技术事实:
- 页面中存在大量已加载但未展示的内容——轮播中非当前帧的图片、折叠区块的文字、懒加载占位元素等,它们存在于 DOM 中但对用户不可见。视觉层只是 DOM 数据的一个投影。以数据结构(容器、属性、节点关系)为单位思考,可以直接触达这些内容,而不依赖视觉层是否将其呈现出来。
- scroll 到底部会触发懒加载,使未进入视口的图片完成加载。用
eval提取图片 URL 前若未滚动,部分图片可能尚未加载。
拿到图片 URL 后,Read 工具原生支持读取本地图片文件。对于无需 session 的公开图片 URL,可直接下载到本地后用 Read 读取,无需经过浏览器。需要 session/cookie 的图片才需要在浏览器内 open + screenshot。
视频内容获取
CDP headed 模式下浏览器真实渲染,截图可捕获当前视频帧。核心能力:seek 到任意时间点截图,可对视频内容进行离散采样分析。
# 获取总时长,制定采样计划
agent-browser --cdp $PORT eval "document.querySelector('video').duration"
# seek + 播放 + 截图
agent-browser --cdp $PORT eval "var v=document.querySelector('video'); v.currentTime=60; v.play()"
sleep 2
agent-browser --cdp $PORT screenshot /tmp/frame.png
# 全屏截图画面更清晰
agent-browser --cdp $PORT eval "document.querySelector('video').requestFullscreen()"采帧粒度(仅供参考,具体视频具体分析:大概了解 → 30-60s 间隔;理解叙事 → 10s;精细分析 → 1-2s)由任务需求自行判断,无需用户指定。
登录判断
登录判断的核心问题只有一个:目标内容拿到了吗?
打开页面后,先尝试获取目标内容,持续执行。在此过程中,结合两方面信息做判断:
1. 领域知识:对该网站的了解——X/Twitter 的最新时间线、小红书的私密内容、微博的完整评论等,这类内容通常需要登录才能获取完整数据 2. 页面实际反馈:内容是否符合预期?是降级版(如热门帖代替最新帖)?是否有明显缺失?
即使页面显示了登录提示,只要目标内容已经拿到,就不需要打扰用户登录。
只有当确认目标内容无法获取时,才推断:登录是否能解决这个问题?若推断成立,告知用户:
"当前页面在未登录状态下无法获取[具体内容],请在已打开的 Chrome 窗口中登录 [网站名],完成后告诉我继续。"
登录完成后无需重启浏览器,直接继续原任务。
任务结束
任务结束后关闭浏览器(必须用此脚本,勿直接 kill,否则会留下崩溃窗口):
bash ~/.claude/skills/web-access/scripts/close-browser.sh [端口] # 默认 9222close-browser.sh 同时清理 Chrome 进程和 agent-browser session daemon,调用方无需额外操作。
并行调研:子 Agent 分治策略
任务包含多个独立调研目标时(如同时调研 N 个项目、N 个来源),鼓励合理分治给子 Agent 并行执行,而非主 Agent 串行处理。
好处:
- 速度:多子 Agent 并行,总耗时约等于单个子任务时长
- 上下文保护:抓取内容不进入主 Agent 上下文,主 Agent 只接收摘要,节省 token
子 Agent 需继承 skill: 在子 Agent prompt 中写 遵循 web-access skill 的指引 即可,子 Agent 会自动加载 skill,无需在 prompt 中复制 skill 内容或指定路径。
子 Agent Prompt 写法:目标导向,而非步骤指令
子 Agent 有完整的 skill 知识和自主判断能力。主 Agent 的职责是说清楚要什么,仅在必要与确信时限定怎么做。过度指定步骤会剥夺子 Agent 的判断空间,反而引入主 Agent 的假设错误。
错误:过度指定(预填了未验证的 URL/账号):
打开 https://x.com/SomeAccount,抓取最新推文正确:目标导向(子 Agent 自主发现路径):
找到 XX 的官方 X 账号,获取最新推文内容关键原则:不要预填未经验证的信息,信息来自用户直接提供或已确认时,才可直接传入。
分治判断标准:
| 适合分治 | 不适合分治 |
|---|---|
| 目标相互独立,结果互不依赖 | 目标有依赖关系,下一个需要上一个的结果 |
| 每个子任务量足够大(多页抓取、多轮搜索) | 简单单页查询,分治开销大于收益 |
| 需要 CDP 浏览器或长时间运行的任务 | 几次 WebSearch / Jina 就能完成的轻量查询 |
CDP 并发:每个端口启动独立 Chrome 实例,互不干扰。 主 agent 在启动子 agent 时,在 task prompt 中为每个子 agent 明确指定一个独占端口,避免浏览器实例冲突(从 9222 起,在 9222–9299 范围内选择,每个子 agent 用不同端口)。子 agent 收到端口后调用 ensure-browser.sh <PORT> 启动。
特殊任务规则
核实任务
核实的目标是一手来源,而非更多的二手报道——多个媒体引用同一个错误会造成循环印证假象。
搜索用于定位来源,不用于证明真伪。找到来源后,直接访问读取原文。
| 信息类型 | 一手来源 |
|---|---|
| 政策/法规 | 发布机构官网 |
| 企业公告 | 公司官方新闻页 |
| 学术声明 | 原始论文/机构官网 |
找不到官网时:权威媒体的原创报道(非转载)可作为次级依据,但需向用户说明:"未找到官方原文,以下核实来自[媒体名]报道,存在转述误差可能。"
工具能力边界理解
对任何工具(MCP、CLI、库)的能力有疑问时,如果没有足够的知识把握,先查官方文档,如无足够文档介绍,可考虑查看源码,再作判断,不猜测、不把不确定性转移给用户。
References 索引
| 文件 | 何时加载 |
|---|---|
references/commands.md | 需要不常用命令时(drag、storage、pdf 等) |
references/login-flow.md | 需要了解登录流程细节时 |
agent-browser 完整命令参考
所有命令格式:agent-browser --cdp 9222 <command>导航类
agent-browser --cdp 9222 open <url> # 打开 URL
agent-browser --cdp 9222 back # 返回上一页
agent-browser --cdp 9222 forward # 前进
agent-browser --cdp 9222 reload # 刷新
agent-browser --cdp 9222 reload --hard # 强制刷新(清缓存)快照类(核心:获取页面状态)
agent-browser --cdp 9222 snapshot # 基础快照(文本+结构)
agent-browser --cdp 9222 snapshot -i # 带交互元素(含 ref 编号,用于后续操作)
agent-browser --cdp 9222 snapshot -c # 带坐标信息
agent-browser --cdp 9222 snapshot -d # 深度快照(展开折叠内容)
agent-browser --cdp 9222 snapshot -i -d # 组合:交互元素 + 深度交互类
agent-browser --cdp 9222 click @ref-123 # 点击元素(ref 来自 snapshot -i)
agent-browser --cdp 9222 click "Submit" # 点击文本匹配的元素
agent-browser --cdp 9222 fill @ref-123 "text" # 填写输入框
agent-browser --cdp 9222 type "hello world" # 在当前焦点处输入
agent-browser --cdp 9222 press Enter # 按键(Enter/Tab/Escape/ArrowDown 等)
agent-browser --cdp 9222 select @ref-123 "option" # 选择下拉选项
agent-browser --cdp 9222 drag @ref-from @ref-to # 拖拽
agent-browser --cdp 9222 hover @ref-123 # 悬停(触发 tooltip/下拉菜单)
agent-browser --cdp 9222 scroll @ref-123 down 300 # 滚动元素
agent-browser --cdp 9222 scroll window down 500 # 滚动页面信息获取
agent-browser --cdp 9222 get text @ref-123 # 获取元素文本
agent-browser --cdp 9222 get html @ref-123 # 获取元素 HTML
agent-browser --cdp 9222 get attr @ref-123 href # 获取属性值
agent-browser --cdp 9222 get url # 获取当前页面 URL
agent-browser --cdp 9222 get title # 获取页面标题等待机制
agent-browser --cdp 9222 wait element @ref-123 # 等待元素出现
agent-browser --cdp 9222 wait 2000 # 等待 2 秒
agent-browser --cdp 9222 wait element --text "加载完成" # 等待含特定文本的元素
agent-browser --cdp 9222 wait load networkidle # 等待网络空闲
agent-browser --cdp 9222 wait element @ref-123 --hidden # 等待元素消失语义定位器(snapshot -i ref 的替代方案)
agent-browser --cdp 9222 find role button "登录" # 按 ARIA role + 文本找元素
agent-browser --cdp 9222 find text "提交" # 按文本内容找元素
agent-browser --cdp 9222 find label "用户名" # 按关联 label 找输入框截图与导出
agent-browser --cdp 9222 screenshot # 截图(viewport)
agent-browser --cdp 9222 screenshot --full # 截图(全页面)
agent-browser --cdp 9222 screenshot -o out.png # 保存到文件
agent-browser --cdp 9222 screenshot --annotate # 注释截图(见下方说明)
agent-browser --cdp 9222 pdf -o page.pdf # 导出 PDFannotate 模式
--annotate 在截图上叠加编号标签,同时输出对应的 ref 列表,ref 与 snapshot -i 共享同一套体系(@e1、@e2…),截图后立即可用于操作:
agent-browser --cdp 9222 screenshot --annotate
# 输出:[1] @e1 button "Submit" [2] @e2 link "Home" ...
agent-browser --cdp 9222 click @e2 # 直接使用默认用 `snapshot -i`。accessibility tree 依赖元素有语义标签(role、name、label),当页面元素缺乏这些语义信息时,snapshot 给出的 ref 可能无法命中或根本不出现。此时用 --annotate,它直接从视觉层枚举可见元素,能覆盖 accessibility tree 看不到的情况:
- 纯图标按钮(无文字、无 aria-label)
- canvas / WebGL 等自定义渲染元素
- 动态注入、框架渲染导致 accessibility tree 结构异常
Cookies & Storage
agent-browser --cdp 9222 cookies # 列出所有 cookies
agent-browser --cdp 9222 cookies --domain x.com # 过滤域名
agent-browser --cdp 9222 storage local # 查看 localStorage
agent-browser --cdp 9222 storage session # 查看 sessionStorage常用组合模式
# 登录表单填写
agent-browser --cdp 9222 snapshot -i
agent-browser --cdp 9222 fill @ref-username "user@example.com"
agent-browser --cdp 9222 fill @ref-password "password"
agent-browser --cdp 9222 click @ref-submit
agent-browser --cdp 9222 wait load networkidle
# 翻页操作
agent-browser --cdp 9222 snapshot -i
agent-browser --cdp 9222 click @ref-next-page
agent-browser --cdp 9222 wait load networkidle
agent-browser --cdp 9222 snapshot登录态处理详细流程
登录检测信号
执行 snapshot -i 后,若出现以下任一信号,判定为需要登录:
- 页面含
input[type=password] - 当前 URL 含
/login、/signin、/auth、/account/login - 页面标题含"登录"、"Sign in"、"Log in"、"Login"
- 快照内容出现"请登录"、"未登录"、"登录后查看"等提示文本
处理步骤
浏览器始终以 headed(有窗口)模式运行,检测到登录信号后:
1. 告知用户:
"已在 Chrome 窗口打开 [网站名],请完成登录。登录成功后告诉我,我来继续。"
2. 等待用户确认登录完成
3. 继续原来的操作(无需重启浏览器,登录态已写入 profile)
agent-browser --cdp 9222 open <url>
agent-browser --cdp 9222 snapshot -iProfile 路径
~/.claude/browser-profile/(登录态持久化,下次直接复用)
常见问题
Q: 端口被占用无法启动?
lsof -ti:9222 | xargs kill -9
bash ~/.claude/skills/web-access/scripts/ensure-browser.shQ: 登录后 cookie 没有持久化?
- 确保使用了
--user-data-dir参数(ensure-browser.sh 已包含) - 不要在浏览器关闭前清除 cookies
#!/usr/bin/env bash
# web-access 共享工具函数,供 ensure-browser.sh 和 close-browser.sh source
# 返回 macos / linux / windows
get_os() {
case "$(uname -s)" in
Darwin) echo "macos" ;;
Linux) echo "linux" ;;
MINGW*|MSYS*|CYGWIN*) echo "windows" ;;
*) echo "linux" ;;
esac
}
# 跨平台 Chrome 路径探测
find_chrome() {
# macOS
local mac_path="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
[ -f "$mac_path" ] && echo "$mac_path" && return
# Linux
for p in \
"$(command -v google-chrome 2>/dev/null)" \
"$(command -v google-chrome-stable 2>/dev/null)" \
"$(command -v chromium 2>/dev/null)" \
"$(command -v chromium-browser 2>/dev/null)" \
/usr/bin/google-chrome /usr/bin/google-chrome-stable \
/usr/bin/chromium /usr/bin/chromium-browser /snap/bin/chromium; do
[ -f "$p" ] && echo "$p" && return
done
# Windows(Git Bash)
if [ "$(get_os)" = "windows" ]; then
local local_app programfiles
local_app=$(cygpath "$LOCALAPPDATA" 2>/dev/null)
programfiles=$(cygpath "$PROGRAMFILES" 2>/dev/null)
for p in \
"$local_app/Google/Chrome/Application/chrome.exe" \
"$programfiles/Google/Chrome/Application/chrome.exe"; do
[ -f "$p" ] && echo "$p" && return
done
fi
echo ""
}
# 按 OS 适当关闭进程
kill_pid() {
local pid=$1
case "$(get_os)" in
macos)
osascript -e "tell application \"System Events\" to tell (first process whose unix id is ${pid}) to quit" 2>/dev/null \
|| kill "$pid" 2>/dev/null
;;
windows)
taskkill /PID "$pid" /F 2>/dev/null
;;
*)
kill "$pid" 2>/dev/null
;;
esac
}
#!/usr/bin/env bash
# 环境探测 - 输出依赖状态供 AI 判断和处理
UTILS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_utils.sh
source "$UTILS_DIR/_utils.sh"
echo "OS: $(uname -s) $(uname -m)"
CHROME_PATH=$(find_chrome)
echo "chrome: ${CHROME_PATH:-missing}"
echo "node: $(command -v node 2>/dev/null && node --version 2>/dev/null || echo 'missing')"
echo "npm: $(command -v npm 2>/dev/null || echo 'missing')"
echo "agent-browser: $(command -v agent-browser 2>/dev/null || echo 'missing → npm install -g agent-browser')"
#!/usr/bin/env bash
# 优雅关闭 agent-browser Chrome 实例(跨平台)
# 用法:bash close-browser.sh [PORT] # 默认 9222
UTILS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_utils.sh
source "$UTILS_DIR/_utils.sh"
CDP_PORT=${1:-9222}
if [ "$CDP_PORT" = "9222" ]; then
PROFILE_DIR="$HOME/.claude/browser-profile"
else
PROFILE_DIR="$HOME/.claude/browser-profile-${CDP_PORT}"
fi
SNAPSHOT_DIR="$HOME/.claude/browser-profile-snapshot"
# 1. CDP Browser.close —— 协议层关闭,跨平台,Chrome 自己正常退出
# 用 Python 发 WebSocket 帧,不依赖外部包
python3 - "${CDP_PORT}" <<'PYEOF' 2>/dev/null
import json, socket, base64, urllib.request, struct, sys
try:
port = int(sys.argv[1]) if len(sys.argv) > 1 else 9222
data = json.loads(urllib.request.urlopen(f'http://localhost:{port}/json/version', timeout=3).read())
ws_url = data['webSocketDebuggerUrl']
path = ws_url[len(f'ws://localhost:{port}'):]
s = socket.socket()
s.settimeout(5)
s.connect(('localhost', port))
key = base64.b64encode(b'claude-web-access!!').decode()
s.send(f'GET {path} HTTP/1.1\r\nHost: localhost:{port}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: {key}\r\nSec-WebSocket-Version: 13\r\n\r\n'.encode())
s.recv(4096)
msg = json.dumps({"id": 1, "method": "Browser.close"}).encode()
mask = bytes([0, 0, 0, 0])
masked = bytes(b ^ mask[i % 4] for i, b in enumerate(msg))
frame = bytes([0x81, 0x80 | len(msg)]) + mask + masked
s.send(frame)
s.close()
print("Browser closed")
except Exception as e:
print(f"CDP close failed: {e}", flush=True)
exit(1)
PYEOF
# 2. 兜底:CDP 失败时按 OS 强制退出
if [ $? -ne 0 ]; then
case "$(get_os)" in
macos)
OUR_PID=$(ps aux | grep "Google Chrome" | grep -- "--user-data-dir=${PROFILE_DIR}" | grep -v grep | awk '{print $2}' | head -1)
[ -n "$OUR_PID" ] && kill_pid "$OUR_PID" && echo "Browser closed (osascript)"
;;
linux)
OUR_PID=$(ps aux | grep "google-chrome\|chromium" | grep -- "--user-data-dir=${PROFILE_DIR}" | grep -v grep | awk '{print $2}' | head -1)
[ -n "$OUR_PID" ] && kill_pid "$OUR_PID" && echo "Browser closed (SIGTERM)"
;;
windows)
# wmic 已在 Windows 11+ 移除,改用 Get-CimInstance
OUR_PID=$(powershell.exe -NoProfile -Command \
"Get-CimInstance Win32_Process -Filter 'Name=\"chrome.exe\"' | Where-Object { \$_.CommandLine -match 'browser-profile' } | Select-Object -First 1 -ExpandProperty ProcessId" \
2>/dev/null | tr -d '\r\n ')
[ -n "$OUR_PID" ] && kill_pid "$OUR_PID" && echo "Browser closed (taskkill)"
;;
*)
echo "Unknown OS — please close the browser manually"
;;
esac
fi
# 3. 关闭 9222 后自动更新 snapshot,供非 9222 端口克隆登录态
if [ "$CDP_PORT" = "9222" ] && [ -d "$PROFILE_DIR" ]; then
mkdir -p "$SNAPSHOT_DIR"
if command -v rsync >/dev/null 2>&1; then
rsync -a --delete \
--exclude="SingletonLock" --exclude="SingletonCookie" --exclude="SingletonSocket" \
--exclude="Default/Cache/" --exclude="Default/Code Cache/" --exclude="Default/GPUCache/" \
--exclude="Default/Service Worker/CacheStorage/" \
--exclude="ShaderCache/" --exclude="GrShaderCache/" --exclude="*.lock" \
"$PROFILE_DIR/" "$SNAPSHOT_DIR/" 2>/dev/null
echo "Profile snapshot updated"
elif [ "$(get_os)" = "windows" ]; then
# Git Bash 默认无 rsync,改用 robocopy(Windows 内置)
PROFILE_WIN=$(cygpath -w "$PROFILE_DIR" 2>/dev/null || echo "$PROFILE_DIR")
SNAPSHOT_WIN=$(cygpath -w "$SNAPSHOT_DIR" 2>/dev/null || echo "$SNAPSHOT_DIR")
powershell.exe -NoProfile -Command \
"robocopy '$PROFILE_WIN' '$SNAPSHOT_WIN' /MIR /XD Cache 'Code Cache' GPUCache ShaderCache GrShaderCache /XF '*.lock' SingletonLock SingletonCookie SingletonSocket | Out-Null" 2>/dev/null
echo "Profile snapshot updated (robocopy)"
fi
fi
# 4. 清理 agent-browser session daemon(如果存在)
SESSION_PID=$(cat "$HOME/.agent-browser/port-${CDP_PORT}.pid" 2>/dev/null)
if [ -n "$SESSION_PID" ] && kill -0 "$SESSION_PID" 2>/dev/null; then
kill "$SESSION_PID" 2>/dev/null
fi
rm -f "$HOME/.agent-browser/port-${CDP_PORT}.pid" \
"$HOME/.agent-browser/port-${CDP_PORT}.sock"
#!/usr/bin/env bash
# Chrome CDP 生命周期管理(始终 headed 模式)
# 用法:bash ensure-browser.sh [PORT]
# PORT 由主 agent 分配(默认 9222;并行时传入 9223、9224 等)
UTILS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_utils.sh
source "$UTILS_DIR/_utils.sh"
CHROME=$(find_chrome)
if [ -z "$CHROME" ]; then
echo "ERROR: Chrome not found. Please install Google Chrome." >&2
exit 1
fi
CDP_PORT=${1:-9222}
SNAPSHOT_DIR="$HOME/.claude/browser-profile-snapshot"
OS=$(get_os)
# Profile 路径(9222 保持历史路径兼容)
if [ "$CDP_PORT" = "9222" ]; then
PROFILE_DIR="$HOME/.claude/browser-profile"
else
PROFILE_DIR="$HOME/.claude/browser-profile-${CDP_PORT}"
fi
# Windows 下 Chrome 需要 Windows 风格路径
if [ "$OS" = "windows" ]; then
PROFILE_DIR_CHROME=$(cygpath -w "$PROFILE_DIR")
else
PROFILE_DIR_CHROME="$PROFILE_DIR"
fi
# 检测 CDP 端口是否已就绪
check_ready() {
curl -s "http://localhost:${CDP_PORT}/json/version" >/dev/null 2>&1
}
# 检测当前运行的 Chrome 是否使用了正确的 profile
check_profile() {
if [ "$OS" = "windows" ]; then
local profile_pattern
if [ "$CDP_PORT" = "9222" ]; then
# 9222 的目录名是 browser-profile(无后缀),用 [^-] 避免误匹配 browser-profile-9223 等
profile_pattern="browser-profile[^-]"
else
profile_pattern="browser-profile-${CDP_PORT}"
fi
wmic process where "name='chrome.exe'" get commandline 2>/dev/null \
| grep -qE "$profile_pattern" 2>/dev/null
else
ps aux | grep -E "Google Chrome|google-chrome|chromium" \
| grep -- "--user-data-dir=${PROFILE_DIR}" \
| grep -v grep >/dev/null 2>&1
fi
}
if check_ready; then
if check_profile; then
AGENT_BROWSER_SESSION="port-${CDP_PORT}" agent-browser connect "${CDP_PORT}" >/dev/null 2>&1 || true
echo "Browser ready on port ${CDP_PORT}"
exit 0
else
echo "ERROR: Port ${CDP_PORT} is in use by another process." >&2
exit 1
fi
fi
# 非主端口:每次从 snapshot 克隆最新登录态(覆盖已有 profile)
if [ "$CDP_PORT" != "9222" ]; then
if [ -d "$SNAPSHOT_DIR" ]; then
mkdir -p "$PROFILE_DIR"
rsync -a --delete \
--exclude="SingletonLock" --exclude="SingletonCookie" --exclude="SingletonSocket" \
--exclude="Default/Cache/" --exclude="Default/Code Cache/" --exclude="Default/GPUCache/" \
--exclude="Default/Service Worker/CacheStorage/" \
--exclude="ShaderCache/" --exclude="GrShaderCache/" --exclude="*.lock" \
"$SNAPSHOT_DIR/" "$PROFILE_DIR/" 2>/dev/null
echo "Profile cloned from snapshot" >&2
else
# 全新环境,用空 profile 启动
mkdir -p "$PROFILE_DIR"
echo "INFO: No snapshot available, starting with fresh profile" >&2
fi
fi
# 如果有我们自己的 Chrome 残留(有 profile 但没监听端口),清理掉
if [ "$OS" = "windows" ]; then
OUR_PID=$(wmic process where "name='chrome.exe'" get commandline,processid 2>/dev/null \
| grep "browser-profile" | grep -oE '[0-9]+$' | head -1 | tr -d ' \r\n')
else
OUR_PID=$(ps aux | grep -E "Google Chrome|google-chrome|chromium" \
| grep -- "--user-data-dir=${PROFILE_DIR}" \
| grep -v grep | awk '{print $2}' | head -1)
fi
if [ -n "$OUR_PID" ]; then
kill_pid "$OUR_PID"
sleep 1
fi
# 启动 Chrome(后台,始终 headed)
"$CHROME" \
"--remote-debugging-port=${CDP_PORT}" \
"--user-data-dir=${PROFILE_DIR_CHROME}" \
"--no-first-run" \
"--no-default-browser-check" \
"--exclude-switches=enable-automation" \
"--disable-infobars" \
>/dev/null 2>&1 &
# 等待 CDP 就绪(最多 15 秒)
for i in $(seq 1 30); do
if check_ready; then
AGENT_BROWSER_SESSION="port-${CDP_PORT}" agent-browser connect "${CDP_PORT}" >/dev/null 2>&1 || true
echo "Browser ready on port ${CDP_PORT}"
exit 0
fi
sleep 0.5
done
echo "ERROR: Browser failed to start within 15 seconds" >&2
exit 1
Related skills
How it compares
Pick web-access over raw curl or Playwright scripts when agents need a unified, dependency-checked gateway for search, scraping, and authenticated browsing.
FAQ
When should I use web-access vs. simpler fetch-based web tools?
Use web-access when you need authentication, dynamic JavaScript-rendered content, or multi-page workflows. For static HTML or simple JSON APIs, lightweight tools are faster
Can I run multiple browser instances in parallel?
Yes - web-access supports child agents with dedicated ports (9222-9299 range). Each sub-agent gets its own Chrome instance for true parallelism
Is Web Access safe to install?
skills.sh reports 0 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.