面试 · 2026年1月4日 0

Puppeteer 与大模型深度融合:浏览器自动化的智能进化

前言

在 AI 快速发展的背景下,将大语言模型(LLM)与浏览器自动化工具结合,正成为一种趋势。Puppeteer 作为领先的浏览器自动化框架,与大模型的结合让智能 Agent 能够理解和执行复杂的网页操作任务。本文将深入探讨 Puppeteer 的技术细节,以及大模型如何理解、调用和处理 Puppeteer 参数的核心机制。

第一部分:Puppeteer 框架深度解析

1.1 Puppeteer 核心架构

Puppeteer 是 Google Chrome 团队开发的 Node.js 库,通过 DevTools Protocol(CDP)与 Chrome/Chromium 通信。核心架构包括:

┌─────────────────┐
│   Node.js 应用  │
└────────┬────────┘
         │ DevTools Protocol (WebSocket)
         ↓
┌─────────────────┐
│  Puppeteer API  │
│  (高级抽象层)    │
└────────┬────────┘
         │
         ↓
┌─────────────────┐
│ Chrome/Chromium │
│  浏览器实例      │
└─────────────────┘

关键组件:

  • Browser:浏览器实例
  • BrowserContext:隔离的上下文(类似无痕窗口)
  • Page:页面(标签页)
  • Frame:页面中的框架
  • ElementHandle:DOM 元素句柄
  • JSHandle:JavaScript 对象句柄

1.2 核心 API 详解

Browser 实例管理

const puppeteer = require('puppeteer');

// 启动浏览器
const browser = await puppeteer.launch({
  headless: true,           // 无头模式
  executablePath: '/path/to/chrome', // 自定义 Chrome 路径
  args: [
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--disable-dev-shm-usage',
    '--disable-accelerated-2d-canvas',
    '--disable-gpu'
  ],
  defaultViewport: {
    width: 1920,
    height: 1080
  },
  timeout: 30000
});

// 创建新的上下文(隔离环境)
const context = await browser.createBrowserContext({
  viewport: { width: 1280, height: 720 },
  userAgent: 'Mozilla/5.0...'
});

// 在新上下文中创建页面
const page = await context.newPage();

Page 导航与等待

// 基础导航
await page.goto('https://example.com', {
  waitUntil: 'networkidle2',  // 等待网络空闲
  timeout: 60000
});

// 等待策略
await page.goto(url, {
  waitUntil: [
    'load',              // 等待 load 事件
    'domcontentloaded',  // DOM 加载完成
    'networkidle0',      // 网络完全空闲(0个连接)
    'networkidle2'       // 网络基本空闲(≤2个连接)
  ]
});

// 等待元素出现
await page.waitForSelector('.content', {
  visible: true,         // 元素可见
  hidden: false,         // 元素不隐藏
  timeout: 5000
});

// 等待函数返回 true
await page.waitForFunction(
  () => document.readyState === 'complete',
  { timeout: 30000 }
);

// 等待导航完成
await page.waitForNavigation({
  waitUntil: 'networkidle2'
});

元素查找与交互

// 查找元素(返回 ElementHandle)
const element = await page.$('.search-input');  // 单个元素
const elements = await page.$$('.item');        // 多个元素

// 在元素上执行操作
await page.click('.button');
await page.type('.input', 'text', { delay: 100 });  // 模拟人类输入
await page.focus('.input');
await page.hover('.menu-item');

// 使用 ElementHandle
const button = await page.$('#submit');
await button.click();
await button.hover();
const text = await button.evaluate(el => el.textContent);

// XPath 选择器
const elements = await page.$x('//button[contains(text(), "提交")]');

JavaScript 执行

// 在页面上下文中执行代码(返回序列化结果)
const result = await page.evaluate(() => {
  return document.title;
});

// 传递参数到页面上下文
const data = await page.evaluate((selector, attribute) => {
  const element = document.querySelector(selector);
  return element ? element.getAttribute(attribute) : null;
}, '.link', 'href');

// 返回非序列化对象(JSHandle)
const handle = await page.evaluateHandle(() => {
  return window.myObject;
});

// 在元素上下文中执行
const element = await page.$('.container');
const text = await element.evaluate(el => el.innerText);

// 执行并等待 Promise
await page.evaluate(async () => {
  await new Promise(resolve => setTimeout(resolve, 1000));
  // 执行异步操作
});

网络拦截与监控

// 拦截请求
await page.setRequestInterception(true);
page.on('request', request => {
  // 修改请求
  if (request.url().includes('api.example.com')) {
    request.continue({
      headers: {
        ...request.headers(),
        'X-Custom-Header': 'value'
      }
    });
  } else {
    request.continue();
  }
});

// 拦截响应
page.on('response', response => {
  if (response.url().includes('api.example.com')) {
    response.json().then(data => {
      console.log('API Response:', data);
    });
  }
});

// 阻止资源加载(提升性能)
await page.setRequestInterception(true);
page.on('request', request => {
  const resourceType = request.resourceType();
  if (['image', 'stylesheet', 'font'].includes(resourceType)) {
    request.abort();
  } else {
    request.continue();
  }
});

截图与 PDF

// 全页面截图
await page.screenshot({
  path: 'screenshot.png',
  fullPage: true,
  type: 'png',
  quality: 90  // 仅对 jpeg 有效
});

// 元素截图
const element = await page.$('.widget');
await element.screenshot({ path: 'widget.png' });

// PDF 生成
await page.pdf({
  path: 'document.pdf',
  format: 'A4',
  printBackground: true,
  margin: {
    top: '20px',
    right: '20px',
    bottom: '20px',
    left: '20px'
  }
});

1.3 高级特性

并行页面管理

// 创建多个页面并行处理
const pages = await Promise.all([
  browser.newPage(),
  browser.newPage(),
  browser.newPage()
]);

const results = await Promise.all(
  pages.map(async (page, index) => {
    await page.goto(urls[index]);
    return await page.title();
  })
);

// 清理
await Promise.all(pages.map(page => page.close()));

性能监控

// 启用性能跟踪
await page.tracing.start({ 
  path: 'trace.json',
  screenshots: true 
});

// 执行操作
await page.goto('https://example.com');

// 停止跟踪
await page.tracing.stop();

// 获取性能指标
const metrics = await page.metrics();
console.log(metrics);
// {
//   Timestamp: 1234567890,
//   Documents: 10,
//   Frames: 5,
//   JSEventListeners: 100,
//   Nodes: 500,
//   LayoutCount: 20,
//   RecalcStyleCount: 15,
//   ...
// }

Cookie 和 LocalStorage 管理

// 设置 Cookie
await page.setCookie({
  name: 'session',
  value: 'abc123',
  domain: 'example.com',
  path: '/',
  expires: Date.now() / 1000 + 3600  // 1小时后过期
});

// 获取 Cookie
const cookies = await page.cookies();
console.log(cookies);

// 清除 Cookie
await page.deleteCookie(...cookies);

// 操作 LocalStorage
await page.evaluate(() => {
  localStorage.setItem('key', 'value');
  const value = localStorage.getItem('key');
});

第二部分:大模型与 Puppeteer 的联合应用

2.1 应用场景架构

大模型与 Puppeteer 的联合应用通常采用以下架构:

┌─────────────────────────────────────────┐
│         用户自然语言指令                  │
│    "帮我在京东搜索iPhone 15并查看价格"    │
└──────────────┬──────────────────────────┘
               │
               ↓
┌─────────────────────────────────────────┐
│        大语言模型 (LLM)                  │
│  - 理解用户意图                          │
│  - 生成操作步骤                          │
│  - 提取关键参数                          │
└──────────────┬──────────────────────────┘
               │
               ↓ JSON Schema / Function Calling
┌─────────────────────────────────────────┐
│      Puppeteer 操作序列生成器             │
│  - 将步骤转换为 Puppeteer API 调用        │
│  - 参数验证与转换                        │
└──────────────┬──────────────────────────┘
               │
               ↓
┌─────────────────────────────────────────┐
│        Puppeteer 执行引擎                │
│  - 执行浏览器操作                        │
│  - 捕获执行结果                          │
└──────────────┬──────────────────────────┘
               │
               ↓
┌─────────────────────────────────────────┐
│         结果处理与反馈                   │
│  - 提取页面内容                          │
│  - 大模型分析结果                        │
│  - 生成自然语言回复                      │
└─────────────────────────────────────────┘

2.2 Function Calling 模式

大模型通过 Function Calling 调用 Puppeteer。定义一组工具函数,大模型决定何时调用:

定义工具函数 Schema

const puppeteerTools = [
  {
    type: "function",
    function: {
      name: "navigate_to_page",
      description: "导航到指定网页",
      parameters: {
        type: "object",
        properties: {
          url: {
            type: "string",
            description: "目标网页的完整URL"
          },
          wait_until: {
            type: "string",
            enum: ["load", "domcontentloaded", "networkidle0", "networkidle2"],
            description: "等待策略,networkidle2表示等待网络基本空闲"
          }
        },
        required: ["url"]
      }
    }
  },
  {
    type: "function",
    function: {
      name: "click_element",
      description: "点击页面上的元素",
      parameters: {
        type: "object",
        properties: {
          selector: {
            type: "string",
            description: "CSS选择器或XPath,用于定位元素"
          },
          selector_type: {
            type: "string",
            enum: ["css", "xpath"],
            description: "选择器类型,css或xpath"
          },
          wait_for_visible: {
            type: "boolean",
            description: "是否等待元素可见后再点击"
          }
        },
        required: ["selector", "selector_type"]
      }
    }
  },
  {
    type: "function",
    function: {
      name: "input_text",
      description: "在输入框中输入文本",
      parameters: {
        type: "object",
        properties: {
          selector: {
            type: "string",
            description: "输入框的CSS选择器"
          },
          text: {
            type: "string",
            description: "要输入的文本内容"
          },
          clear_first: {
            type: "boolean",
            description: "输入前是否清空原有内容"
          },
          delay: {
            type: "number",
            description: "每个字符之间的延迟毫秒数,模拟人类输入"
          }
        },
        required: ["selector", "text"]
      }
    }
  },
  {
    type: "function",
    function: {
      name: "extract_content",
      description: "从页面提取内容",
      parameters: {
        type: "object",
        properties: {
          selector: {
            type: "string",
            description: "要提取内容的元素选择器"
          },
          extraction_type: {
            type: "string",
            enum: ["text", "html", "attribute", "all_links", "table_data"],
            description: "提取类型:text-文本内容,html-HTML代码,attribute-属性值,all_links-所有链接,table_data-表格数据"
          },
          attribute_name: {
            type: "string",
            description: "当extraction_type为attribute时,指定属性名(如href、src等)"
          }
        },
        required: ["selector", "extraction_type"]
      }
    }
  },
  {
    type: "function",
    function: {
      name: "wait_for_element",
      description: "等待页面元素出现或消失",
      parameters: {
        type: "object",
        properties: {
          selector: {
            type: "string",
            description: "要等待的元素选择器"
          },
          state: {
            type: "string",
            enum: ["visible", "hidden", "attached"],
            description: "等待状态:visible-元素可见,hidden-元素隐藏,attached-元素添加到DOM"
          },
          timeout: {
            type: "number",
            description: "超时时间(毫秒),默认5000"
          }
        },
        required: ["selector", "state"]
      }
    }
  },
  {
    type: "function",
    function: {
      name: "execute_javascript",
      description: "在页面上下文中执行JavaScript代码",
      parameters: {
        type: "object",
        properties: {
          code: {
            type: "string",
            description: "要执行的JavaScript代码"
          },
          return_result: {
            type: "boolean",
            description: "是否返回执行结果"
          }
        },
        required: ["code"]
      }
    }
  },
  {
    type: "function",
    function: {
      name: "take_screenshot",
      description: "截取页面或元素的截图",
      parameters: {
        type: "object",
        properties: {
          selector: {
            type: "string",
            description: "要截图的元素选择器,为空则截取整个页面"
          },
          full_page: {
            type: "boolean",
            description: "是否截取整个页面(仅当selector为空时有效)"
          }
        },
        required: []
      }
    }
  }
];

实现工具函数

class PuppeteerAgent {
  constructor() {
    this.browser = null;
    this.page = null;
  }

  async init() {
    this.browser = await puppeteer.launch({
      headless: true,
      args: ['--no-sandbox', '--disable-setuid-sandbox']
    });
    this.page = await this.browser.newPage();
  }

  async navigate_to_page(url, wait_until = 'networkidle2') {
    try {
      await this.page.goto(url, { waitUntil: wait_until, timeout: 60000 });
      return {
        success: true,
        message: `成功导航到 ${url}`,
        title: await this.page.title()
      };
    } catch (error) {
      return {
        success: false,
        message: `导航失败: ${error.message}`
      };
    }
  }

  async click_element(selector, selector_type = 'css', wait_for_visible = true) {
    try {
      if (selector_type === 'xpath') {
        const elements = await this.page.$x(selector);
        if (elements.length === 0) {
          return { success: false, message: `未找到元素: ${selector}` };
        }
        if (wait_for_visible) {
          await this.page.waitForXPath(selector, { visible: true });
        }
        await elements[0].click();
      } else {
        if (wait_for_visible) {
          await this.page.waitForSelector(selector, { visible: true });
        }
        await this.page.click(selector);
      }
      return {
        success: true,
        message: `成功点击元素: ${selector}`
      };
    } catch (error) {
      return {
        success: false,
        message: `点击失败: ${error.message}`
      };
    }
  }

  async input_text(selector, text, clear_first = true, delay = 50) {
    try {
      await this.page.waitForSelector(selector, { visible: true });
      if (clear_first) {
        await this.page.click(selector, { clickCount: 3 }); // 三击选中全部
        await this.page.keyboard.press('Backspace');
      }
      await this.page.type(selector, text, { delay });
      return {
        success: true,
        message: `成功输入文本到 ${selector}`
      };
    } catch (error) {
      return {
        success: false,
        message: `输入失败: ${error.message}`
      };
    }
  }

  async extract_content(selector, extraction_type, attribute_name = null) {
    try {
      let result;

      switch (extraction_type) {
        case 'text':
          result = await this.page.$eval(selector, el => el.textContent.trim());
          break;
        case 'html':
          result = await this.page.$eval(selector, el => el.innerHTML);
          break;
        case 'attribute':
          if (!attribute_name) {
            return { success: false, message: 'attribute类型需要指定attribute_name' };
          }
          result = await this.page.$eval(selector, (el, attr) => el.getAttribute(attr), attribute_name);
          break;
        case 'all_links':
          result = await this.page.$$eval('a', links => 
            links.map(link => ({
              text: link.textContent.trim(),
              href: link.href
            }))
          );
          break;
        case 'table_data':
          result = await this.page.$eval(selector, table => {
            const rows = Array.from(table.querySelectorAll('tr'));
            return rows.map(row => {
              const cells = Array.from(row.querySelectorAll('td, th'));
              return cells.map(cell => cell.textContent.trim());
            });
          });
          break;
        default:
          return { success: false, message: `不支持的提取类型: ${extraction_type}` };
      }

      return {
        success: true,
        data: result,
        message: `成功提取内容`
      };
    } catch (error) {
      return {
        success: false,
        message: `提取失败: ${error.message}`
      };
    }
  }

  async wait_for_element(selector, state, timeout = 5000) {
    try {
      const options = { timeout };

      if (state === 'visible') {
        await this.page.waitForSelector(selector, { visible: true, ...options });
      } else if (state === 'hidden') {
        await this.page.waitForSelector(selector, { hidden: true, ...options });
      } else if (state === 'attached') {
        await this.page.waitForSelector(selector, options);
      }

      return {
        success: true,
        message: `元素 ${selector} 已${state === 'visible' ? '可见' : state === 'hidden' ? '隐藏' : '附加'}`
      };
    } catch (error) {
      return {
        success: false,
        message: `等待超时或失败: ${error.message}`
      };
    }
  }

  async execute_javascript(code, return_result = true) {
    try {
      const result = return_result 
        ? await this.page.evaluate(code)
        : await this.page.evaluate(() => { eval(code); });

      return {
        success: true,
        data: return_result ? result : null,
        message: 'JavaScript执行成功'
      };
    } catch (error) {
      return {
        success: false,
        message: `执行失败: ${error.message}`
      };
    }
  }

  async take_screenshot(selector = null, full_page = false) {
    try {
      const timestamp = Date.now();
      const filename = `screenshot_${timestamp}.png`;

      if (selector) {
        const element = await this.page.$(selector);
        if (!element) {
          return { success: false, message: `未找到元素: ${selector}` };
        }
        await element.screenshot({ path: filename });
      } else {
        await this.page.screenshot({ 
          path: filename,
          fullPage: full_page 
        });
      }

      return {
        success: true,
        filename: filename,
        message: '截图成功'
      };
    } catch (error) {
      return {
        success: false,
        message: `截图失败: ${error.message}`
      };
    }
  }

  async close() {
    if (this.browser) {
      await this.browser.close();
    }
  }
}

2.3 大模型调用流程

完整的 Agent 实现

const { OpenAI } = require('openai');

class IntelligentWebAgent {
  constructor(apiKey) {
    this.openai = new OpenAI({ apiKey });
    this.agent = new PuppeteerAgent();
    this.conversationHistory = [];
  }

  async init() {
    await this.agent.init();
  }

  async executeTask(userQuery) {
    // 构建消息历史
    const messages = [
      {
        role: "system",
        content: `你是一个智能网页操作助手。你可以使用提供的工具函数来控制浏览器执行各种操作。

请遵循以下原则:
1. 仔细分析用户的需求,将其分解为多个步骤
2. 每次调用工具函数后,检查返回结果
3. 如果操作失败,尝试替代方案
4. 在提取内容时,使用合适的选择器
5. 对于动态加载的内容,使用wait_for_element等待元素出现
6. 完成任务后,总结执行结果并回复用户`
      },
      ...this.conversationHistory,
      {
        role: "user",
        content: userQuery
      }
    ];

    // 调用大模型,使用 function calling
    const response = await this.openai.chat.completions.create({
      model: "gpt-4",
      messages: messages,
      tools: puppeteerTools,
      tool_choice: "auto",  // 让模型决定是否调用工具
      temperature: 0.7
    });

    const message = response.choices[0].message;

    // 检查是否需要调用工具
    if (message.tool_calls && message.tool_calls.length > 0) {
      const toolResults = [];

      for (const toolCall of message.tool_calls) {
        const functionName = toolCall.function.name;
        const functionArgs = JSON.parse(toolCall.function.arguments);

        console.log(`🔧 调用工具: ${functionName}`, functionArgs);

        // 执行 Puppeteer 操作
        const result = await this.agent[functionName](...Object.values(functionArgs));

        toolResults.push({
          tool_call_id: toolCall.id,
          role: "tool",
          name: functionName,
          content: JSON.stringify(result)
        });
      }

      // 将工具结果发送回大模型
      this.conversationHistory.push(message);
      this.conversationHistory.push(...toolResults);

      // 继续对话,让大模型处理结果
      return await this.executeTask("");  // 递归调用,处理工具结果
    } else {
      // 大模型返回最终回复
      this.conversationHistory.push(message);
      return message.content;
    }
  }

  async close() {
    await this.agent.close();
  }
}

// 使用示例
(async () => {
  const agent = new IntelligentWebAgent(process.env.OPENAI_API_KEY);
  await agent.init();

  const result = await agent.executeTask("帮我在百度搜索'Puppeteer教程',然后告诉我第一页有多少个搜索结果");
  console.log(result);

  await agent.close();
})();

2.4 大模型理解参数的技术细节

参数理解机制

大模型理解 Puppeteer 参数主要依赖:

  1. Schema 描述质量
  • 清晰的参数说明
  • 枚举值定义
  • 类型约束
  • 示例值
  1. 上下文学习
  • 通过示例学习
  • 错误反馈调整
  • 多轮对话优化
  1. 参数验证与转换
class ParameterValidator {
  // 验证和标准化选择器
  static normalizeSelector(selector, selector_type) {
    if (selector_type === 'css') {
      // 验证 CSS 选择器语法
      if (!this.isValidCSSSelector(selector)) {
        throw new Error(`无效的CSS选择器: ${selector}`);
      }
      return selector;
    } else if (selector_type === 'xpath') {
      // XPath 选择器
      if (!selector.startsWith('//') && !selector.startsWith('.//')) {
        // 大模型可能生成不完整的XPath,尝试修复
        if (selector.includes('text()')) {
          return `//*[contains(text(), "${selector.match(/text\(\)='(.+?)'/)?.[1] || ''}")]`;
        }
      }
      return selector;
    }
    return selector;
  }

  // 智能选择器生成(当大模型提供的选择器不准确时)
  static async generateAlternativeSelector(page, description) {
    // 使用大模型生成更准确的选择器
    const prompt = `根据以下描述,生成一个准确的CSS选择器:
描述:${description}
页面HTML结构:
${await page.content()}`;

    // 调用大模型生成选择器
    // ... 实现细节
  }

  // 参数类型转换
  static convertParameter(value, expectedType) {
    switch (expectedType) {
      case 'number':
        const num = parseFloat(value);
        if (isNaN(num)) throw new Error(`无法转换为数字: ${value}`);
        return num;
      case 'boolean':
        if (typeof value === 'string') {
          return value.toLowerCase() === 'true';
        }
        return Boolean(value);
      case 'array':
        return Array.isArray(value) ? value : [value];
      default:
        return value;
    }
  }
}

错误处理与重试机制

class RobustPuppeteerAgent extends PuppeteerAgent {
  async executeWithRetry(operation, maxRetries = 3) {
    let lastError;

    for (let i = 0; i < maxRetries; i++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error;
        console.log(`尝试 ${i + 1}/${maxRetries} 失败: ${error.message}`);

        // 分析错误类型,决定是否重试
        if (this.isRetryableError(error)) {
          await this.delay(1000 * (i + 1)); // 指数退避
          continue;
        } else {
          throw error; // 不可重试的错误直接抛出
        }
      }
    }

    throw lastError;
  }

  isRetryableError(error) {
    const retryablePatterns = [
      /timeout/i,
      /Navigation timeout/i,
      /Target closed/i,
      /Protocol error/i
    ];

    return retryablePatterns.some(pattern => pattern.test(error.message));
  }

  // 智能等待元素(处理动态加载)
  async smartWaitForElement(selector, timeout = 10000) {
    try {
      // 首先尝试标准等待
      await this.page.waitForSelector(selector, { timeout });
      return true;
    } catch (error) {
      // 如果失败,尝试滚动页面(触发懒加载)
      await this.page.evaluate(() => {
        window.scrollTo(0, document.body.scrollHeight);
      });

      // 再次尝试等待
      try {
        await this.page.waitForSelector(selector, { timeout: 5000 });
        return true;
      } catch (e) {
        // 最后尝试:使用更宽松的选择器
        const alternativeSelectors = this.generateAlternativeSelectors(selector);
        for (const altSelector of alternativeSelectors) {
          try {
            await this.page.waitForSelector(altSelector, { timeout: 2000 });
            return true;
          } catch (e2) {
            continue;
          }
        }
        return false;
      }
    }
  }
}

2.5 高级应用:视觉理解结合

结合视觉模型(如 GPT-4 Vision)和 Puppeteer,可以实现更智能的交互:

class VisionEnhancedAgent extends PuppeteerAgent {
  async findElementByDescription(description) {
    // 截取页面截图
    const screenshot = await this.page.screenshot({ encoding: 'base64' });

    // 使用视觉模型识别元素位置
    const visionResponse = await this.openai.chat.completions.create({
      model: "gpt-4-vision-preview",
      messages: [
        {
          role: "user",
          content: [
            {
              type: "text",
              text: `在这个网页截图中,找到以下描述的元素,并返回它的CSS选择器或坐标位置:${description}`
            },
            {
              type: "image_url",
              image_url: {
                url: `data:image/png;base64,${screenshot}`
              }
            }
          ]
        }
      ]
    });

    // 解析视觉模型的回复,提取选择器
    const selector = this.extractSelectorFromResponse(visionResponse.choices[0].message.content);

    return selector;
  }

  async clickByDescription(description) {
    const selector = await this.findElementByDescription(description);
    return await this.click_element(selector, 'css', true);
  }
}

第三部分:实际应用案例

案例1:电商价格监控 Agent

class PriceMonitorAgent extends IntelligentWebAgent {
  async monitorPrice(productUrl, targetPrice) {
    const task = `
    1. 访问商品页面:${productUrl}
    2. 等待页面加载完成
    3. 提取商品价格
    4. 提取商品名称
    5. 比较价格是否低于目标价格 ${targetPrice}
    6. 如果低于目标价格,发送通知
    `;

    return await this.executeTask(task);
  }

  async extractProductInfo() {
    // 使用大模型智能提取结构化数据
    const pageContent = await this.agent.page.content();

    const extractionPrompt = `
    从以下HTML中提取商品信息(JSON格式):
    - 商品名称
    - 当前价格
    - 原价(如果有)
    - 库存状态
    - 商品评分

    HTML内容:
    ${pageContent.substring(0, 5000)}
    `;

    const response = await this.openai.chat.completions.create({
      model: "gpt-4",
      messages: [
        {
          role: "system",
          content: "你是一个数据提取专家,从网页HTML中提取结构化数据,返回JSON格式。"
        },
        {
          role: "user",
          content: extractionPrompt
        }
      ],
      response_format: { type: "json_object" }
    });

    return JSON.parse(response.choices[0].message.content);
  }
}

案例2:自动化测试生成

class TestGeneratorAgent extends IntelligentWebAgent {
  async generateTestScenario(featureDescription) {
    const prompt = `
    基于以下功能描述,生成详细的测试场景和对应的Puppeteer操作步骤:

    功能描述:${featureDescription}

    请生成:
    1. 测试用例列表
    2. 每个测试用例的详细操作步骤
    3. 预期的验证点
    `;

    const testPlan = await this.openai.chat.completions.create({
      model: "gpt-4",
      messages: [{ role: "user", content: prompt }],
      tools: puppeteerTools
    });

    // 执行测试用例
    return await this.executeTestPlan(testPlan);
  }
}

案例3:内容抓取与摘要

class ContentScraperAgent extends IntelligentWebAgent {
  async scrapeAndSummarize(url, summaryLength = 200) {
    await this.agent.navigate_to_page(url);

    // 提取主要内容
    const mainContent = await this.agent.page.evaluate(() => {
      // 移除脚本、样式等
      const scripts = document.querySelectorAll('script, style, nav, footer, header');
      scripts.forEach(el => el.remove());

      // 提取正文内容
      const article = document.querySelector('article, main, .content, .post');
      return article ? article.innerText : document.body.innerText;
    });

    // 使用大模型生成摘要
    const summary = await this.openai.chat.completions.create({
      model: "gpt-4",
      messages: [
        {
          role: "system",
          content: "你是一个内容摘要专家,生成准确、简洁的文章摘要。"
        },
        {
          role: "user",
          content: `请为以下内容生成一个${summaryLength}字左右的摘要:\n\n${mainContent.substring(0, 8000)}`
        }
      ]
    });

    return {
      url: url,
      content: mainContent,
      summary: summary.choices[0].message.content
    };
  }
}

第四部分:最佳实践与优化

4.1 性能优化

// 1. 浏览器实例复用
class BrowserPool {
  constructor(poolSize = 3) {
    this.pool = [];
    this.poolSize = poolSize;
  }

  async getBrowser() {
    if (this.pool.length > 0) {
      return this.pool.pop();
    }
    return await puppeteer.launch({ headless: true });
  }

  async releaseBrowser(browser) {
    if (this.pool.length < this.poolSize) {
      this.pool.push(browser);
    } else {
      await browser.close();
    }
  }
}

// 2. 请求拦截优化(禁用不必要的资源)
async function optimizePage(page) {
  await page.setRequestInterception(true);
  page.on('request', (req) => {
    const resourceType = req.resourceType();
    if (['image', 'stylesheet', 'font', 'media'].includes(resourceType)) {
      req.abort();
    } else {
      req.continue();
    }
  });
}

// 3. 并行处理
async function parallelScraping(urls) {
  const browser = await puppeteer.launch();
  const results = await Promise.all(
    urls.map(async (url) => {
      const page = await browser.newPage();
      await page.goto(url);
      const title = await page.title();
      await page.close();
      return { url, title };
    })
  );
  await browser.close();
  return results;
}

4.2 错误处理策略

class ErrorHandler {
  static async handlePuppeteerError(error, context) {
    const errorInfo = {
      message: error.message,
      stack: error.stack,
      context: context,
      timestamp: new Date().toISOString()
    };

    // 根据错误类型采取不同策略
    if (error.message.includes('Navigation timeout')) {
      // 导航超时:尝试刷新或使用更宽松的等待策略
      return {
        action: 'retry',
        strategy: 'use_networkidle0',
        errorInfo
      };
    } else if (error.message.includes('Target closed')) {
      // 目标关闭:重新创建页面
      return {
        action: 'recreate_page',
        errorInfo
      };
    } else if (error.message.includes('No node found')) {
      // 元素未找到:尝试替代选择器
      return {
        action: 'try_alternative_selector',
        errorInfo
      };
    }

    return {
      action: 'log_and_continue',
      errorInfo
    };
  }
}

4.3 安全考虑

// 1. 沙箱隔离
const browser = await puppeteer.launch({
  args: [
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--disable-dev-shm-usage',
    '--disable-accelerated-2d-canvas',
    '--disable-gpu',
    '--disable-features=IsolateOrigins,site-per-process'
  ]
});

// 2. 内容安全策略
await page.setContent(html, {
  waitUntil: 'networkidle0'
});

// 3. 限制执行时间
async function executeWithTimeout(fn, timeout = 30000) {
  return Promise.race([
    fn(),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('操作超时')), timeout)
    )
  ]);
}

总结

Puppeteer 与大模型的结合为浏览器自动化带来新的可能性。通过 Function Calling,大模型能够理解和执行复杂的网页操作,而 Puppeteer 提供稳定的执行环境。

关键技术点:

  1. 清晰的工具函数 Schema 定义
  2. 参数验证与智能转换
  3. 错误处理与重试机制
  4. 视觉理解增强交互能力
  5. 性能优化与资源管理

未来方向:

  • 多模态理解(文本+视觉)
  • 更强的上下文记忆
  • 自主错误恢复
  • 更自然的对话交互

这种结合将推动智能 Agent 在自动化领域的应用。