Puppeteer学习笔记

Puppeteer学习笔记

2025年06月07日 阅读:24 字数:1624 阅读时长:4 分钟

使用Puppeteer进行自动化测试、爬取数据时,遇到的一些常见问题和笔记。

1. 简介

Puppeteer 是一个由谷歌 Chrome 开发团队发布的 Node.js 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。

1.1. 主要功能

  • 生成截图和PDF:可以生成页面的屏幕截图和 PDF。
  • 抓取和预渲染:能够抓取单页应用(SPA)并生成预渲染内容,即服务器端渲染(SSR)。
  • 自动化测试:可以自动提交表单、进行 UI 测试、模拟键盘输入等,创建自动化测试环境。
  • 性能分析:捕获站点的时间线跟踪,帮助诊断性能问题。
  • 测试浏览器扩展:支持测试 Chrome 扩展程序。

1.2. 使用场景

  • 前端自动化测试:帮助前端工程师快速测试和优化前端代码。
  • 爬虫:用于网络爬虫,获取网页数据。
  • 页面渲染:用于服务器端渲染,提高页面加载速度

2. 示例笔记

import puppeteer, { Browser, Frame, Page } from 'puppeteer'

// 启动浏览器
const browser = await puppeteer.launch({
  headless: true,
  // userDataDir: '../storage',  // 是否需要存储数据
  args: ['--no-sandbox']
})

// 新建页面
const page = await browser.newPage()

// 跳转到新页面
await page.goto('https://www.timelessq.com')

2.1. 查找元素

支持CSS选择器和x-path

  • 定位器:locator

定位器会确保元素位于视口中,等待元素变为 visible 或隐藏,等待元素启用。

const usernameInput = await page.locator(`input[type='text']`)
const passwordInput = page.locator(`input[type='password']`)
// 如果要返回ElementHandles可以用waitHandle
const $username = await usernameInput.waitHandle()

// 过滤器:仅当按钮元素的 innerText 为 '我的按钮' 时,才会单击该元素
await page
  .locator('button')
  .filter(button => button.innerText === 'My button')
  .click();
  • waitForSelector

与定位器相比,waitForSelector 是一种更底层的 API,允许等​​待元素在 DOM 中可用。如果操作失败,它不会自动重试该操作,并且需要手动处理生成的 ElementHandle 以防止内存泄漏。

const $login = await page.waitForSelector(`::-p-xpath(//span[text()='登录'])`, {
  timeout: 3000,
})

// 查询.
const element = await page.waitForSelector('div > .class-name');
// 操作
await element.click(); // Just an example.
// 销毁
await element.dispose();
  • 无需等待即可查询
const $container = (await page.$$(`div[class='layout'] > div`)).at(-1)

// 返回与选择器匹配的单个元素。
page.$() 

// 返回与选择器匹配的所有元素。
page.$$() 

// 返回对与选择器匹配的第一个元素运行 JavaScript 函数的结果。
page.$eval() 

// 返回对与选择器匹配的每个元素运行 JavaScript 函数的结果。
page.$$eval() 

2.2. 操作

// 输入用户名
await $username.click()
await $username.type(email, { delay: 100 })

// 输入密码
await $pwd.click()
await $pwd.type(password, { delay: 100 })

// 获取文本内容
const text = await option.evaluate((el) => el.innerText)

2.3. 截图

const $bgImg = await page.waitForSelector("img[id='captcha_verify_image']")
const backgroundImg = await $bgImg.screenshot({ encoding: 'base64' })

2.4. 获取请求响应

const response = await page.waitForResponse(
  (response) => response.url().includes('https://api.timelessq.com/time') && response.status() === 200,
)
const result = await response.json()

2.5. 拖动验证码

截图后可以借助云码平台获取验证码识别结果,比如拖动验证码获取偏移量后就可以参考下边的方法进行拖动验证。

import { BoundingBox, Page } from 'puppeteer'


// 缓动函数(easeOutQuad)
function easeOutQuad(t: number): number {
  return t * (2 - t)
}

// 生成人类化轨迹
function generateHumanTrajectory(distance: number, steps: number = 30): { x: number; y: number }[] {
  const trajectory: { x: number; y: number }[] = []
  let current = 0
  let prevPosition = 0

  for (let i = 0; i < steps; i++) {
    const t = i / (steps - 1)
    const eased = easeOutQuad(t)
    const newPosition = eased * distance

    // 添加随机扰动(幅度逐渐减小)
    const randomFactor = 1 - t // 越到后期扰动越小
    const deltaX = newPosition - prevPosition
    const deltaY = (Math.random() * 4 - 2) * randomFactor // Y轴扰动

    trajectory.push({
      x: deltaX,
      y: deltaY,
    })

    prevPosition = newPosition
    current = newPosition
  }

  // 精度补偿确保最终到达目标
  const totalX = trajectory.reduce((sum, step) => sum + step.x, 0)
  const diff = distance - totalX
  if (Math.abs(diff) > 0.001) {
    trajectory[steps - 1].x += diff
  }

  return trajectory
}

// 执行人类化拖动
function humanDrag(page: Page, sliderBox: BoundingBox, distance: number): Promise<void> {
  const startPoint = {
    x: sliderBox.x + sliderBox.width / 2,
    y: sliderBox.y + sliderBox.height / 2,
  }

  // 加入初始随机偏移
  await page.mouse.move(startPoint.x + Math.random() * 3 - 1.5, startPoint.y + Math.random() * 3 - 1.5, { steps: 5 })
  await page.mouse.down()

  const trajectory = generateHumanTrajectory(distance)
  let currentPosition = { ...startPoint }

  for (const [index, step] of trajectory.entries()) {
    currentPosition.x += step.x
    currentPosition.y += step.y

    // 添加非匀速移动效果
    const moveSteps = index < trajectory.length / 2 ? 5 : 10
    await page.mouse.move(currentPosition.x + Math.random() * 2 - 1, currentPosition.y + Math.random() * 2 - 1, {
      steps: moveSteps,
    })

    // 动态间隔时间(前期快后期慢)
    const baseDelay = index < trajectory.length / 2 ? 20 : 40
    await delay(baseDelay + Math.random() * 30)

    // 随机添加停顿
    if (Math.random() < 0.2) {
      await delay(50 + Math.random() * 100)
    }
  }

  // 最终微调(模拟人类修正行为)
  for (let i = 0; i < 2; i++) {
    await page.mouse.move(currentPosition.x + (Math.random() * 2 - 1), currentPosition.y + (Math.random() * 2 - 1), {
      steps: 3,
    })
    await delay(50)
  }

  await page.mouse.up()
}

3. 常见问题

3.1. Linux安装缺少依赖

安装完puppeteer后,到/root/.cache/puppeteer/chrome/linux-xxxxx/chrome-linux64/目录下,执行 ldd chrome | grep not 以检查缺少哪些依赖。

比如,我的服务器就输出:

libglib-2.0.so.0 => not found
libgobject-2.0.so.0 => not found
libgio-2.0.so.0 => not found
libatk-1.0.so.0 => not found
libatk-bridge-2.0.so.0 => not found
libcups.so.2 => not found
libxkbcommon.so.0 => not found
libatspi.so.0 => not found
libXcomposite.so.1 => not found
libXdamage.so.1 => not found
libXfixes.so.3 => not found
libXrandr.so.2 => not found
libgbm.so.1 => not found
libpango-1.0.so.0 => not found
libcairo.so.2 => not found
libasound.so.2 => not found

然后把这个丢给ai分析,或者搜索看缺少哪些依赖包,然后使用apt安装依赖

apt install libglib2.0-0 libatk1.0-0 libatk-bridge2.0-0 libcups2 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2

3.2. Sandbox

使用root运行,sandbox报错。

如果信任抓取的网站,可以用--no-sandbox参数运行

const browser = await puppeteer.launch({
  headless: true,
  args: [
    '--no-sandbox'
  ]
})

或者创建一个非root账户运行

3.3. Docker部署

因为环境依赖问题,使用docker部署还是不小的问题。

可以基于一个linux的底包,然后用3.1的方法找到缺少的依赖,再到dockerfile里面写安装依赖的明了。但是这样太麻烦了,有没有一个底包已经解决依赖问题的呢,有的,兄弟有的,可以基于官方的镜像(ghcr.io/puppeteer/puppeteer)来搭建。

puppeteer/docker at main · puppeteer/puppeteer

Dockerfile

# 这里要锁下版本号,因为要配置chrome的路径,版本号不同路径不一样
FROM ghcr.io/puppeteer/puppeteer@sha256:749562044ade4f1410f49506cefbfc37ad4a9eb5133a98e5f238285a10e25771

# 底包是puppeteer用户
# USER root

# 跳过 Puppeteer 自动下载 Chromium
ENV PUPPETEER_SKIP_DOWNLOAD=true

# 设置工作目录
WORKDIR /app

# 复制项目文件
COPY . /app

# 使用国内的 NPM 镜像安装依赖
RUN npm install --registry https://registry.npmmirror.com/

# 暴露端口
EXPOSE 3000

# 设置启动命令
CMD ["node", "app.js"]

app.js

import puppeteer, { Page } from 'puppeteer'

async function main() {
  const browser = await puppeteer.launch({
    headless: true,
    executablePath: '/home/pptruser/.cache/puppeteer/chrome/linux-137.0.7151.70/chrome-linux64/chrome',
  })

  const page = await browser.newPage()
}

 

4. 参考文档

推荐阅读

恰饭区

评论区 (0)

0/500

还没有评论,快来抢第一吧