
“`html
一、传统爬虫的困境:为什么你的爬虫总是被封
做网页数据采集的朋友大概都遇到过这种场景:用requests写好的爬虫跑得好好的,突然某天全部请求返回403;辛辛苦苦模拟登录成功,结果下一秒访问其他页面就被踢回登录页;好不容易抓到的数据,因为网站全站改成React/Vue框架渲染,HTML里只剩下一个空的div。
这些问题不是你的代码写得不够好,而是传统HTTP请求式的爬虫天然存在缺陷。网站越来越”聪明”,它们用JavaScript动态渲染页面、用浏览器指纹识别机器人、用验证码和行为分析来判断访问者是人还是程序。requests发出去的请求在服务器眼里就是一张”无脸照”,而真正的浏览器却能”刷脸入场”。
OpenClaw正是为解决这些问题而生的。它本质上是一个高级浏览器自动化工具,通过程序直接控制真实的Chrome浏览器内核执行操作,让网站无法从技术层面区分自动化访问和真实用户访问。不只是”能加载JS”这么简单——它能完整保留登录态、精准填写表单、智能处理弹窗、甚至模拟多账号并发操作。
二、5分钟快速上手:安装与第一个自动化脚本
安装OpenClaw环境
OpenClaw基于Node.js生态,安装前请确保本地有Node.js环境(v16以上)。在项目目录下执行安装命令:
npm install openclaw --save npm install puppeteer-core --save # OpenClaw依赖的底层驱动
如果你需要完整的浏览器内核(首次安装会下载约150MB的Chrome),使用:
npm install openclaw --save npx openclaw install chrome # 安装完整Chrome浏览器
写一个最基本的脚本
打开百度、搜索关键词、截图保存——这是浏览器自动化的”Hello World”。完整代码如下:
const { OpenClaw } = require('openclaw');
(async () => {
const browser = await OpenClaw.launch({
headless: false, // 生产环境设为true
userDataDir: './browser-data', // 保留浏览器配置
});
const page = await browser.newPage();
await page.goto('https://www.baidu.com');
// 搜索框输入内容
await page.type('#kw', 'OpenClaw浏览器自动化');
await page.click('#su');
// 等待搜索结果加载
await page.waitForSelector('.result');
// 截图保存
await page.screenshot({ path: 'search-result.png', fullPage: true });
console.log('截图已保存');
await browser.close();
})();这段代码展示了OpenClaw的核心逻辑:启动浏览器 → 创建页面 → 执行操作 → 关闭资源。注意到userDataDir参数了吗?这是保持登录态的关键所在。
三、实战一:自动登录与表单批量填写
模拟登录并保持会话
很多数据采集场景需要先登录账号。传统做法是抓包分析登录接口、用requests模拟POST请求——这种方式在验证码、短信登录、风控检测面前几乎束手无策。用OpenClaw模拟真人操作登录,就能绕过这些障碍。
const { OpenClaw } = require('openclaw');
const fs = require('fs');
(async () => {
const browser = await OpenClaw.launch({
userDataDir: './user-session-001',
headless: false,
});
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...',
});
const page = await context.newPage();
// 打开登录页
await page.goto('https://example.com/login');
// 填写用户名密码(选择器根据实际页面调整)
await page.fill('input[name="username"]', 'your_account');
await page.fill('input[name="password"]', 'your_password');
// 点击登录按钮
await page.click('button[type="submit"]');
// 等待登录成功后的跳转
await page.waitForURL('**/dashboard**', { timeout: 10000 });
// 保存登录状态(后续脚本直接复用userDataDir即可保持登录)
console.log('登录成功,当前页面:', page.url());
// 后续操作...
await browser.close();
})();关键点在于:第一次登录时指定userDataDir路径,后续脚本复用这个路径打开浏览器时,之前登录的Cookie和Session都会被保留。这样做的好处是避免每次都重新登录,也不需要手动处理Token传递。
批量填写表单:抓取房产信息实战
比登录更常见的需求是批量填写表单。比如某房产平台需要逐个查询小区房价,我们用OpenClaw实现自动化查询:
const { OpenClaw } = require('openclaw');
const districts = ['浦东新区', '徐汇区', '静安区', '黄浦区'];
(async () => {
const browser = await OpenClaw.launch({ headless: true });
const page = await browser.newPage();
const results = [];
for (const district of districts) {
try {
await page.goto('https://example-house.com/search');
// 清除之前的输入
await page.click('input#district-input', { clickCount: 3 });
await page.keyboard.press('Backspace');
// 输入区域名称
await page.fill('input#district-input', district);
// 点击搜索
await page.click('button.search-btn');
// 等待结果加载
await page.waitForSelector('.house-list .house-item', { timeout: 5000 });
// 提取数据
const items = await page.$$eval('.house-item', els => {
return els.slice(0, 10).map(el => ({
title: el.querySelector('.title')?.innerText,
price: el.querySelector('.price')?.innerText,
area: el.querySelector('.area')?.innerText,
}));
});
results.push({ district, items });
console.log(`${district} 查询完成,获取 ${items.length} 条数据`);
} catch (err) {
console.error(`${district} 查询失败:`, err.message);
}
}
// 保存结果
const fs = require('fs');
fs.writeFileSync('house-data.json', JSON.stringify(results, null, 2));
await browser.close();
console.log('全部查询完成,结果已保存');
})();这个脚本循环查询四个区域、每个区域取前10条房源信息。用try-catch包裹每个查询步骤,即使某个区域查询失败也不会中断整个流程。真实项目中建议加入随机延时(await page.waitForTimeout(randomDelay()))模拟人类操作节奏,降低被识别为机器人的概率。
四、实战二:价格监控与数据自动采集
电商价格监控
价格监控是浏览器自动化的经典应用场景。比价网站、促销活动追踪、竞品价格分析——这些需求用传统爬虫实现需要处理复杂的反爬机制,而用OpenClaw可以直接”看”到页面内容。
const { OpenClaw } = require('openclaw');
class PriceMonitor {
constructor() {
this.browser = null;
this.targets = [
{ name: 'iPhone15 Pro', url: 'https://example-shop.com/product/iphone15pro' },
{ name: 'MacBook Air M3', url: 'https://example-shop.com/product/macbook-air-m3' },
];
}
async start() {
this.browser = await OpenClaw.launch({ headless: true });
const results = [];
for (const target of this.targets) {
const price = await this.getPrice(target.url);
results.push({
name: target.name,
price,
timestamp: new Date().toISOString(),
});
console.log(`${target.name}: ¥${price}`);
}
await this.browser.close();
return results;
}
async getPrice(url) {
const page = await this.browser.newPage();
// 设置额外的请求头
await page.setExtraHTTPHeaders({
'Accept-Language': 'zh-CN,zh;q=0.9',
});
await page.goto(url, { waitUntil: 'networkidle2' });
// 等待价格元素加载(可能有多处价格,取主价格)
await page.waitForSelector('.product-price', { timeout: 10000 });
// 提取价格文本
const priceText = await page.$eval('.product-price', el => el.innerText);
// 清洗价格数据(去掉"¥"符号和逗号)
const price = parseFloat(priceText.replace(/[¥,]/g, ''));
await page.close();
return price;
}
}
// 执行监控
const monitor = new PriceMonitor();
const data = await monitor.start();
console.log('监控数据:', data);这个监控类的设计思路值得借鉴:把浏览器实例作为类属性复用,每个目标单独开一个page处理,任务完成后统一关闭。这样既保证了并发效率,又避免了资源泄漏。把这段代码配合cron定时任务,就能实现每日自动价格采集。
登录态维持的进阶技巧
前面提到用userDataDir保持登录态,但实际场景更复杂:Token过期怎么办?多账号怎么管理?这里介绍一个更稳健的方案:
const { OpenClaw } = require('openclaw');
const fs = require('fs');
// 管理多个账号的登录状态
class SessionManager {
constructor() {
this.sessionDir = './sessions';
if (!fs.existsSync(this.sessionDir)) {
fs.mkdirSync(this.sessionDir, { recursive: true });
}
}
// 获取或创建指定账号的浏览器上下文
async getContext(accountId) {
const browser = await OpenClaw.launch({
userDataDir: `${this.sessionDir}/${accountId}`,
headless: true,
});
const context = await browser.newContext();
const page = await context.newPage();
// 检查登录状态
await page.goto('https://example.com/api/check-login');
const loginStatus = await page.evaluate(() => {
return document.body.innerText.includes('"loggedIn":true');
});
if (!loginStatus) {
console.log(`账号 ${accountId} 未登录或登录已过期`);
await browser.close();
return null;
}
return { browser, context, page };
}
}
// 使用示例
(async () => {
const manager = new SessionManager();
const session1 = await manager.getContext('user_001');
if (session1) {
await session1.page.goto('https://example.com/user-center');
// 进行需要登录的操作
await session1.browser.close();
}
})();这个方案的核心思路是:每个账号独立一个userDataDir目录,首次登录后状态会被持久化保存。定期运行”检查登录状态”的脚本,如果发现登录失效,就重新执行登录流程并更新目录。这样可以确保长期稳定运行。
五、批量采集的最佳实践与性能优化
并发控制与资源管理
批量采集时很多人会陷入两个极端:串行执行太慢、并发太高被封。这里给出一个经过验证的并发控制模式:
const { OpenClaw } = require('openclaw');
// 并发控制器:控制同时打开的浏览器数量
class ConcurrencyController {
constructor(maxConcurrent = 3) {
this.maxConcurrent = maxConcurrent;
this.running = 0;
this.queue = [];
}
async run(task) {
return new Promise((resolve, reject) => {
const execute = async () => {
this.running++;
try {
const result = await task();
resolve(result);
} catch (err) {
reject(err);
} finally {
this.running--;
this.processQueue();
}
};
if (this.running < this.maxConcurrent) {
execute();
} else {
this.queue.push(execute);
}
});
}
processQueue() {
if (this.queue.length > 0) {
const next = this.queue.shift();
next();
}
}
}
// 批量采集函数
async function scrapeAll(urls) {
const controller = new ConcurrencyController(3); // 最多3个并发
const browser = await OpenClaw.launch({ headless: true });
const tasks = urls.map((url, index) => async () => {
console.log(`开始采集: ${url} (并发数: ${controller.running})`);
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'domcontentloaded' });
// 提取数据
const data = await page.evaluate(() => {
return {
title: document.querySelector('h1')?.innerText,
content: document.querySelector('.content')?.innerText,
};
});
await page.close();
// 模拟人类操作间隔(随机1-3秒)
await new Promise(r => setTimeout(r, 1000 + Math.random() * 2000));
return data;
});
const results = await Promise.all(tasks.map(t => controller.run(t)));
await browser.close();
return results;
}这个并发控制器的设计参考了计算机科学中的信号量(Semaphore)模式。通过限制同时运行的浏览器实例数量,既能保证采集效率,又不会因为并发过高触发网站的反爬机制。配合随机延时,真实用户很难分辨这是机器人还是真人操作。
错误处理与断点续采
大规模采集任务必须考虑容错。一个设计良好的采集器应该能做到:单条失败不影响全局、任务中断后能断点续采。
const fs = require('fs');class ResumableScraper {
constructor(outputFile) {
this.outputFile = outputFile;
this.processedFile = outputFile + '.processed';
}
// 读取已处理列表
getProcessedIds() {
if (fs.existsSync(this.processedFile)) {
return new Set(fs.readFileSync(this.processedFile, 'utf-8').split('\n').filter(Boolean));
}
return new Set();
}
// 标记已处理
markProcessed(id) {
fs.appendFileSync(this.processedFile, id + '\n');
}
// 保存结果(追加模式)
saveResult(result) {
const line = JSON.stringify(result) + '\n';
fs.appendFileSync(this.outputFile, line);
}
}
// 使用示例
(async () => {
const scraper = new ResumableScraper('results.jsonl');
const processed = scraper.getProcessedIds();
const allUrls = [
'https://example.com/item/1',
'https://example.com/item/2',
// ... 更多URL
];
const browser = await OpenClaw.launch({ headless: true });
for (const url of allUrls) {
const itemId = url.split('/').pop();
// 跳过已处理的
if (processed.has(itemId)) {
console.log(`跳过: ${itemId}`);
continue;
}
try {
const page = await browser.newPage();
await page.goto(url, { timeout: 15000 });
const data = await page.evaluate(() => {
return { /*