一、概述
网页自动化在数据抓取、UI 测试和业务流程优化中发挥着重要作用。然而,传统工具如 Selenium 和 Puppeteer 要求用户具备编程技能,编写复杂的选择器和脚本维护成本高昂。Midscene.js 通过自然语言接口革新了这一领域,用户只需描述任务(如“点击登录按钮”或“提取产品价格”),AI 即可自动执行,大幅降低技术门槛。
Midscene.js 由 web-infra-dev 团队开发,开源于 GitHub(GitHub - web-infra-dev/midscene),采用 MIT 许可。它支持多种 AI 模型,集成 Puppeteer 和 Playwright,提供丰富的功能。本文基于官网(Midscene.js)内容,全面解析其功能、模型、案例、技术细节和优化建议,为开发者提供详尽指南。
二、安装与配置
2.1. npm 安装
Midscene.js 提供两个包:
@midscene/web
:支持浏览器自动化,集成 Puppeteer/Playwright。@midscene/core
:核心功能,适合轻量级场景。
npm install @midscene/web
配置 GPT-4o:
import { overrideAIConfig } from '@midscene/web';
// 配置 AI 模型和密钥
overrideAIConfig({
OPENAI_API_KEY: '你的密钥', // OpenAI API 密钥,用于访问 GPT-4o
model: 'gpt-4o' // 默认使用 GPT-4o 模型
});
本地模型(如 UI-TARS):
// 配置本地运行的 UI-TARS 模型
overrideAIConfig({
model: 'ui-tars', // 开源 UI 自动化模型
endpoint: 'http://localhost:5000' // 本地服务地址
});
2.1.1.环境要求
建议 Node.js 18+,安装时确保网络畅通以拉取依赖。
2.2. Chrome 扩展
通过 Chrome Web Store 安装,提供无代码体验,直接在网页输入指令即可运行。
2.2.1. 使用步骤
- 访问 Chrome Web Store,点击“添加至 Chrome”。
- 在浏览器右上角扩展图标中输入指令,如“提取页面标题”。
2.3.环境变量与高级配置
支持环境变量:
MIDSCENE_OPENAI_API_KEY
:密钥。MIDSCENE_LANGSMITH_DEBUG
:调试模式(1
)。MIDSCENE_CACHE
:启用缓存(1
)。
运行时配置:
// 高级配置示例
overrideAIConfig({
OPENAI_API_KEY: '你的密钥', // 设置 API 密钥
model: 'gpt-4o', // 指定模型
cache: true, // 启用缓存以提升性能
timeout: 20000, // 设置超时时间为 20 秒
logLevel: 'verbose' // 设置详细日志级别
});
2.2.1. 本地部署
运行 UI-TARS:
docker run -p 5000:5000 ui-tars:latest # 启动 Docker 容器运行 UI-TARS 服务
配置:
// 配置本地模型连接
overrideAIConfig({
model: 'ui-tars',
endpoint: 'http://localhost:5000'
});
三、核心功能详解
3.1. 自然语言交互(aiAction)
通过自然语言执行操作:
// 在搜索框输入并点击搜索按钮
await agent.aiAction('在页面顶部的搜索框中输入 "JavaScript",然后点击旁边的搜索按钮');
使用技巧
- 具体性:指令越详细越好,如“点击右上角的红色按钮”。
- 多步骤:支持连续操作:
// 完整登录流程
await agent.aiAction('前往 https://example.com/login'); // 访问登录页面
await agent.aiAction('在用户名输入框中输入 "user123"'); // 输入用户名
await agent.aiAction('在密码输入框中输入 "pass123"'); // 输入密码
await agent.aiAction('点击 "登录" 按钮'); // 点击登录按钮
3.2. 数据提取(aiQuery)
提取结构化数据:
// 提取页面时间
const data = await agent.aiQuery({
time: '页面左上角显示的日期和时间,格式为字符串' // 定义提取目标
});
console.log(data); // 输出:{ time: "2025-03-22 10:00 AM" }
复杂提取:
// 提取产品信息
const product = await agent.aiQuery({
name: '产品名称,字符串格式', // 商品名称
price: '产品价格,数字格式', // 商品价格
stock: '库存状态,布尔值' // 是否有货
});
console.log(product); // 输出:{ name: "iPhone 15", price: 999, stock: true }
动态提取
const item = 'Sauce Labs Onesie'; // 定义商品名称变量
// 根据变量提取价格
const price = await agent.aiQuery({
price: `"${item}" 的价格,数字格式` // 动态生成查询条件
});
console.log(price); // 输出:{ price: 7.99 }
3.3. 条件断言(aiAssert)
验证页面状态:
// 验证价格是否正确
await agent.aiAssert('"Sauce Labs Onesie" 的价格是 7.99');
复杂条件:
// 检查购物车状态
await agent.aiAssert('购物车中的商品数量大于 3'); // 验证数量
await agent.aiAssert('"结账" 按钮可见且可用'); // 验证按钮状态
错误处理
try {
// 执行断言
await agent.aiAssert('页面显示 "登录成功"');
} catch (e) {
console.error('断言失败:', e.message); // 输出错误信息
}
3.4. 等待条件(aiWaitFor)
等待特定状态:
// 等待加载完成
await agent.aiWaitFor('加载动画不再可见', {
timeout: 30000, // 设置超时为 30 秒
interval: 5000 // 每 5 秒检查一次
});
结合操作
// 等待并执行
await agent.aiWaitFor('搜索结果已加载'); // 确保结果加载完成
await agent.aiAction('点击第一个搜索结果'); // 点击第一个结果
3.5. YAML 脚本(runYaml)
批量任务:
steps:
- action: '点击登录按钮' # 点击登录按钮
- action: '在用户名输入框中输入 "user123"' # 输入用户名
- action: '在密码输入框中输入 "pass123"' # 输入密码
- action: '点击提交按钮' # 提交表单
执行:
// 运行 YAML 脚本
await agent.runYaml('path/to/script.yaml');
复杂脚本
steps:
- action: '前往 https://shop.com' # 访问电商网站
- waitFor: '产品列表已加载' # 等待产品加载
- query:
products: '页面所有产品名称和价格,格式为数组 {name: string, price: number}' # 提取产品数据
- assert: '"iPhone 15" 的价格低于 1000' # 验证价格
3.6. 可视化调试
生成动画和报告:
// 启用 LangSmith 调试模式
process.env.MIDSCENE_LANGSMITH_DEBUG = '1'; // 设置环境变量以输出详细日志
await agent.aiAction('点击 "注册" 按钮'); // 执行操作并生成调试报告
调试输出
报告示例:
- “定位到 ID 为 signup 的按钮”
- “点击坐标 (x: 100, y: 50),耗时 300ms”
四、支持的 AI 模型
4.1. GPT-4o
- 特点:OpenAI 多模态模型,支持文本和图像处理。
- 适用:通用自动化任务。
- 配置:
// 配置 GPT-4o 模型
overrideAIConfig({
model: 'gpt-4o', // 模型名称
OPENAI_API_KEY: '你的密钥' // OpenAI API 密钥
});
- 限制:无法操作跨域 iframe 或 canvas。
4.2. UI-TARS
- 特点:开源,支持图像识别和拖拽。
- 适用:复杂 UI 交互。
- 配置:
// 配置本地 UI-TARS 模型
overrideAIConfig({
model: 'ui-tars', // 模型名称
endpoint: 'http://localhost:5000' // 本地服务地址
});
4.3. Qwen2.5-VL
- 特点:阿里云视觉语言模型,擅长图像和文本混合处理。
- 适用:图像相关任务。
- 配置:
// 配置本地 Qwen2.5-VL 模型
overrideAIConfig({
model: 'qwen2.5-vl', // 模型名称
endpoint: 'http://localhost:6000' // 本地服务地址
});
模型对比
模型 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
GPT-4o | 易用、多模态、性能稳定 | 云依赖、跨域限制 | 快速原型、通用任务 |
UI-TARS | 本地部署、支持拖拽、隐私友好 | 配置复杂 | 企业应用、复杂 UI |
Qwen2.5-VL | 视觉能力强、无跨域限制 | 部署需技术支持 | 图像处理、隐私敏感任务 |
五、实际应用案例
5.1. 社交媒体自动化
自动发布 X 帖子:
// 自动发帖流程
await agent.aiAction('点击 "发帖" 按钮'); // 打开发帖框
await agent.aiAction('在文本框中输入 "Midscene.js 真好用!"'); // 输入内容
await agent.aiAction('点击 "发布" 按钮'); // 提交帖子
定时发帖:
const cron = require('node-cron');
// 每天凌晨定时发帖
cron.schedule('0 0 * * *', async () => {
await agent.aiAction('发布一条帖子,内容为 "每日更新 by Midscene.js"'); // 定时发布
});
5.2. 数据收集
收集音乐会信息:
// 访问网站并提取数据
await agent.aiAction('前往 https://concert-site.com'); // 打开音乐会网站
const concertData = await agent.aiQuery({
event: '音乐会名称', // 提取活动名称
date: '音乐会日期', // 提取活动日期
location: '举办地点' // 提取活动地点
});
console.log(concertData); // 输出结果
5.3. 测试与验证
验证电商价格:
// 检查价格和按钮状态
await agent.aiAssert('"iPhone 15" 的价格低于 1000 美元'); // 验证价格
await agent.aiAssert('"加入购物车" 按钮可见且可用'); // 验证按钮状态
5.4. 电商价格监控
监控产品价格:
async function monitorPrice() {
// 访问产品页面
await agent.aiAction('前往 https://shop.com/product/123');
// 提取当前价格
const priceData = await agent.aiQuery({
price: '当前产品价格,数字格式' // 获取价格
});
console.log(`当前价格: ${priceData.price}`);
// 检查价格是否低于阈值
if (priceData.price < 800) {
console.log('价格低于 800,发送通知!');
// 可集成邮件通知
}
}
// 每小时检查一次
setInterval(monitorPrice, 60 * 60 * 1000);
保存历史价格
const fs = require('fs');
// 保存价格历史到 CSV 文件
async function savePriceHistory() {
const priceData = await agent.aiQuery({
price: '当前产品价格,数字格式' // 提取价格
});
const record = `${new Date().toISOString()},${priceData.price}\n`; // 格式化记录
fs.appendFileSync('price_history.csv', record); // 追加到文件
}
setInterval(savePriceHistory, 60 * 60 * 1000); // 每小时执行
5.5. 表单自动填写
自动填写注册表单:
async function autoFillForm() {
// 访问注册页面
await agent.aiAction('前往 https://example.com/register');
// 等待表单加载
await agent.aiWaitFor('注册表单已加载'); // 确保表单可见
// 填写表单字段
await agent.aiAction('在用户名输入框中输入 "testuser"'); // 输入用户名
await agent.aiAction('在邮箱输入框中输入 "test@example.com"'); // 输入邮箱
await agent.aiAction('在密码输入框中输入 "Password123"'); // 输入密码
// 提交表单
await agent.aiAction('点击 "提交" 按钮'); // 提交表单
// 验证结果
await agent.aiAssert('页面显示 "注册成功"'); // 检查注册是否成功
}
autoFillForm().catch(console.error);
批量填写
const users = [
{ username: 'user1', email: 'user1@example.com', password: 'Pass1' },
{ username: 'user2', email: 'user2@example.com', password: 'Pass2' }
];
// 批量注册多个用户
for (const user of users) {
await agent.aiAction(`在用户名输入框中输入 "${user.username}"`); // 输入用户名
await agent.aiAction(`在邮箱输入框中输入 "${user.email}"`); // 输入邮箱
await agent.aiAction(`在密码输入框中输入 "${user.password}"`); // 输入密码
await agent.aiAction('点击 "提交" 按钮'); // 提交表单
}
5.6. 动态网页抓取
抓取动态加载内容:
async function scrapeDynamicContent() {
// 访问动态网页
await agent.aiAction('前往 https://dynamic-site.com');
// 等待内容加载
await agent.aiWaitFor('动态内容已加载', { timeout: 60000 }); // 等待 60 秒
// 提取文章标题和摘要
const articles = await agent.aiQuery({
articles: '页面所有文章标题和摘要,格式为数组 {title: string, summary: string}' // 提取动态数据
});
console.log(articles);
// 保存到 JSON 文件
fs.writeFileSync('articles.json', JSON.stringify(articles, null, 2)); // 写入文件
}
scrapeDynamicContent().catch(console.error);
处理分页
async function scrapeAllPages() {
let page = 1;
const allArticles = [];
while (true) {
// 等待当前页面加载
await agent.aiWaitFor(`第 ${page} 页内容已加载`); // 确保页面加载完成
// 提取数据
const articles = await agent.aiQuery({
articles: '当前页面所有文章标题,格式为数组 {title: string}' // 提取标题
});
allArticles.push(...articles.articles); // 追加到总数组
// 检查是否有下一页
const hasNext = await agent.aiQuery({
hasNext: '是否存在 "下一页" 按钮,布尔值' // 检查分页按钮
});
if (!hasNext.hasNext) break; // 无下一页则退出
// 点击下一页
await agent.aiAction('点击 "下一页" 按钮'); // 翻页
page++;
}
console.log(`共抓取 ${allArticles.length} 篇文章`); // 输出总数
}
scrapeAllPages();
5.7. 自动化客服
模拟客服回复:
async function autoReply() {
// 访问客服页面
await agent.aiAction('前往 https://support.com/chat');
// 等待新消息
await agent.aiWaitFor('新客户消息出现'); // 等待消息加载
// 获取消息内容
const message = await agent.aiQuery({
message: '最新客户消息,字符串格式' // 提取最新消息
});
// 根据消息内容回复
if (message.message.includes('价格')) {
await agent.aiAction('在回复框中输入 "我们的价格请查看官网"'); // 回复价格问题
} else {
await agent.aiAction('在回复框中输入 "请稍等,我为您查询"'); // 默认回复
}
await agent.aiAction('点击 "发送" 按钮'); // 发送回复
}
// 每分钟检查一次
setInterval(autoReply, 60 * 1000);
多语言支持
async function replyInLanguage(lang) {
const message = await agent.aiQuery({
message: '最新客户消息,字符串格式' // 获取最新消息
});
// 根据语言回复
if (lang === 'zh') {
await agent.aiAction(`在回复框中输入 "感谢您的消息,请稍等"`);
} else {
await agent.aiAction(`在回复框中输入 "Thank you for your message, please wait"`);
}
await agent.aiAction('点击 "发送" 按钮'); // 发送回复
}
replyInLanguage('zh'); // 中文回复
六、技术细节与集成
Puppeteer 集成
const puppeteer = require('puppeteer');
const { Midscene } = require('@midscene/web');
// 启动浏览器
const browser = await puppeteer.launch({ headless: true }); // 无头模式运行
const page = await browser.newPage();
const agent = new Midscene(page); // 创建 Midscene 实例
// 执行操作
await agent.aiAction('前往 https://example.com'); // 访问页面
await agent.aiAction('点击 "注册" 按钮'); // 点击注册按钮
自定义配置
// 配置代理和无头模式
const browser = await puppeteer.launch({
headless: true, // 无头模式
args: ['--proxy-server=http://proxy:8080'] // 设置代理
});
缓存机制
提升性能:
// 启用默认内存缓存
overrideAIConfig({ cache: true });
// 自定义文件缓存
const fs = require('fs');
overrideAIConfig({
cache: {
get: (key) => fs.readFileSync(`cache/${key}.json`, 'utf8'), // 从文件读取缓存
set: (key, value) => fs.writeFileSync(`cache/${key}.json`, value) // 保存到文件
}
});
缓存清理
// 每天清理缓存
setInterval(() => {
fs.rmSync('cache', { recursive: true, force: true }); // 删除缓存目录
console.log('缓存已清理');
}, 24 * 60 * 60 * 1000); // 每天执行一次
数据隐私
本地部署 Qwen2.5-VL:
docker run -p 6000:6000 qwen2.5-vl:latest # 启动 Qwen2.5-VL 服务
配置:
// 使用本地模型保护隐私
overrideAIConfig({
model: 'qwen2.5-vl',
endpoint: 'http://localhost:6000'
});
隐私验证
// 测试数据是否外泄
await agent.aiAction('在输入框中输入 "敏感数据"');
console.log('检查本地服务日志,确保数据未上传云端');
七、限制与优化
限制
- 交互类型:仅支持点击、输入、滚动等,拖拽限于 UI-TARS。
- AI 稳定性:自然语言解析可能出错。
- 跨域限制:GPT-4o 无法操作跨域 iframe。
优化建议
- 提示优化:用“点击蓝色提交按钮”代替“点击按钮”。
- 重试机制:
async function retryAction(action, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
// 执行操作
await agent.aiAction(action);
return;
} catch (e) {
console.warn(`第 ${i + 1}/${retries} 次重试: ${e.message}`); // 输出重试信息
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待 1 秒
}
}
throw new Error('操作失败'); // 重试失败抛出错误
}
// 重试点击操作
await retryAction('点击 "提交" 按钮');
性能监控
// 记录执行时间
const start = Date.now();
await agent.aiAction('点击 "搜索" 按钮');
console.log(`执行时间: ${Date.now() - start}ms`); // 输出耗时
并行处理
// 并行执行多个任务
const tasks = [
agent.aiAction('点击 "产品" 菜单'),
agent.aiAction('点击 "关于我们" 菜单')
];
await Promise.all(tasks); // 同时执行多个操作
八、社区生态与贡献
Midscene.js 的开源特性促成了活跃社区:
- 示例:X 发帖、数据收集(见 GitHub)。
- 问题跟踪:通过 GitHub Issues 提交 bug。
- 贡献:添加新模型或功能。
贡献步骤
- Fork 仓库。
- 修改代码,例如添加模型:
// 添加自定义模型
overrideAIConfig({
model: 'my-model',
endpoint: 'http://my-server:8000'
});
- 提交 Pull Request。
社区案例
用户贡献的 YAML 示例:
steps:
- action: '前往 https://news.com' # 访问新闻网站
- query:
headlines: '头条新闻标题,格式为数组 {title: string}' # 提取头条
九、参考资料
- Midscene.js 官网
- GitHub 仓库
- API 文档
- 模型选择