无头浏览器 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
相关推荐
Json____10 分钟前
学法减分交管12123模拟练习小程序源码前端和后端和搭建教程
前端·后端·学习·小程序·uni-app·学法减分·驾考题库
上趣工作室23 分钟前
vue2在el-dialog打开的时候使该el-dialog中的某个输入框获得焦点方法总结
前端·javascript·vue.js
家里有只小肥猫23 分钟前
el-tree 父节点隐藏
前端·javascript·vue.js
fkalis25 分钟前
【海外SRC漏洞挖掘】谷歌语法发现XSS+Waf Bypass
前端·xss
zxg_神说要有光1 小时前
自由职业第二年,我忘记了为什么出发
前端·javascript·程序员
陈随易1 小时前
农村程序员-关于小孩教育的思考
前端·后端·程序员
云深时现月1 小时前
jenkins使用cli发行uni-app到h5
前端·uni-app·jenkins
昨天今天明天好多天2 小时前
【Node.js]
前端·node.js
2401_857610032 小时前
深入探索React合成事件(SyntheticEvent):跨浏览器的事件处理利器
前端·javascript·react.js
雾散声声慢2 小时前
前端开发中怎么把链接转为二维码并展示?
前端