【项目经验】详解Puppeteer入门及案例

news2024/12/25 12:32:44

文章目录

  • 一.项目需求及Puppeteer是什么?
  • 二.Puppeteer注意事项及常用的方法
    • 1.注意事项
    • 2.常用的方法
      • *puppeteer.launch()*
      • *browser.newPage()*
      • *page.goto()*
      • *page.on('request',()=> {})*
      • *page.evaluate()*
      • *page.evaluate()*
      • *browser.close()*
  • 三.案例代码及注释及运行结果
    • index.js(代码)
    • 运行结果

一.项目需求及Puppeteer是什么?

前端爬取别的网页并下载网页上的内容到本地。上网查到可以使用node.js的Puppeteer库来实现。下面我根据我的理解来**说一下puppeteer是什么?**参考puppeteer中文文档。
puppeteer中文意思是被操纵的木偶,从字面意思理解操纵木偶应该很简单。官方文档给的定义是:puppeteer是node的一个库(方法),Puppeteer 默认以 headless 模式运行(其实就是前端通过headless模式来运行谷歌浏览器),headless谷歌浏览器有说明,感兴趣的可以去看。也可以通过配置进行‘有头’模式去运行浏览器。

二.Puppeteer注意事项及常用的方法

1.注意事项

Puppeteer 至少需要 Node v6.4.0及以上的版本,这个在使用中需要注意。打开官方文档,发现一堆async/await,这是es7的语法。
我这个案例是下载图片到本地的案例,刚开始创建项目是需要npm init,否则项目运行不起来,还有一个注意点事在package.json里面配置type:“module”
package.json中的配置

2.常用的方法

puppeteer.launch()

创建浏览器实例,括号里面可以配置浏览器,headless:false,就是打开浏览器,反之就是关闭浏览器。

browser.newPage()

创建一个新的页面

page.goto()

加载浏览器页面,参数是要爬取网页的URL。

page.on(‘request’,()=> {})

监听浏览器请求,就是被爬取网页的请求

page.evaluate()

这个方法是为了获取被爬取网页上的内容,比如本案例的图片的url,他的参数是一个回调函数,它还可以在页面上指向一些点击事件等操作,特别注意的是这个方法传参必须是字符串

page.evaluate()

这个方法是用来获取页面元素属性和值。

browser.close()

关闭浏览器

三.案例代码及注释及运行结果

index.js(代码)

import axios from 'axios'
import path from 'path'
import fs from 'fs/promises'
import puppeteer from 'puppeteer' 

const __dirname = path.resolve() // 当前文件所在目录

// 定义下载函数
const download = (url, dir, filename) =>
  new Promise(async (resolve, reject) => {
    try {
      // { responseType: 'arraybuffer' } 不加这个会乱码
      const { data } = await axios.get(url, { responseType: 'arraybuffer' }) // 请求图片数据
      fs.writeFile(path.join(dir, filename), data).then(() => resolve(filename + ' -> 下载成功')) // 写入图片
    } catch ({ code }) {
      reject({ filename, url, code }) // 抛出错误
    }
  })

// 失败重试
Promise.retry = (fn, arg, count = 3) =>
  new Promise(async (resolve, reject) => {
    if (typeof fn !== 'function') reject(new Error('fn must be a function')) // 判断 fn 是否为函数
    if (!Array.isArray(arg)) reject(new Error('arg must be an array')) // 判断 arg 是否为数组
    let index = 0
    while (index < count) {
      try {
        resolve(await fn(...arg))
        return
      } catch (e) {
        index++
        if (index === count - 1) reject(e)
      }
    }
  })

// 图片下载函数
const imgDownloader = async (url, dir, count = 100, cb) => {
  if (cb && typeof cb !== 'function') throw new Error('cb must be a function')
  // 浏览器加载模块
  async function getImgUrl(url) {
    if (!url) throw new Error('url must be a string')
    const options = {
      defaultViewport: {
        width: 1920,
        height: 1080
      },
      headless: true // 不打开浏览器
      // headless: false // 打开浏览器
      // slowMo: 1000 // 慢慢加载
    }
    const browser = await puppeteer.launch(options) // 创建浏览器实例
    const page = await browser.newPage() // 创建一个新的页面

    try {
      await page.goto(url) // 加载页面
      // 定义加载方法
      const loadAll = () =>
        new Promise((resolve, reject) => {
          // 没有数据请求后3秒 resolve() 结束异步等候
          var timer
          var timeout = () => (timer = setTimeout(() => resolve(true), 3000))
          // 监听网页请求
          page.on('request', () => {
            clearTimeout(timer) // 清除定时器
            timeout() // 重新设置定时器
            // 网页下拉 这部分是在浏览器操作的    !!!!!这方法不在node执行
            page.evaluate(cb => {
              const height = document.body.offsetHeight
              window.scrollTo(0, height + 500)

              try {
                const fn = eval(cb)
                if (typeof fn === 'function') fn()
              } catch (e) {
                console.log(e)
              }
            }, `${cb}`)
          })
        })

      console.log('开始加载网页数据...')
      await loadAll() // 加载全部页面 没有请求2秒后停止
      console.log('网页数据加载完毕!')

      // 获取图片地址
      const data = await page.$$eval('img', imgs => {
        let imgUrlList = []
        imgs.forEach(img => {
          const property = [...img.attributes] // 将img的属性转换为数组
          property.forEach(({ value }) => value.slice(0, 9).includes('//') && imgUrlList.push(value)) // 判断是否是url
        })
        return [...new Set(imgUrlList)].filter(i => i.indexOf('.svg', i.length - 9) == -1) // 去重
      })

      // 关闭浏览器
      await browser.close()
      const results = data.map(url => (/^http/.test(url) ? url : `https:${url}`)) // 判断是否是https协议
      return results // 返回结果
    } catch (e) {
      // 关闭浏览器
      console.log(e)
      await browser.close()
    }
  }

  // 尝试下载
  try {
    const urlList = await getImgUrl(url) // 获取图片下载地址列表
    await fs.access(dir).catch(() => fs.mkdir(dir, { recursive: true })) // 创建目录

    // 数组分片方法
    const slicing = (arr, count) => {
      if (!Array.isArray(arr)) throw new Error('arr must be an Array')
      if (typeof count !== 'number' || count < 1) throw new Error('count must be a number')

      let list = []
      for (let i = 0; i < arr.length; i += count) {
        list.push(arr.slice(i, i + count))
      }
      return list
    }

    // 下载列表
    const list = slicing(urlList, count)
    // 下载结果
    const results = []

    console.log(`${urlList.length}张照片待下载...`)
    const startTime = new Date() // 开始时间

    // 开始分片下载
    for (let i = 0; i < list.length; i++) {
      console.log(`开始下载 -> ${(i + 1) * count}`)
      const downloadList = list[i].map((item, index) => {
        // 文件名
        const filename = (new Array(10).join('0') + (i * count + index)).slice(-6) + '.jpg'
        return Promise.retry(download, [item, dir, filename])
      })
      const res = await Promise.allSettled(downloadList)
      results.push(...res)
    }

    // 输出下载结果
    results.forEach(({ value, reason }) => console.log(value || reason))

    // 输出结束时间
    const endTime = new Date() // 结束时间
    console.log('下载完成 -> 耗时:' + (endTime - startTime) / 1000 + 's') // 耗时
  } catch (e) {
    console.error(e)
  }
}

// const url =
//   'https://image.baidu.com/search/index?tn=baiduimage&ipn=r&ct=201326592&cl=2&lm=-1&st=-1&fm=result&fr=&sf=1&fmq=1652366997889_R&pv=&ic=&nc=1&z=&hd=&latest=&copyright=&se=1&showtab=0&fb=0&width=&height=&face=0&istype=2&dyTabStr=MCwzLDEsNCw1LDcsOCwyLDYsOQ%3D%3D&ie=utf-8&sid=&word=%E9%A3%8E%E6%99%AF'

const url = 'https://www.vcg.com/sets/516942437'

const dir = path.join(__dirname, 'imgs/img1') // 图片存储目录

// 第一个参数 要爬取网站的地址
// 第二个参数 图片存储目录
// 第三个参数 分片下载数量
// 第四个参数 回调函数用于浏览器操作
imgDownloader(url, dir, 200, () => {
  const dom = document.querySelector('.jss50')
  dom && dom.click()
})

运行结果

运行结果
说实话感觉Puppeteer做爬虫有点屈才了,它应该有更强大的功能,大家一起慢慢学习慢慢总结吧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1389015.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何根据水利需求选择合适的遥测终端机

在水利信息化建设中&#xff0c;遥测终端机作为关键的设备之一&#xff0c;发挥着越来越重要的作用。如何根据实际的水利需求选择合适的遥测终端机&#xff0c;成为了众多企业和单位关注的焦点。本文将为您揭示遥测终端机的选择之道&#xff0c;助您在水利信息化建设中取得事半…

微信接入知识库定制化的AI会怎样?

想不想要一个更加了解你的chatgpt&#xff1f;或者想给chatgpt加入特定的知识库&#xff1f; LinkAI来帮你&#xff01; 通过LinkAI&#xff0c;无需openai的api key&#xff0c;直接使用chatgpt。无需考虑服务器代理配置&#xff0c;openai账号注册等&#xff01;自定义知识…

3.3.3 使用集线器的星形拓扑

3.3.3 使用集线器的星形拓扑 集线器的一些特点 3.3.4 以太网的信道利用率 多个站在以太网上同时工作就可能会发生碰撞当发生碰撞时&#xff0c;信道资源实际上是被浪费了。因此&#xff0c;当扣除碰撞所造成的信道损失后&#xff0c;以太网总的信道利用率并不能达到100% 3.…

多个table的选中问题

多个table的选中问题 场景&#xff1a;循环出来多个table&#xff0c;最后拿到所有选中的数据 出现的问题&#xff1a;比如先选择第一个table的某些数据&#xff0c;再去选另外的table&#xff0c;这样selection里面只有最后选中的table的数据。 解决方法&#xff1a;在sele…

接口interface--java学习笔记

认识接口 java提供了一个关键字interface&#xff0c;用这个关键字可以定义出一个特殊的结构&#xff1a;接口在接口里面定义的变量&#xff0c;不管加不加public static final修饰都默认为常量&#xff0c;必须赋初值在接口里面定义的方法&#xff0c;不管加不加public abstr…

Java实现高校大学生创业管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统公告模块2.2 创业项目模块2.3 创业社团模块2.4 政府政策模块2.5 创业比赛模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 系统公告表3.2.2 创业项目表3.2.3 创业社团表3.2.4 政策表 四、系统展示五、核心代码5.…

操作系统详解(5.2)——信号(Signal)的题目进阶

系列文章&#xff1a; 操作系统详解(1)——操作系统的作用 操作系统详解(2)——异常处理(Exception) 操作系统详解(3)——进程、并发和并行 操作系统详解(4)——进程控制(fork, waitpid, sleep, execve) 操作系统详解(5)——信号(Signal) 操作系统详解(5.1)——信号(Signal)的相…

linux终端上传github提示:更新被拒绝,因为远程仓库包含您本地尚不存在的提交

问题&#xff1a; 提示&#xff1a;更新被拒绝&#xff0c;因为远程仓库包含您本地尚不存在的提交。这通常是因为另外 提示&#xff1a;一个仓库已向该引用进行了推送。再次推送前&#xff0c;您可能需要先整合远程变更 提示&#xff1a;&#xff08;如 git pull ...&#xff…

走进Spring Boot 3.x时代(一)

目录 前言Spring Boot 2.x 时代Spring Boot 3.x 时代Spring Boot 3.x 新特性JDK调整平滑升级依赖调整最低环境要求配置属性兼容提高应用可观察性Jakarta EE升级的关键变更支持 GraalVM 原生镜像三方包升级Log4j2增强ConstructorBinding检测优化Micrometer 升级Micrometer Trac…

有哪些免费3D模型网站值得推荐?

设计师们经常需要一些免费的3D模型网站用来寻找各种类型的3D模型&#xff0c;如人物、动物、车辆、建筑、道具等&#xff0c;从而满足不同的设计需求以及设计灵感。那么有哪些免费3D模型网站值得推荐&#xff1a; ①建e网&#xff1a;建e网是一个提供3D模型下载的平台&#xff…

D20XB100-ASEMI开关电源桥堆D20XB100

编辑&#xff1a;ll D20XB100-ASEMI开关电源桥堆D20XB100 型号&#xff1a;D20XB100 品牌&#xff1a;ASEMI 封装&#xff1a;GBJ-5&#xff08;带康铜丝&#xff09; 平均正向整流电流&#xff08;Id&#xff09;&#xff1a;20A 最大反向击穿电压&#xff08;VRM&#…

【Python3】【力扣题】387. 字符串中的第一个唯一字符

【力扣题】题目描述&#xff1a; 【Python3】代码&#xff1a; 1、解题思路&#xff1a;遍历字符串&#xff0c;依次判断元素在字符串中的个数是否为1&#xff0c;第一个为1的返回该元素的索引号&#xff0c;若整个字符串都没有个数为1的&#xff0c;则返回-1。 知识点&#…

基于ssm的学籍管理系统论文

摘 要 当下&#xff0c;如果还依然使用纸质文档来记录并且管理相关信息&#xff0c;可能会出现很多问题&#xff0c;比如原始文件的丢失&#xff0c;因为采用纸质文档&#xff0c;很容易受潮或者怕火&#xff0c;不容易备份&#xff0c;需要花费大量的人员和资金来管理用纸质文…

vue+echarts 几个案例

普通柱状图 <template><!-- 容器默认宽高是0 如果不设置 页面不显示--><div ref"mychart" id"mychart"></div> </template><script> import * as echarts from "echarts" import axios from axiosexport …

致大家的一封信2024 — 对称性原理

紫光集团董事长李滨先生&#xff0c;作为融信产业联盟理事长每年都会为联盟成员发布一封新年信&#xff0c;与各联盟伙伴分享新一年的思考与感悟。今年&#xff0c;李滨先生围绕“对称性原理”进行了2024年的新年分享&#xff0c;并向大家带来新一年的美好祝福。 原文如下&…

python 二次封装Modbus库实现设备间Modbus通信

前言&#xff1a; 想想好久没有更博客了&#xff0c;今天忙中偷闲准备写一篇Modbus相关的分享。在进入工业领域之前我一直从事软件行业的测试工作&#xff0c;所以也没听过Modbus协议&#xff0c;但是自从进入西门子工作后正式进入了工业领域&#xff0c;所以对Modbus协议也就…

chromium+clangd快速代码跳转

在开发chromium的时候我们使用vscode工具进行开发&#xff0c;如果使用C插件发现很容就卡死计算机了。 所以我们使用clangd工具来查看chromium的代码。 一、安装 在vscode中安装还是很简单的。 输入cland&#xff0c;点击安装即可 二、生成编译数据库 代码编译完成后&…

javascript的变量存储机制和原理

前言 在了解javascript的变量存储机制之前需要了解javascript的数据类型&#xff0c;在js中&#xff0c;数据类型分为基本数据类型和引用数据类型。二者存在内存中&#xff0c;基本类型存在栈中&#xff0c;引用类型存储在堆里。 想查看javascript数据类型详细介绍请访问&…

Spring中的事件机制

文章目录 摘要正文jdk事件Spring事件Spring事件监听ApplicationContext主动监听注解监听Bean监听 Spring事件发布 总结 摘要 在这篇文章我们将介绍Spring的事件机制&#xff0c;包括Spring内置事件、自定义事件、事件监听、事件发布、事件广播机制、事件异常处理等内容。Sprin…

Codeforces Round 919 (Div. 2)

Problem - A - Codeforces n个约束条件 a x 求出满足n个约束条件的整数的个数 大于等于x&#xff0c;取最大的 小于等于x&#xff0c;取最小的 然后不等于x的&#xff0c;记录在区间范围内的个数&#xff0c;减去这些 #include<bits/stdc.h> #define endl \n #define …