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()
}
还没有评论,快来抢第一吧