由Alice生成,注意鉴别
Playwright浏览器自动化工具核心实现解析
概述
这是一个基于Playwright的浏览器自动化工具,专门处理复杂的网页自动化操作。工具采用模块化设计,支持页面导航、元素操作、表单填写、文件下载等丰富功能。
核心架构设计
1. 实例管理机制
// 浏览器实例管理(每个执行独立上下文,避免并发干扰)
let browserInstance: Browser | null = null;
let contextInstance: BrowserContext | null = null;
let pageInstance: Page | null = null;
设计要点:
- 单例模式:全局维护浏览器、上下文和页面实例
- 状态隔离:确保每次操作都在正确的上下文中执行
- 资源管理:统一管理生命周期,防止资源泄漏
2. 配置系统
const config = {
headless: process.env.BROWSER_HEADLESS === 'true',
userDataDir: process.env.BROWSER_USER_DATA_DIR || undefined,
executablePath: process.env.BROWSER_EXECUTABLE_PATH || undefined,
downloadPath: process.env.BROWSER_DOWNLOAD_PATH || join(process.cwd(), 'downloads'),
viewport: { width: 1920, height: 1080 },
extraArgs: [] // 额外启动参数
};
配置特点:
- 环境变量驱动:所有配置通过环境变量控制
- 路径管理:自动处理下载目录创建
- 参数过滤:自动过滤冲突参数(如–user-data-dir)
核心功能实现
1. 浏览器启动逻辑
async function launchBrowser(params?: any): Promise<string> {
// 优先使用传入的headless参数,否则使用环境变量配置
const headless = params?.headless !== undefined ? params.headless : config.headless;
if (config.userDataDir) {
// 使用launchPersistentContext(新版Playwright推荐方式)
contextInstance = await chromium.launchPersistentContext(config.userDataDir, {
headless: headless,
// ...其他配置
});
browserInstance = contextInstance.browser();
} else {
// 普通启动
browserInstance = await chromium.launch({ headless: headless });
contextInstance = await browserInstance.newContext();
}
// 自动处理弹窗
contextInstance.on('page', page => {
page.on('dialog', async (dialog: Dialog) => {
await dialog.accept();
});
});
pageInstance = await contextInstance.newPage();
}
关键技术点:
- 双重启动模式:支持普通启动和持久化上下文启动
- 弹窗自动处理:默认接受所有弹窗,可通过handle_dialog覆盖
- 参数优先级:运行时参数 > 环境变量配置
2. 页面操作封装
导航功能
async function navigate(url: string, waitUntil: 'load' | 'domcontentloaded' | 'networkidle' = 'load'): Promise<string> {
const page = ensurePage();
await page.goto(url, { waitUntil: waitUntil as any });
return JSON.stringify({
success: true,
url: page.url(),
title: await page.title()
});
}
设计亮点:
- 等待策略:支持多种页面加载完成判断标准
- 错误处理:区分超时错误和一般错误
- 状态返回:返回实际URL和页面标题
元素操作
async function click(selector: string, index?: number): Promise<string> {
const page = ensurePage();
if (index !== undefined) {
// 支持按索引点击多个匹配元素
const elements = await page.$$(selector);
if (index >= elements.length) {
throw new Error(`索引 ${index} 超出范围`);
}
await elements[index].click();
} else {
await page.click(selector);
}
return JSON.stringify({ success: true, message: '已点击元素' });
}
特色功能:
- 索引支持:可点击多个匹配元素中的特定索引
- 超时控制:所有操作都支持自定义超时
- 错误分类:明确区分超时错误和其他错误
3. 内容获取与截断
// 统一截断函数:限制返回内容不超过10万字符
function truncateResponse(content: string): string {
const MAX_LENGTH = 90000; // 安全上限
if (content.length > MAX_LENGTH) {
return content.slice(0, MAX_LENGTH) + `\n\n[内容过长,已截断...]`;
}
return content;
}
async function getDOM(selector?: string): Promise<string> {
const page = ensurePage();
let html: string;
if (selector) {
// 获取特定元素的HTML
const element = await page.$(selector);
if (!element) throw new Error(`未找到元素: ${selector}`);
html = await element.innerHTML();
} else {
// 获取整个页面HTML
html = await page.content();
}
const response = JSON.stringify({ success: true, html, url: page.url() });
return truncateResponse(response); // 应用截断
}
安全设计:
- 内存保护:防止大页面导致内存溢出
- 友好提示:截断时显示原始长度信息
- JSON安全:确保截断后的JSON仍然有效
4. 标签页管理
async function switchTab(tabIndex: number): Promise<string> {
if (!contextInstance) throw new Error('浏览器上下文未初始化');
const pages = contextInstance.pages();
if (tabIndex >= pages.length || tabIndex < 0) {
throw new Error(`标签页索引 ${tabIndex} 超出范围`);
}
pageInstance = pages[tabIndex];
await pageInstance.bringToFront(); // 激活标签页
return JSON.stringify({
success: true,
currentTab: tabIndex,
totalTabs: pages.length,
url: pageInstance.url()
});
}
标签页特性:
- 索引管理:基于0的标签页索引
- 自动激活:切换时自动将标签页提到最前
- 状态同步:更新当前页面实例引用
5. 文件下载处理
async function downloadFile(downloadUrl: string, filename?: string): Promise<string> {
const page = ensurePage();
// 等待下载事件
const downloadPromise = page.waitForEvent('download');
await page.goto(downloadUrl, { waitUntil: 'domcontentloaded' });
const download = await downloadPromise;
// 保存文件到指定路径
const savePath = join(config.downloadPath, filename || download.suggestedFilename());
await download.saveAs(savePath);
return JSON.stringify({
success: true,
message: '文件已下载',
path: savePath,
filename: filename || download.suggestedFilename()
});
}
下载机制:
- 事件监听:使用waitForEvent监听下载开始
- 路径管理:自动使用配置的下载目录
- 文件名处理:支持自定义文件名或使用建议文件名
6. 脚本执行与安全
async function executeScript(script: string, args: any[] = []): Promise<string> {
const page = ensurePage();
const result = await page.evaluate(script, ...args);
const response = JSON.stringify({ success: true, result }, null, 2);
return truncateResponse(response); // 结果也进行截断
}
安全特性:
- 沙箱环境:在浏览器上下文中执行,与主进程隔离
- 参数传递:支持向脚本传递参数
- 结果截断:防止大结果导致问题
错误处理体系
1. 错误分类
catch (error: any) {
return JSON.stringify({
success: false,
error: error.message,
error_code: error.name === 'TimeoutError' ? 'timeout' : 'general'
});
}
2. 状态验证
function ensurePage(): Page {
if (!pageInstance) {
throw new Error('浏览器未启动,请先调用launch_browser或connect_browser');
}
return pageInstance;
}
设计模式总结
1. 工厂模式
- 根据配置选择不同的浏览器启动方式
- 统一创建和管理浏览器实例
2. 命令模式
- 每个操作都是独立的命令函数
- 通过主执行函数统一调度
3. 单例模式
- 全局唯一的浏览器实例管理
- 确保资源正确释放
4. 策略模式
- 不同的等待策略(load/domcontentloaded/networkidle)
- 不同的错误处理策略
性能优化
1. 懒加载
- 浏览器实例按需创建
- 页面实例延迟初始化
2. 资源复用
- 浏览器上下文复用
- Cookie和状态保持
3. 内存管理
- 大内容自动截断
- 及时释放无用资源
扩展性设计
1. 操作扩展
// 新增操作只需:
// 1. 在enum中添加操作名
// 2. 实现对应的函数
// 3. 在主switch中添加case
2. 配置扩展
- 通过环境变量添加新配置
- 运行时参数覆盖默认配置
3. 功能扩展
- 可轻松添加新的浏览器操作
- 支持自定义脚本注入
总结
这个浏览器自动化工具的核心优势在于:
- 健壮性:完善的错误处理和状态管理
- 灵活性:支持多种启动方式和配置选项
- 安全性:内容截断和沙箱执行保护
- 易用性:清晰的API设计和友好的错误提示
- 扩展性:模块化设计便于功能扩展
通过合理的架构设计和细致的功能实现,该工具能够满足大多数Web自动化需求,同时保持代码的清晰和可维护性。
由Alice生成
此方悬停