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

文章目录

  • 一.项目需求及Puppeteer是什么?
  • 二.Puppeteer注意事项及常用的方法
    • 1.注意事项
    • 2.常用的方法
      • *puppeteer.launch()*
      • *browser.newPage()*
      • *page.goto()*
      • *page.on('request',()=> {})*
      • *page.evaluate()*
      • *page.$$eval()*
      • *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.$$eval()

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

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做爬虫有点屈才了,它应该有更强大的功能,大家一起慢慢学习慢慢总结吧

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(亲测可行)关于提高IDEA运行速度的方案

1.作者IDEA软件版本和计算机内存 Ultimate 2022.1.2版IDEA&#xff0c;计算机内存为12GB 2.修改配置以提高IDEA运行速度的误区-调高堆内存 很多文章会教调配置的内存&#xff0c;但大多是让你调高堆内存&#xff0c;比如会让你调高-Xms -Xmx &#xff0c;这两种对应的是最…

推荐几个Github高星GoLang管理系统

在Web开发领域&#xff0c;Go语言&#xff08;Golang&#xff09;以其高效、简洁、高并发等特性逐渐成为许多开发者的首选语言。有许多优秀的Go语言Web后台管理系统&#xff0c;这些项目星星众多&#xff0c;提供了丰富的功能和良好的代码质量。本文将介绍一些GitHub高星的GoLa…

32单片机RTC时间接续,掉电时间保存

1、实现思路 前提&#xff1a;首先要实现RTC掉电之后时间还能继续走&#xff0c;RTC电池是必要的 说明&#xff1a;设备第一次启动需要初始化配置RTC&#xff0c;但当二次启动再重新配置RTC会导致RTC计数器置零&#xff0c;所以传统的程序流程是不行的&#xff0c;我们需要知…

.sync详解记录(vue2)

.sync修饰符使用注意 在Vue.js中&#xff0c;.sync修饰符是一个非常有用的修饰符&#xff0c;它可以让父组件和子组件之间实现双向数据绑定。本文将详细介绍.sync修饰符的使用方法和原理。 什么是.sync修饰符&#xff1f; .sync修饰符是Vue.js提供的一种语法糖&#xff0c;它可…

transbigdata笔记:轨迹切片

1 方法介绍 在transbigdata笔记&#xff1a;轨迹停止点和行程提取-CSDN博客中&#xff0c;已经可以把轨迹点拆分成停止点和行程点&#xff0c;但是行程点只有起止位置&#xff0c;不包含行程轨迹信息为了进一步分析车辆的行驶轨迹&#xff0c;需要从每次行程的时间段中提取轨迹…

《2023年度程序员收入报告》 :旧金山位居第一,北京程序员中位数超60万元

2024年刚刚拉开序幕&#xff0c;备受瞩目的程序员薪资调研报告再度登场。由知名数据采集平台levels.fyi 搜集并整理了《2023年全球程序员收入报告》&#xff0c;为我们揭示了程序员最新的收入情况&#xff0c;其中有哪些值得关注的亮点呢&#xff1f; 行情向好&#xff0c;大多…

jmeter--8.加密传输

目录 1. Base64加密 2. MD5加密 3. SHA加密&#xff08;sha1\sha\sha224\sha256\sha384\sha512&#xff09; 4. RSA加密-公钥加密&#xff0c;私钥解密 1. Base64加密 1.1 在需要加密传输的接口下新增BeanShell 预处理程序&#xff0c;${username}可替换成value值&#xff…

Pycharm Terminal 无法激活conda环境

1.问题 Failed to activate conda environment. Please open Anaconda prompt, and run conda init powershell there. 这导致我们无法在Pycharm中使用conda命令 2.解决办法 修改为第二个&#xff0c;然后重启Terminal 再打开时发现已经是当前的conda环境

在客户端访问远程Linux服务器的私有IP地址的URL

文章目录 环境背景SSH tunnel和正向/反向代理步骤第一步第二步效果考一考 其它多次跳转另一种方法&#xff1a;正向代理 参考 环境 服务器&#xff1a;Ubuntu 22.04客户端&#xff1a;Mac 14.2.1 背景 在远程Linux服务器上搭建了minikube环境。minikube提供了dashboard功能&…

LLM:Scaling Laws for Neural Language Models (上)

论文&#xff1a;https://arxiv.org/pdf/2001.08361.pdf 发表&#xff1a;2020 摘要1&#xff1a;损失与模型大小、数据集大小以及训练所用计算量成比例&#xff0c;其中一些趋势跨越了七个量级以上。 2&#xff1a;网络宽度或深度等其他架构细节在很大范围内影响较小。3&…

mac pro “RESP.app”意外退出 redis desktop manager

文章目录 redis desktop manager下载地址提示程序含有恶意代码“RESP.app”意外退出解决办法&#xff1a;下载python3.10.并安装重新打开RESP如果还是不行&#xff0c;那么需要替换错误路径&#xff08;我的没用&#xff09;外传 最近在研究redis的消息&#xff0c;看到了strea…

mac查看maven版本报错:The JAVA_HOME environment variable is not defined correctly

终端输入mvn -version报错: The JAVA_HOME environment variable is not defined correctly, this environment variable is needed to run this program. Java环境变量的问题,打开bash_profile查看 open ~/.bash_profile export JAVA_8_HOME/Library/Java/JavaVirtualMachine…

Elasticsearch的基本功能和使用

Elasticsearch &#xff0c;简称为 ES&#xff0c;是一款非常强大的开源的高扩展的分布式全文 检索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容,它可以近乎实时的 存储、检索数据.还可以可以实现日志统计、分析、系统监控等功能. 官网:https://www.elastic.c…

Spark---累加器和广播变量

文章目录 1.累加器实现原理2.自定义累加器3.广播变量 1.累加器实现原理 累加器用来把 Executor 端变量信息聚合到 Driver 端。在 Driver 程序中定义的变量&#xff0c;在Executor 端的每个 Task 都会得到这个变量的一份新的副本&#xff0c;每个 task 更新这些副本的值后&…

利用HTML和CSS实现的浮动布局

代码如下 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>*{m…

Python武器库开发-武器库篇之Whois信息收集模块化(四十五)

Python武器库开发-武器库篇之Whois信息收集模块化(四十五) 我们在进行渗透的时候&#xff0c;需要进行全面的信息收集&#xff0c;除了主动信息收集之外&#xff0c;我们还经常会进行被动信息收集&#xff0c;Whois信息收集就是其中的一种,我们可以利用一些网站进行Whois信息收…

区间预测 | Matlab实现LSTM-Adaboost-ABKDE的集成学习长短期记忆神经网络自适应带宽核密度估计多变量回归区间预测

区间预测 | Matlab实现LSTM-Adaboost-ABKDE的集成学习长短期记忆神经网络自适应带宽核密度估计多变量回归区间预测 目录 区间预测 | Matlab实现LSTM-Adaboost-ABKDE的集成学习长短期记忆神经网络自适应带宽核密度估计多变量回归区间预测效果一览基本介绍程序设计参考资料 效果一…

Java--HashMap中put()方法是如何实现的

一、什么是HashMap HashMap是Java中常用的数据结构之一&#xff0c;它基于哈希表实现&#xff0c;提供了快速的键值对存取能力。在HashMap中&#xff0c;put方法是最常用的方法之一&#xff0c;用于将键值对存储到HashMap中。本文将深入探究HashMap的put方法的实现原理&#x…

vue 作用域插槽、具名插槽

在子组件中使用了具名插槽<slot name"qwe" :games"games" x"我是x" y"我是y"></slot>&#xff0c; 相应的在父组件中使用<template #qwe"data">的语法来接收插槽内容。 这个语法是ok的&#xff0c; 但…

微信使用wx.getLocation

1&#xff0c;小程序管理后台 -「开发」-「开发管理」-「接口设置」” 中完成权限申请&#xff1b; 2&#xff0c;需在 app.json 中声明其需调用的地理位置相关接口 "permission": {"scope.userLocation": { "desc": "你的位置信息将用于小…