无头浏览器 Puppeteer

参考:

juejin.cn/post/684490...

js-banana.github.io/blog/pages/...

www.w3cschool.cn/puppeteer/

puppeteer.bootcss.com/

介绍

无头浏览器:使用脚本执行的浏览器。

Google 提供的无头浏览器(headless Chrome):

利用Puppeteer可以获取页面DOM节点、网络请求和响应、程序化操作页面行为、进行页面的性能监控和优化、获取页面截图和PDF,可以操作Chrome浏览器等;

一个 Node 库,它提供了高级的 API 并通过 DevTools 协议来控制 Chrome。

  1. Browser:这是一个浏览器实例,可以拥有浏览器上下文,可通过 puppeteer.launchpuppeteer.connect 创建一个 Browser 对象。
  2. BrowserContext:该实例定义了一个浏览器上下文,可拥有多个页面,创建浏览器实例时默认会创建一个浏览器上下文(不能关闭),此外可以利用 **browser.createIncognitoBrowserContext()**创建一个匿名的浏览器上下文(不会与其它浏览器上下文共享cookie/cache).
  3. Page:至少包含一个主框架,除了主框架外还有可能存在其它框架,例如iframe。
  4. Frame:页面中的框架,在每个时间点,页面通过**page.mainFrame()和frame.childFrames()**方法暴露当前框架的细节。对于该框架中至少有一个执行上下文
  5. ExecutionCOntext:表示一个JavaScript的执行上下文。
  6. Worker:具有单个执行上下文,便于与 WebWorkers 交互。

可以做什么

  • 生成网页截图或者 PDF
  • 抓取 SPA(单页应用)并生成预渲染内容(即"SSR"(服务器端渲染))
  • 做表单的自动提交、UI的自动化测试、模拟键盘输入等
  • 用浏览器自带的一些调试工具和性能分析工具帮助我们分析问题
  • 在最新的无头浏览器环境里做测试、使用最新浏览器特性
  • 测试浏览器扩展

使用

文档:puppeteer.bootcss.com/#%E4%BD%BF%...

表单提交

javascript 复制代码
/**
 * 在无头浏览器自动填写表单并提交
 */
const puppeteer = require('puppeteer');

const autoSubmitForm = async (url) => {
  //启动浏览器实例&非无头模式
  const browser = await puppeteer.launch({
    headless: false,
  });

  // 创建了一个新的页面,并设置了视窗大小
  const page = await browser.newPage();
  page.setViewport({
    width: 1468,
    height: 768,
  });

  // 方法导航到指定的 URL,并在页面加载完成时算是完成
  await page.goto(url, {
    waitUntil: 'networkidle2',
  });

  // 等待页面中具有 .RNNXgb 类名的元素出现,确保该元素已经加载完毕。(指的是搜索输入框的选择器)
  await page.waitForSelector('.RNNXgb');

  // 将焦点设置到具有.RNNXgb 类名的输入框。
  await page.focus('.RNNXgb');
  // 向输入框中输入 '麻辣香锅',并通过设置 { delay: 100 } 控制输入每个字母的间隔。
  await page.type('.RNNXgb', '麻辣香锅', { delay: 100 });

  // 模拟按下键盘上的 Enter 键
  await page.keyboard.press('Enter');

  // 不关闭浏览器,看看效果
  // await browser.close();
};

module.exports = autoSubmitForm;

// 检查脚本是否被直接执行,而不是通过导入模块的方式调用。
if (require.main === module) {
  autoSubmitForm('http://google.com');
}

爬虫

传统的爬虫是基于 HTTP 协议,模拟 UserAgent 发送 http 请求,获取到 html 内容后使用正则解析出需要抓取的内容,这种方式面对服务端渲染直出 html 的网页时非常便捷。

但遇到单页应用(SPA)时,或遇到登录校验时,这种爬虫就显得比较无力。

而使用无头浏览器,抓取网页时完全使用了人机交互时的操作,所以页面的初始化完全能使用宿主浏览器环境渲染完备,不再需要关心这个单页应用在前端初始化时需要涉及哪些 HTTP 请求。

无头浏览器提供的各种点击、输入等指令,完全模拟人的点击、输入等指令,也就再也不用担心正则写不出来了

SPA(单页应用程序)具有动态加载内容和路由的特性。传统爬虫在处理SPA时可能遇到一些挑战:

  • 动态加载:

SPA通常使用JavaScript动态加载内容,这意味着页面内容可能不是一次性加载的,而是在用户与页面进行交互时动态生成。

  • 路由跳转:

SPA使用客户端路由管理页面状态。在URL更改时,页面内容可能会动态变化,但浏览器不会实际刷新页面。

  • 页面状态:

传统爬虫在爬取网页时通常只能获取到初始加载的HTML内容,而无法获取后续由JavaScript生成的内容,导致缺乏页面状态的完整性。

为了解决这些问题,针对SPA的爬虫需要模拟用户与页面交互的过程,并等待内容加载完成后再进行抓取。这意味着需要等待异步加载的内容,并在页面内容改变时及时更新抓取的数据。

对于SPA,可能需要使用无头浏览器(如Puppeteer)来模拟用户操作并抓取动态生成的内容。另外,也可以通过监视路由变化、等待特定元素加载完成等方法来优化爬取过程。

javascript 复制代码
// 引入process和puppeteer模块
const process = require("process");
const puppeteer = require("puppeteer");

// 定义代理地址、延时时间和延时函数
const PROXY = "http://www.16yun.cn:80"; // 代理服务器(产品 www.16yun.cn)可以修改这个代理地址
const SLEEP = 5000; // 你可以修改这个延时时间(毫秒)
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// 定义一个异步函数view,接受一个URL和一个代理地址作为参数
async function view(url, proxy) {
  // 在view函数中,使用puppeteer.launch方法启动一个浏览器实例,并设置一些参数,如headless、ignoreHTTPSErrors、defaultViewport和args
  const browser = await puppeteer.launch({
    headless: false, // 设置为无头模式(不显示浏览器窗口)
    ignoreHTTPSErrors: true, // 忽略HTTPS错误
    defaultViewport: { width: 1280, height: 800 }, // 设置默认视口大小
    args: [`--proxy-server=${proxy}`], // 设置代理服务器地址
  });

  // 使用browser.newPage方法创建一个新的页面实例,并设置视口大小
  const page = await browser.newPage();
  await page.setViewport({ width: 1280, height: 800 });

  // 使用page.on方法监听request事件,如果请求的资源类型是media,并且请求的URL以https://video.twimg.com/开头,则打印出请求的URL
  page.on("request", (request) => {
    if (
      request.resourceType() === "media" &&
      request.url().startsWith("https://weibo.com//")
    ) {
      console.log(request.url());
    }
  });

  // 使用page.goto方法访问传入的URL
  await page.goto(url);

  // 使用page.click方法点击页面上选择器为.r-1ndi9ce > div:nth-child(1) > div:nth-child(1) > span:nth-child(1) > span:nth-child(1) 的元素
  await page.click(
    ".r-1ndi9ce > div:nth-child(1) > div:nth-child(1) > span:nth-child(1) > span:nth-child(1)"
  );

  // 使用sleep函数等待一段时间(SLEEP)
  await sleep(SLEEP);

  // 使用page.screenshot方法截取页面图片并保存为debug.png文件
  await page.screenshot({ path: "debug.png" });

  // 使用browser.close方法关闭浏览器实例
  await browser.close();
}

// 最后,在主程序中,获取命令行参数中的第一个参数作为URL,并调用view函数。
const url = process.argv[2]; // 获取命令行参数中的第一个参数作为URL

if (url) {
  view(url, PROXY);
} else {
  console.log("请输入一个有效的URL");
}

截图

通过接口 传入URL指定元素转发请求后截图DOM返回图片,缺点十分明显,就是耗时太久。

(HT中的截图服务)

截图服务需要首先写好一个页面,Puppeteer原理是打开一个看不见的网页然后去截图

比如一个活动的页面地址,其中包含一个分享功能,需要分享一个页面海报出去,其中包含了自己的用户数据。

那么,我们可以在当前页面中,写一些参数,这些参数是我们在海报截图中会用到的,如果有这些参数,我们就直接打开海报页面,然后将参数拼接过去在调用截图服务即可,拼接之后的地址和参数如下

然后将这个地址作为截图服务的参数地址传入,这样就实现了海报截图。

🌰

javascript 复制代码
const express = require('express');
const router = express.Router();
const puppeteer = require('puppeteer');

router.post('/', async (req, res) => {
  try {
    const { url, width = 375, height = 667 } = req.body;
    console.log('url', url);
    const browser = await puppeteer.launch({
      headless: true,
    });
    const page = await browser.newPage();
    await page.setViewport({ width, height });
    await page.goto(url);
    await page.waitForTimeout(1000);
    const imgBuffer = await page.screenshot({
      type: 'png',
    });
    const base64Str = imgBuffer.toString('base64');
    res.json({
      code: 200,
      data: `data:image/png;base64,${base64Str}`,
      msg: '海报生成成功',
    });
    browser.close();
  } catch (error) {
    res.status(500).json({
      message: error.message,
      errors: error.stack,
    });
  }
});

使用方式

用户端只需要传生成图片的H5链接作为参数。

javascript 复制代码
axios({
  method: 'POST',
  url: 'http://localhost:3000/screenshot', // 托管海报的页面
  data: {
    url: `https://xxx.com/poster`,
  }
})

🌰

javascript 复制代码
const getScreenshot = (domId: string, day: string | number, ipLocation: string) => {
  const key = md5(userInfo.getUser.userId + "hellotalktalktome")
    .toString()
    .toLocaleLowerCase();
  return new Promise((resolve, reject) => {
    getPosterScreenshot({
      screen_url: `${host}hybrid_h5/activity/generate-screenshots?hidebar=1&htdisableselectedword=1&hidebottombar=1&winningDay=${day}&lang=${userInfo.getUser.appLang}&ipLocation=${ipLocation}&w=750&h=1920&id=${domId}`,
      user_id: Number(userInfo.getUser.userId),
      key
    }).then((res: Record<string, any>) => {
      if (res.code === 0) {
        resolve(res.data.data.url);
      } else {
        reject();
      }
    });
  }).catch((error) => {
    isShare.value = false;
    console.log(error);
  });
};

问题

  • PS:

运行node_modules/puppeteer/install.mjs 文件会尝试下载并设置 Puppeteer 运行所需的特定版本的 Chromium 浏览器。通常情况下,当你安装 Puppeteer 时,它会自动下载所需的 Chromium 版本。但是,有时由于网络问题或其他限制,自动下载可能会失败。在这种情况下,手动运行

install.mjs 可以尝试再次下载并配置 Chromium。

手动安装解决报错:

为了确保新版本的正确安装,检查目录node_modules/puppeteer。如果您发现安装文件的扩展名为.mjs,请相应地使用它。

javascript 复制代码
node node_modules/puppeteer/install.mjs
相关推荐
用户125758524362 分钟前
XYGo Admin ArtTable 表格组件:一行代码搞定加载、刷新与分页
前端
gogoing5 分钟前
Prettier 配置说明
前端·javascript
十有八七6 分钟前
Hermes Agent 自进化实现:从源码到架构的深度拆解
前端·人工智能
渐儿6 分钟前
NestJS 生产级开发教程
前端
前端毕业班7 分钟前
uni-app onShareAppMessage hook 原理分析
前端·javascript
gogoing8 分钟前
React 分包加载优化
前端·react.js
gogoing11 分钟前
Babel 配置与工具
前端·javascript
亲亲小宝宝鸭12 分钟前
重新install,项目就跑不起来了?!
前端·npm
Mike117.25 分钟前
GBase 8a 物化视图依赖和 DDL 风险排查记录
java·服务器·前端
蜡台41 分钟前
Vue3 Hook 与 Store 状态管理:深度解析与选型指南
前端·javascript·vue.js