关于 browser-automation
browser-automation 是通过 Chrome DevTools Protocol 控制 Chrome 的命令行 Skill,可捕获、提取网页内容并与页面交互;持久化配置可在命令间保留会话状态。
主要功能
- 01截取整页或指定元素
- 02将网页生成 PDF
- 03提取文本、链接、图片和元数据
- 04导航、交互及填写表单
- 05使用 Chrome 配置保留会话
- 06录制浏览器画面帧
适用场景
- 01将网页或元素保存为图片
- 02提取文章内容和页面链接
- 03自动填写并提交表单
- 04在已登录网站执行多步交互
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ the-focus-ai/ chrome-driver --skill browser-automation安装后在 Claude Code 中输入/browser-automation确认已加载。
详细介绍
使用 Chrome DevTools Protocol 进行浏览器自动化
使用简单的命令行脚本以编程方式控制 Chrome 浏览器。如果 Chrome 尚未运行,所有脚本都会自动以无头模式启动 Chrome。
全局选项(适用于所有命令)
所有命令均支持以下选项,用于保持会话状态和交互操作:
--no-headless- 在可见窗口中运行 Chrome(适用于交互式网站、登录等)--user-data=PATH- 使用指定的 Chrome 配置目录,以便在不同运行之间保留 Cookie、登录信息和会话状态
示例:为已登录的网站保持会话
# First time: login interactively with visible browser
${CLAUDE_PLUGIN_ROOT}/bin/navigate https://instacart.com --no-headless --user-data=~/.chrome-instacart
# After logging in manually, future commands use same profile (cookies preserved)
${CLAUDE_PLUGIN_ROOT}/bin/navigate https://instacart.com/store/market-32 --user-data=~/.chrome-instacart
${CLAUDE_PLUGIN_ROOT}/bin/screenshot https://instacart.com/account --user-data=~/.chrome-instacart /tmp/account.png
可用命令
所有脚本均位于 ${CLAUDE_PLUGIN_ROOT}/bin/:
screenshot - 将网页截取为图像
${CLAUDE_PLUGIN_ROOT}/bin/screenshot URL [OUTPUT] [OPTIONS]
选项:
--full-page- 截取整个可滚动页面--selector=CSS- 截取指定元素--format=png|jpeg|webp- 输出格式(默认:png)--quality=N- JPEG/WebP 质量,范围为 0-100--width=N --height=N- 设置视口大小--max-dimension=N- 输出的最大尺寸(默认:8000;大型页面会自动缩放)
示例:
# Basic screenshot
${CLAUDE_PLUGIN_ROOT}/bin/screenshot https://example.com /tmp/page.png
# Full page as JPEG
${CLAUDE_PLUGIN_ROOT}/bin/screenshot https://example.com /tmp/full.jpg --full-page --format=jpeg
# Capture specific element
${CLAUDE_PLUGIN_ROOT}/bin/screenshot https://example.com /tmp/header.png --selector="header"
pdf - 将网页生成 PDF
${CLAUDE_PLUGIN_ROOT}/bin/pdf URL [OUTPUT] [OPTIONS]
选项:
--paper=letter|a4|legal|a3|a5|tabloid- 纸张尺寸(默认:letter)--landscape- 横向--margin=INCHES- 所有边距(默认:0.4)--scale=FACTOR- 缩放比例,范围为 0.1-2.0(默认:1.0)--no-background- 不包含背景颜色/图像
示例:
# Basic PDF
${CLAUDE_PLUGIN_ROOT}/bin/pdf https://example.com /tmp/doc.pdf
# A4 landscape
${CLAUDE_PLUGIN_ROOT}/bin/pdf https://example.com /tmp/report.pdf --paper=a4 --landscape
# Tight margins
${CLAUDE_PLUGIN_ROOT}/bin/pdf https://example.com /tmp/compact.pdf --margin=0.25
extract - 从网页提取内容
${CLAUDE_PLUGIN_ROOT}/bin/extract URL [OPTIONS]
选项:
--format=markdown|text|html- 输出格式(默认:markdown)--selector=CSS- 仅提取指定元素--links- 同时列出所有链接--images- 同时列出所有图像--metadata- 同时显示页面元数据
示例:
# Get page as markdown
${CLAUDE_PLUGIN_ROOT}/bin/extract https://example.com
# Get plain text from article
${CLAUDE_PLUGIN_ROOT}/bin/extract https://example.com --format=text --selector="article"
# Get all links and metadata
${CLAUDE_PLUGIN_ROOT}/bin/extract https://example.com --links --metadata
navigate - 导航并与页面交互
${CLAUDE_PLUGIN_ROOT}/bin/navigate URL [OPTIONS]
选项:
--wait-for=SELECTOR- 等待元素出现--click=SELECTOR- 点击元素--type=SELECTOR=TEXT- 在输入框中输入文本--eval=JAVASCRIPT- 执行 JavaScript 并打印结果--timeout=SECONDS- 超时时间(默认:30)
示例:
# Navigate and wait for content
${CLAUDE_PLUGIN_ROOT}/bin/navigate https://example.com --wait-for="#content"
# Fill form and submit
${CLAUDE_PLUGIN_ROOT}/bin/navigate https://google.com --type="input[name=q]=hello" --click="input[type=submit]"
# Get page title via JavaScript
${CLAUDE_PLUGIN_ROOT}/bin/navigate https://example.com --eval="document.title"
form - 填写并提交网页表单
${CLAUDE_PLUGIN_ROOT}/bin/form URL [OPTIONS]
选项:
--fill=SELECTOR=VALUE- 填写输入框(可重复使用)--select=SELECTOR=VALUE- 选择下拉选项(可重复使用)--fill-json='{"sel":"val"}'- 使用 JSON 填写多个字段--submit=SELECTOR- 填写后点击提交按钮--wait-for=SELECTOR- 填写前等待元素出现--wait-after=SELECTOR- 提交后等待元素出现--screenshot=PATH- 操作完成后截取屏幕截图
示例:
# Login form
${CLAUDE_PLUGIN_ROOT}/bin/form https://example.com/login \
--fill='#username=john' \
--fill='#password=secret' \
--submit='button[type=submit]'
# Form with dropdowns
${CLAUDE_PLUGIN_ROOT}/bin/form https://example.com/register \
--fill='#name=John Doe' \
--fill='#[email protected]' \
--select='#country=US' \
--submit='#register-btn'
# Using JSON
${CLAUDE_PLUGIN_ROOT}/bin/form https://example.com/contact \
--fill-json='{"#name":"John","#email":"[email protected]"}' \
--submit='button.send'
record - 录制屏幕视频帧
${CLAUDE_PLUGIN_ROOT}/bin/record URL OUTPUT_DIR [OPTIONS]
选项:
--duration=SECONDS- 录制时长(默认:5)--count=N- 要截取的精确帧数--format=jpeg|png- 帧格式(默认:jpeg)--quality=N- JPEG 质量,范围为 0-100(默认:80)--max-width=N- 最大帧宽度--max-height=N- 最大帧高度--fps=N- 近似帧率(默认:10)
示例:
# Record 5 seconds
${CLAUDE_PLUGIN_ROOT}/bin/record https://example.com /tmp/frames
# Record 30 PNG frames
${CLAUDE_PLUGIN_ROOT}/bin/record https://example.com /tmp/frames --count=30 --format=png
# Convert to video with ffmpeg
ffmpeg -framerate 10 -i /tmp/frames/frame-%04d.jpg -c:v libx264 output.mp4
interact - 与当前页面交互(不导航)
${CLAUDE_PLUGIN_ROOT}/bin/interact [OPTIONS]
这是执行多步骤工作流的关键命令。 与 navigate 不同,此命令会连接到已打开的浏览器标签页并执行操作,而不会重新加载页面。适用于:
- 多步骤流程(搜索 → 点击结果 → 添加到购物车)
- 单页应用(SPA)
- 导航会重置状态的网站
选项:
--click=SELECTOR- 点击与 CSS 选择器匹配的元素--type=SELECTOR=TEXT- 在输入框中输入文本--eval=JAVASCRIPT- 执行 JavaScript 并打印结果--wait-for=SELECTOR- 等待元素出现--select=SELECTOR=VALUE- 选择下拉选项--focus=SELECTOR- 聚焦元素--text=SELECTOR- 获取元素的文本内容--timeout=SECONDS- 超时时间(默认:30)
示例:
# Execute JavaScript on current page
${CLAUDE_PLUGIN_ROOT}/bin/interact --eval="document.title" --user-data=~/.chrome-mysite
# Click a button on current page
${CLAUDE_PLUGIN_ROOT}/bin/interact --click="#submit-btn" --user-data=~/.chrome-mysite
# Type into an input without navigating away
${CLAUDE_PLUGIN_ROOT}/bin/interact --type="#search=query" --user-data=~/.chrome-mysite
# Chain actions: focus, type, then click
${CLAUDE_PLUGIN_ROOT}/bin/interact --focus="#search" --type="#search=pork" --click="#search-btn" --user-data=~/.chrome-mysite
# Get text from an element
${CLAUDE_PLUGIN_ROOT}/bin/interact --text="h1.title" --user-data=~/.chrome-mysite
chrome-status - 检查浏览器状态
${CLAUDE_PLUGIN_ROOT}/bin/chrome-status
显示 Chrome 是否正在运行、版本信息以及已打开的标签页。
常见工作流
截取页面截图
${CLAUDE_PLUGIN_ROOT}/bin/screenshot https://example.com /tmp/screenshot.png
将页面转换为 PDF
${CLAUDE_PLUGIN_ROOT}/bin/pdf https://example.com /tmp/document.pdf --paper=a4
抓取页面内容
${CLAUDE_PLUGIN_ROOT}/bin/extract https://example.com --format=markdown
填写并提交表单
${CLAUDE_PLUGIN_ROOT}/bin/form https://example.com/login \
--fill='#username=user' \
--fill='#password=pass' \
--submit='button[type=submit]' \
--wait-after='.dashboard'
录制屏幕视频
${CLAUDE_PLUGIN_ROOT}/bin/record https://example.com /tmp/frames --duration=10
ffmpeg -framerate 10 -i /tmp/frames/frame-%04d.jpg -c:v libx264 video.mp4
说明
- 需要时,Chrome 会自动以无头模式启动(使用
--no-headless可显示浏览器) - 为提升速度,Chrome 会在命令之间保持运行
- 使用
--user-data=PATH可在不同会话之间保留登录信息和 Cookie - 使用
pkill -f 'chrome.*--remote-debugging-port'可手动停止 Chrome - 默认端口为 9222;设置
CHROME_DRIVER_PORT可更改端口 - 所有脚本均支持
--help,可查看完整用法信息 - 大尺寸截图会自动缩放至 8000px 以内(API 限制)
会话管理技巧
对于需要登录的网站(Instacart、Amazon、银行网站等):
- 创建持久化配置文件:始终使用
--user-data=~/.chrome-sitename - 首次登录:使用
--no-headless显示浏览器,然后手动登录 - 后续自动化:使用相同的
--user-data路径保留会话 - 多个账户:为不同账户或网站使用不同的
--user-data路径
多步工作流指南:购物网站(Instacart、Amazon 等)
本节将介绍如何在需要登录并进行多次交互的网站上自动执行复杂的多步流程。关键在于结合使用两个命令:
navigate- 前往某个 URL(用于初始导航)interact- 与当前页面交互,而不重新加载(用于后续所有操作)
了解双命令模式
为什么仅使用 navigate 不够:
navigate总是会加载或重新加载指定的 URL- 每次调用
navigate都是独立的,不会从之前的状态“继续” - 多步工作流(搜索 → 选择 → 加入购物车)会因每一步都重置而中断
为什么 interact 必不可少:
interact会连接到现有的浏览器标签页- 它会对当前显示的页面执行操作
- 多次调用
interact之间会保留页面状态 - 非常适合单页应用和多步流程
完整示例:将商品添加到 Instacart 购物车
下面是一个可行的实际示例:
步骤 1:初始设置 - 创建配置文件并登录
仅首次需要 - 打开可见的浏览器,以便用户登录:
# Open Instacart with visible browser and persistent profile
${CLAUDE_PLUGIN_ROOT}/bin/navigate https://www.instacart.com \
--no-headless \
--user-data=~/.chrome-instacart
# USER ACTION: Log in manually in the browser window
# Your cookies/session will be saved to ~/.chrome-instacart
步骤 2:前往商店
登录后,前往所需商店:
# First, find the correct store URL by checking the homepage
${CLAUDE_PLUGIN_ROOT}/bin/navigate https://www.instacart.com/store \
--no-headless \
--user-data=~/.chrome-instacart
# Use JavaScript to find links containing your store name
${CLAUDE_PLUGIN_ROOT}/bin/interact \
--eval="Array.from(document.querySelectorAll('a')).filter(a => a.innerText.includes('Market 32')).map(a => a.href).join('\n')" \
--no-headless \
--user-data=~/.chrome-instacart
# Navigate to the store (use the URL found above)
${CLAUDE_PLUGIN_ROOT}/bin/navigate https://www.instacart.com/store/price-chopper-ny/storefront \
--no-headless \
--user-data=~/.chrome-instacart
步骤 3:搜索商品(使用 interact 执行多步操作)
现在使用 interact 搜索,不要离开当前页面:
# Click the search input
${CLAUDE_PLUGIN_ROOT}/bin/interact \
--click="#search-bar-input" \
--no-headless \
--user-data=~/.chrome-instacart
# Type the search query
${CLAUDE_PLUGIN_ROOT}/bin/interact \
--type="#search-bar-input=pork shoulder" \
--no-headless \
--user-data=~/.chrome-instacart
# Submit the search using JavaScript (trigger form submit event)
${CLAUDE_PLUGIN_ROOT}/bin/interact \
--eval="var input = document.querySelector('#search-bar-input'); var form = input.closest('form'); form.dispatchEvent(new Event('submit', {bubbles: true, cancelable: true})); 'submitted'" \
--no-headless \
--user-data=~/.chrome-instacart
步骤 4:查看搜索结果
检查有哪些商品可供选择:
# Wait a moment for results to load, then check the page content
sleep 2
${CLAUDE_PLUGIN_ROOT}/bin/interact \
--eval="document.body.innerText.substring(0, 2000)" \
--no-headless \
--user-data=~/.chrome-instacart
步骤 5:将商品加入购物车
点击所需商品对应的“添加”按钮:
# Find and click the first "Add" button
${CLAUDE_PLUGIN_ROOT}/bin/interact \
--eval="var addBtns = Array.from(document.querySelectorAll('button')).filter(b => b.innerText.trim() === 'Add'); if (addBtns.length > 0) { addBtns[0].click(); 'Added to cart'; } else { 'No Add button found'; }" \
--no-headless \
--user-data=~/.chrome-instacart
步骤 6:确认购物车已更新
确认商品已加入:
# Check cart count or page state
${CLAUDE_PLUGIN_ROOT}/bin/interact \
--eval="document.body.innerText.includes('2') ? 'Cart has 2 items' : document.body.innerText.substring(0, 500)" \
--no-headless \
--user-data=~/.chrome-instacart
多步自动化的关键技巧
1. 使用 JavaScript 查找元素
当 CSS 选择器过于复杂或具有动态性时,使用 --eval 查找元素:
# Find all buttons with specific text
--eval="Array.from(document.querySelectorAll('button')).filter(b => b.innerText.includes('Add')).length"
# Find links by text content
--eval="Array.from(document.querySelectorAll('a')).filter(a => a.innerText.includes('Checkout'))[0].href"
# Get input field IDs/names
--eval="JSON.stringify(Array.from(document.querySelectorAll('input')).map(i => ({id: i.id, name: i.name, placeholder: i.placeholder})))"
2. 通过 JavaScript 点击元素
当 --click 无效时(例如动态元素、覆盖层等):
# Click by finding element with JS
--eval="document.querySelector('.add-to-cart-btn').click(); 'clicked'"
# Click first matching element
--eval="Array.from(document.querySelectorAll('button')).filter(b => b.innerText === 'Add')[0].click(); 'clicked'"
3. 提交表单
提交表单的不同方法:
# Method 1: Dispatch submit event (most reliable for SPAs)
--eval="document.querySelector('form').dispatchEvent(new Event('submit', {bubbles: true, cancelable: true})); 'submitted'"
# Method 2: Click submit button
--click="button[type=submit]"
# Method 3: Call submit() directly
--eval="document.querySelector('form').submit(); 'submitted'"
4. 等待动态内容
使用 --wait-for 或 JavaScript 轮询:
# Wait for element to appear
--wait-for=".search-results"
# Or use JavaScript with timeout
--eval="new Promise(r => { let i = setInterval(() => { if (document.querySelector('.results')) { clearInterval(i); r('found'); }}, 100); setTimeout(() => { clearInterval(i); r('timeout'); }, 5000); })"
5. 读取页面状态
提取信息以便作出决策:
# Get current URL
--eval="window.location.href"
# Check if logged in
--eval="document.body.innerText.includes('Sign Out') ? 'logged in' : 'not logged in'"
# Get cart count
--eval="document.querySelector('.cart-count')?.innerText || '0'"
# Get product prices
--eval="JSON.stringify(Array.from(document.querySelectorAll('.price')).map(p => p.innerText))"
多步工作流故障排除
问题:命令之间页面重置
解决方案:初始导航之后的所有步骤都使用 interact,而不是 navigate。
问题:找不到元素
解决方案:
- 使用
--eval检查页面结构 - 使用
--wait-for或sleep等待动态内容 - 检查元素是否位于 iframe 中
问题:点击无效
解决方案:
- 尝试使用 JavaScript 点击:
--eval="document.querySelector('...').click()" - 先将元素滚动到可视区域
- 检查是否有覆盖层挡住元素
问题:表单无法提交
解决方案:
- 使用表单提交事件:
form.dispatchEvent(new Event('submit', ...)) - 检查它是否是采用自定义表单处理方式的 SPA
- 查找并点击实际的提交按钮
问题:登录会话丢失
解决方案:
- 始终使用相同的
--user-data路径 - 检查 Cookie 是否已过期
- 某些网站需要定期重新登录
最佳实践
- 对于需要登录的网站,始终使用
--user-data - 开发期间使用
--no-headless,以便查看正在发生的情况 - 使用
navigate加载初始页面 - 页面加载后,后续所有操作都切换到
interact - 尽可能多地使用
--eval检查页面状态并查找元素 - 如果网站需要时间更新,在操作之间添加
sleep - 每一步之后检查结果,确认操作是否成功
Instacart 自动化指南(经过测试验证的模式)
本节包含用于自动化 Instacart 购物的实战验证模式。这些模式经过大量真实场景测试。
快速参考:必备命令
# Base command pattern (always use these flags)
INTERACT="${CLAUDE_PLUGIN_ROOT}/bin/interact --no-headless --user-data=~/.chrome-instacart"
NAVIGATE="${CLAUDE_PLUGIN_ROOT}/bin/navigate --no-headless --user-data=~/.chrome-instacart"
# Suppress stderr noise
$INTERACT --eval="..." 2>/dev/null
选择商店
重要:切勿手动构造 Instacart URL。 始终通过 UI 导航:
# Go to homepage first
$NAVIGATE "https://www.instacart.com"
# Find store links on page
$INTERACT --eval="Array.from(document.querySelectorAll('a')).filter(a => a.innerText.includes('Stop & Shop')).map(a => ({text: a.innerText, href: a.href}))"
# Click the store link found above (or navigate to storefront)
$NAVIGATE "https://www.instacart.com/store/stop-shop/storefront"
搜索模式(可靠的三步流程)
# Step 1: Clear existing search (IMPORTANT - prevents concatenation)
$INTERACT --eval="var input = document.querySelector('#search-bar-input'); input.select(); document.execCommand('delete'); 'cleared'" 2>/dev/null
# Step 2: Type search term
$INTERACT --type="#search-bar-input=pork shoulder" 2>/dev/null
# Step 3: Submit form (dispatchEvent is most reliable for SPAs)
$INTERACT --eval="document.querySelector('#search-bar-input').closest('form').dispatchEvent(new Event('submit', {bubbles: true, cancelable: true})); 'submitted'" 2>/dev/null && sleep 3
提高效率的单行组合命令:
$INTERACT --eval="var input = document.querySelector('#search-bar-input'); input.select(); document.execCommand('delete'); 'cleared'" 2>/dev/null && \
$INTERACT --type="#search-bar-input=SEARCH_TERM" 2>/dev/null && \
$INTERACT --eval="document.querySelector('#search-bar-input').closest('form').dispatchEvent(new Event('submit', {bubbles: true, cancelable: true})); 'submitted'" 2>/dev/null && sleep 3
查找商品:发现有货商品
列出可用的“添加”按钮(显示商品名称):
$INTERACT --eval="JSON.stringify(Array.from(document.querySelectorAll('button[aria-label*=\"Add\"]')).slice(0,8).map(b => b.getAttribute('aria-label')))" 2>/dev/null
获取页面内容以查看价格和规格:
$INTERACT --eval="document.body.innerText.substring(0, 4000)" 2>/dev/null
将商品添加到购物车
Instacart 使用 aria-label 标注商品按钮。常见模式:
| 操作 | Aria-label 模式 |
|---|---|
| 添加商品 | Add 1 ct [Product Name] 或 Add 1 lb [Product Name] |
| 增加数量 | Increment quantity of [Product Name] |
| 减少数量 | Decrement quantity of [Product Name] |
| 移除 | Remove [Product Name] |
添加商品(完全匹配):
$INTERACT --eval="var btn = document.querySelector('button[aria-label=\"Add 1 ct Store Brand Baby Spinach\"]'); if(btn) { btn.click(); 'Added'; } else { 'Not found'; }" 2>/dev/null
添加商品(部分匹配,更灵活):
$INTERACT --eval="var btn = document.querySelector('button[aria-label*=\"Add\"][aria-label*=\"Spinach\"]'); if(btn) { btn.click(); 'Added'; } else { 'Not found'; }" 2>/dev/null
数量管理
增加数量(添加更多相同商品):
$INTERACT --eval="var btn = document.querySelector('button[aria-label*=\"Increment\"][aria-label*=\"Avocado\"]'); if(btn) { btn.click(); 'Incremented'; } else { 'Not found'; }" 2>/dev/null
多次增加数量(例如,购买 6 个牛油果):
# Add first one, then increment 5 times
$INTERACT --eval="document.querySelector('button[aria-label*=\"Add\"][aria-label*=\"Avocado\"]').click(); 'Added 1'" 2>/dev/null && sleep 1
for i in 1 2 3 4 5; do
sleep 0.5
$INTERACT --eval="var btn = document.querySelector('button[aria-label*=\"Increment\"][aria-label*=\"Avocado\"]'); if(btn) btn.click(); 'Inc'" 2>/dev/null
done
使用 JavaScript 循环多次增加数量(更快):
$INTERACT --eval="for(let i=0;i<5;i++){var btn=document.querySelector('button[aria-label*=\"Increment\"][aria-label*=\"Lemon\"]');if(btn)btn.click();} 'Incremented to 6'" 2>/dev/null
从购物车中移除商品:
$INTERACT --eval="var btn = document.querySelector('button[aria-label=\"Remove Bertolli Original Extra Virgin Olive Oil\"]'); if(btn) { btn.click(); 'Removed'; } else { 'Not found'; }" 2>/dev/null
购物车管理
查看购物车(点击购物车徽标):
$INTERACT --eval="var badge = Array.from(document.querySelectorAll('*')).find(el => /^\\d+$/.test(el.innerText.trim()) && parseInt(el.innerText) > 10 && el.offsetWidth < 50); if(badge) { var parent = badge.closest('a') || badge.closest('button') || badge.parentElement.parentElement; if(parent) parent.click(); 'clicked cart'; } else { 'cart not found'; }" 2>/dev/null && sleep 2
获取购物车总价:
$INTERACT --eval="document.body.innerText.match(/\\$\\d{2,3}\\.\\d{2}/) ? document.body.innerText.match(/\\$\\d{2,3}\\.\\d{2}/)[0] : 'no total found'" 2>/dev/null
列出购物车中的所有商品及其价格(购物车面板打开时):
$INTERACT --eval="document.body.innerText.substring(5500, 12000)" 2>/dev/null
查找购物车中的所有移除/减少按钮:
$INTERACT --eval="Array.from(document.querySelectorAll('button')).filter(b => b.getAttribute('aria-label') && (b.getAttribute('aria-label').includes('Remove') || b.getAttribute('aria-label').includes('Decrement'))).slice(0,20).map(b => b.getAttribute('aria-label')).join(' | ')" 2>/dev/null
完整购物流程
添加多个商品的高效模式:
# Function to search and add item
add_item() {
local search="$1"
local product="$2"
local qty="${3:-1}"
# Search
$INTERACT --eval="var input = document.querySelector('#search-bar-input'); input.select(); document.execCommand('delete'); 'cleared'" 2>/dev/null
$INTERACT --type="#search-bar-input=$search" 2>/dev/null
$INTERACT --eval="document.querySelector('#search-bar-input').closest('form').dispatchEvent(new Event('submit', {bubbles: true, cancelable: true})); 'submitted'" 2>/dev/null
sleep 3
# Add
$INTERACT --eval="var btn = document.querySelector('button[aria-label*=\"Add\"][aria-label*=\"$product\"]'); if(btn) { btn.click(); 'Added'; } else { 'Not found'; }" 2>/dev/null
sleep 1
# Increment if qty > 1
if [ "$qty" -gt 1 ]; then
for i in $(seq 2 $qty); do
sleep 0.5
$INTERACT --eval="var btn = document.querySelector('button[aria-label*=\"Increment\"][aria-label*=\"$product\"]'); if(btn) btn.click();" 2>/dev/null
done
fi
}
# Usage examples:
add_item "avocados" "Hass Avocado" 6
add_item "baby spinach" "Store Brand Baby Spinach" 2
add_item "bell peppers" "Green Bell Pepper" 2
Instacart 特定问题的故障排除
问题:搜索词被拼接在一起(例如,“lemonsfresh thyme”)
- 原因:之前的搜索文本未清除
- 解决方案:输入前始终使用
input.select(); document.execCommand('delete')清除
问题:找不到“添加”按钮
- 原因:aria-label 中的商品名称与搜索词不匹配
- 解决方案:
- 列出可用按钮:
JSON.stringify(Array.from(document.querySelectorAll('button[aria-label*="Add"]')).slice(0,8).map(b => b.getAttribute('aria-label'))) - 使用部分匹配:
aria-label*="Add"][aria-label*="partial name"
- 列出可用按钮:
问题:当前地址无法使用该商店
- 原因:Instacart 会根据配送地址显示不同的商店
- 解决方案:检查当前地址可用的商店,或更改地址
问题:购物车显示的商品数量有误
- 原因:某些商品按重量出售(例如,肉类显示为 1 件,但重量为 5 磅)
- 解决方案:这是正常现象,请查看购物车详情以确认实际数量
问题:不同商店的价格不同
- 原因:每家商店都有各自独立的定价
- 解决方案:比较不同商店的购物车,将价格较高的商品换成替代品
商品选择技巧
- 优先选择商店自有品牌 - 通常是最便宜的选项,搜索结果中经常会显示商店自有品牌
- 查看规格 - “16 oz”和“50.7 oz”会显著影响价格
- 优惠装 - 查看商品名称中是否含有“优惠装”,以获得更优惠的单件价格
- 按磅计价的商品 - 肉类/农产品按重量出售;数量表示重量,而非件数
- 留意促销 - 搜索结果中会显示“使用会员卡”的价格
经验总结
- 初次
navigate后务必使用interact- Instacart 是单页应用;导航会重置状态 - 开始新搜索前清空搜索框 - 避免出现文本拼接问题
- 使用 aria-label 选择器 - 查找 Instacart 按钮最可靠的方式
- 添加
2>/dev/null- 抑制 Chrome 驱动程序产生的嘈杂标准错误输出 - 提交搜索后添加
sleep 3- 留出时间等待结果加载 - 使用部分 aria-label 匹配(
*=) - 比精确匹配更可靠 - 定期检查购物车 - 确认商品已正确添加
- 商店是否可配送因地址而异 - 并非所有商店都能配送到每个地方