运行时依赖
安装命令
点击复制技能文档
GasBuddy 浏览器技能 ⚠️ 重要:使用 Playwright 通过 exec 工具,而不是内置的浏览器工具。内置的 OpenClaw Edge CDP 浏览器无法可靠地等待 Cloudflare 的基于时间的挑战。直接使用 Node.js 的 Playwright 可以导航到城市 URL 并等待挑战自动解决。
快速工作流程 导航到城市汽油价格 URL(默认为 North York,例如 https://www.gasbuddy.com/gasprices/ontario/north-york) 等待 Cloudflare 挑战清除 — 标题将从 "Just a moment..." 改变为 "Best Gas Prices & Local Gas Stations in..." 逐步滚动以加载所有加油站卡片 使用 [class="stationListItem"] 选择器和价格正则表达式提取加油站数据 返回前 5 个最便宜的加油站
Playwright 脚本模板
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({
headless: false,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu']
// 不需要 --disable-blink-features=AutomationControlled 或自定义 UA
// Cloudflare 挑战是基于时间的,而不是基于检测的
});
const page = await browser.newPage();
await page.goto('https://www.gasbuddy.com/gasprices/ontario/north-york', {
timeout: 60000,
waitUntil: 'networkidle'
});
// 等待 Cloudflare 清除 — 检查标题变化
let attempts = 0;
while (attempts < 20) {
const title = await page.title();
if (!title.includes('Just a moment') && !title.includes('Cloudflare') && title.includes('Gas Prices')) break;
await page.waitForTimeout(3000);
attempts++;
}
// 逐步滚动以触发懒加载所有加油站卡片
await page.evaluate(() => window.scrollTo(0, 0));
await page.waitForTimeout(500);
for (let i = 0; i < 8; i++) {
await page.evaluate(() => window.scrollBy(0, 600));
await page.waitForTimeout(400);
}
await page.waitForTimeout(2000);
// 提取加油站数据使用实际的 CSS 类和价格格式
// 如果价格显示 "- - -"(不可用),我们重试以刷新页面
let stations = await page.evaluate(() => {
const cards = document.querySelectorAll('[class="stationListItem"]');
return Array.from(cards).map(card => {
const text = card.innerText;
const lines = text.split('\n').map(l => l.trim()).filter(l => l);
// 价格格式为 "XXX.X¢"(一位小数,例如 "167.9¢")
const priceMatch = text.match(/(\d+\.\d+)\s¢/);
const price = priceMatch ? priceMatch[1] : null;
// 检查 "- - -" 占位符,指示不可用价格
const hasDashPrice = text.includes('- - -') || text.includes('— — —');
// 品牌是第一个非空行
const brand = lines[0] || '';
// 地址是一个以数字开头,后跟字母的行
const address = lines.find(l => /^\d+\s+[A-Za-z]/.test(l)) || '';
return { brand, price, address, hasDashPrice };
}).filter(s => s.price && s.address);
});
// 如果价格显示 "- - -"(所有有 hasDashPrice 或价格为 null),刷新并重试
const hasValidPrices = stations.length > 0 && stations.some(s => s.price && !s.hasDashPrice);
if (!hasValidPrices) {
console.log('价格未加载(显示 "- - -"),刷新...');
await page.reload({ timeout: 30000, waitUntil: 'networkidle' });
await page.waitForTimeout(3000);
// 重新滚动以加载所有加油站卡片
for (let i = 0; i < 5; i++) {
await page.evaluate(() => window.scrollBy(0, 600));
await page.waitForTimeout(400);
}
stations = await page.evaluate(() => {
const cards = document.querySelectorAll('[class="stationListItem"]');
return Array.from(cards).map(card => {
const text = card.innerText;
const lines = text.split('\n').map(l => l.trim()).filter(l => l);
const priceMatch = text.match(/(\d+\.\d+)\s*¢/);
const price = priceMatch ? priceMatch[1] : null;
const brand = lines[0] || '';
const address = lines.find(l => /^\d+\s+[A-Za-z]/.test(l)) || '';
return { brand, price, address };
}).filter(s => s.price && s.address);
});
}
// 按价格升序排序
stations.sort((a, b) => parseFloat(a.price) - parseFloat(b.price));
const date = new Date().toLocaleDateString('en-CA', { year: 'numeric', month: 'short', day: 'numeric' });
console.log(\n North York Gas Prices (Regular 87) — ${date});
console.log('='.repeat(55));
stations.slice(0, 8).forEach((s, i) => {
const p = parseFloat(s.price);
console.log(${i+1}. ${s.brand} — $${(p/100).toFixed(3)}/L (${p}¢) — ${s.address});
});
await browser.close();
})();
Cloudflare 跳过策略 关键见解是基于时间的等待:Cloudflare 的托管挑战是基于时间的 —— 它在几秒钟后自动解决任何 IP navigator.webdriver=true 不是阻塞因素 —— Cloudflare 通过 Playwright 浏览器 --disable-blink-features=AutomationControlled 和自定义 UA 不需要