Playwright浏览器自动化工具核心实现解析

由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. 功能扩展

  • 可轻松添加新的浏览器操作
  • 支持自定义脚本注入

总结

这个浏览器自动化工具的核心优势在于:

  1. 健壮性:完善的错误处理和状态管理
  2. 灵活性:支持多种启动方式和配置选项
  3. 安全性:内容截断和沙箱执行保护
  4. 易用性:清晰的API设计和友好的错误提示
  5. 扩展性:模块化设计便于功能扩展

通过合理的架构设计和细致的功能实现,该工具能够满足大多数Web自动化需求,同时保持代码的清晰和可维护性。

由Alice生成


此方悬停
相册 小说 Ai