Puppeteer 使用教程-实战篇(爬取图片、视频、音频,页面数据)

news2024/11/25 12:57:30

目录

前言

一、 获取实体店铺信息

二、 获取全国各省市县地图json数据

三、 cookies

四、 获取网络图片、视频资源

五、 自动化测试

总结


前言

        续上篇,我们简单讲述一下puppeteer常见的应用场景,包括静态页面数据获取,网络请求获取截取、图片、视频资源下载、自动化测试等。

一、 获取实体店铺信息

        这个案例是我在网上看到的真实案例,需求是需要爬取店铺信息,用于广告投放,需要有店铺面积、联系方式、租金、位置等信息,出价800¥,还是非常诱人的。大家学会了puppeteer后,也可以接这种单子做。

 下面我们来实现这个案例:

先爬取基础信息吧,这个代码是 puppeteer最基础的代码了。

// 初始化puppeteer
async function initPuppeteer() {
  const browser = await puppeteer.launch({ headless: false });

  const page = await browser.newPage();

  page.goto(baseURL);
}

 信息都在这个div里面,我们使用 page.$eval()选择这个div,向里取数。

 

 我们想要的信息在这,使用 document.querySelectorAll()选中所有的 class='list-item',每一项单独获取信息即可:

 await page.$eval('div[class="list-left"]', (listLeft) => {
 // 这里不用document,而是在 已经选中的基础上进行下一步操作
    const items = listLeft.querySelectorAll('div[class="list-item"]');
    items.forEach(async (item) => {
      // 这里获取的是每一项数据,可以直接拿到信息
      const item_a_link = item.querySelector("a");

      //   获取图片链接
      const item_img_src = item_a_link
        .querySelector('div[class="item-img"]')
        .querySelector("img")
        .getAttribute("src");

      // 获取标题
      const item_title = item_a_link
        .querySelector('div[class="item-info"]')
        .querySelector('div[class="item-title"]')
        .querySelector("span").innerText;

      // 获取 联系人 名称
      const item_user = item_a_link
        .querySelector('div[class="item-info"]')
        .querySelectorAll("p")[2]
        .querySelector("span").innerText;
    });
});

 

 现在处理联系方式:

        页面设计为需要打开新tab页,点击 电话联系TA 按钮,才能显示电话 ,因为需要等待 60 的浏览器响应数据,因此,设计为异步处理。异步处理则是在一个页面中跳转路由,而不是打开多个浏览器,节省内存,不然会导致内存溢出,程序中断。

for (const item of data) {
    if (!item.phoneUrl) return;
    // 请求phone
    await page.goto(item.phoneUrl);

    // 处理 元素不存在,需要点击校验的问题(存在机器校验问题,需要等待元素)

    if (!(await page.waitForSelector('div[class="tel-wrap"]')))
      await page.click('input[class="btn_tj"]');

    await page.waitForSelector('div[class="tel-wrap"]');
    //   点击 电话联系ta 显示号码
    await page.click('div[class="tel-wrap"]');

    //   等待元素
    await page.waitForSelector('div[class="tel-phone-number"]');

    //   获取号码
    const phone = await page.evaluate(() => {
      return document.querySelector('div[class="tel-phone-number"]').innerText;
    });

    item.phone = phone;

    // 这里不要 page.close() 不然没有操作页面,
    // 其二 close 后,一定要 newPage(),两种方案
  }

 请求次数过多,会有这个提示,这个时候,需要判断元素是否存在,不存在,需要进行点击处理:

 //   首页也会有机器校验问题
  if (!document.querySelector('div[class="list-left"]'))
    await page.click('input[class="btn_tj"]');

效果如下:

当然,有些用户的号码是虚拟的,10分有限,那每隔10分钟爬取一次,更新变量就行了,将数据转存为json文件:

 测试没问题了,就可以关闭 headless 模式了。还可以通过参数控制数据获取范围,参数型数据获取,我们到下面再说哈

二、 获取全国各省市县地图json数据

DataV.GeoAtlas地理小工具系列

        这个就是我地图篇的数据爬取了,下面说说思路:

        页面网络请求中,有一个 all.json 的请求,是全国各省市县的adcode、name属性,我们先拿到这个数据,然后根据adcode进行分别请求即可。

 全国JSON数据:https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json

 广西JSON数据:https://geo.datav.aliyun.com/areas_v3/bound/450000_full.json

 柳州市JSON数据:https://geo.datav.aliyun.com/areas_v3/bound/450200_full.json

柳城县JSON数据:https://geo.datav.aliyun.com/areas_v3/bound/450222.json

        可以看出,前缀是一样的,无非就是更换了请求的adcode,县级地图没有 _full ,因此,我们最重要的三个数据项:adcode、name、level,【但是我们仔细看all.json,他只有四个层级,country、province、city、district。】就可以爬取全国各省市县的JSON数据,下面我们开始吧。

         puppeteer 页面刚加载时,并没有请求 all.json,因此需要实现刷新页面 page.reload(),监听请求没有响应体,转为监听响应了,两者都是相似的

  page.on("response", async (res) => {
    if (
      res.request().url() ===
      "https://geo.datav.aliyun.com/areas_v3/bound/all.json"
    )
       const data = await res.text();
      saveFile(data);
  });

 

 拿到这个数据后,直接发送get请求,就可以得到响应体实现文件转存了:

// 请求各省市县json数据
async function queryJson(list) {
  if (!list.length) return;
  for (const item of JSON.parse(list)) {
    //  一共3000多个,我就模拟前几个就行了 到 adcode = 110115 退出
    if (item.adcode === 110115) break;
    // 发送请求
    console.log(`## 正在请求 ${item.name} json数据,命名为${item.adcode}.json`);
    const url =
      item.level === "district"
        ? `https://geo.datav.aliyun.com/areas_v3/bound/${item.adcode}.json`
        : `https://geo.datav.aliyun.com/areas_v3/bound/${item.adcode}_full.json`;

    try {
      const { data } = await axios.get(url);
      saveJson(data, item.adcode);
    } catch (error) {
      console.log("请求出错", error);
    }
  }
}

        通过这两个案例,你也能清晰看出,每个页面的数据获取并不全是一样的。一定要先关注你想爬取的数据,是怎么来的,页面静态数据、接口数据还是啥,下面的案例,我们说一下怎么通过监听接口响应来获取数据。

三、 cookies

        上面两个案例,接触了静态页面数据获取、接口数据获取,既然puppeteer也能进行输入操作,为什么不直接输入账号密码登录,而是要进行cookies设置?有些是需要手机验证码的,在puppeteer上等待验证码不太好,因此,登录一次后,进行cookies设置是最合适的。目前没找到合适的案例进行说明,以后遇到了再补充。

四、 获取网络图片、视频资源

 https://www.upupoo.com/bd01?n=20210426043&bd_vid=11724880147497932614

 难点在于请求的资源进行保存,使用 fs 模块完成即可。

// 在这里处理一下 参数 的真正实现的思路吧(先获取映射)
  const paramsIndex = await page.evaluate(() => {
    let map = [];
    const lis = document
      .querySelector('ul[class="wallpaper-tag-list"]')
      .querySelectorAll("li");
    lis.forEach((i) => map.push(i.innerText));
    return map;
  });
// 判断参数
  if (type) {
    const btns = await page.$$('ul[class="wallpaper-tag-list"] li');
    btns[paramsIndex.findIndex((i) => i === type)].click();
  }

 先使用page的方法,点击了页面后,才可以进行页面数据获取,这才是参数型获取数据正确的做法。

  await page.exposeFunction("downloadImg", downloadImg);
 // 处理数据(又要等待,不然没结果)
  await page.waitForSelector('li[class="wallpaper-item"] div img');

  await page.evaluate(() => {
    const images = document.querySelectorAll(
      'li[class="wallpaper-item"] div img'
    );
    images.forEach((img) => {
      // 获取li的img属性
      downloadImg(img.getAttribute("src"));
    });
  });
async function downloadImg(url) {
  // 解析类型
  const [name, type] = url.split("theme")[1].split(".");
  const { data } = await axios.get(url, {
    responseType: "arraybuffer", // 务必设置响应类型
  });
  const filename = name.split("/");

  fs.writeFile(
    `./demo/img/${filename[1]}_${filename[2]}.${type}`,
    data,
    "binary",
    function (err) {
      if (err) return console.log("文件保存失败", err);
      console.log("保存图片成功");
    }
  );
}

 视频的获取也是类似的,都是拿到url,进行请求,然后进行文件保存:

async function initVideo() {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();
  page.goto("https://www.douyin.com/");

  const closebtn = await page.waitForSelector('div[class="dy-account-close"]');
  // 如果有提示登录,则关闭按钮
  if (closebtn) await page.click('div[class="dy-account-close"]');

  await page.exposeFunction("downloadVideo", downloadVideo);
  for (const i of new Array(5).fill(0)) {
    await page.evaluate(async () => {
      await downloadVideo(
        document.querySelector("video source").getAttribute("src")
      );
    });
    // 点击下一个视频
    await page.click('div[class="xgplayer-playswitch-next"]');
  }
}

 

  爬取音频:

        任何网络资源请求,在操作前,都一定要观察一下它的资源是怎么出现的。无非常见的两种形式:页面url、网络请求。我已经很多次都强调了这个点,每个页面都是不一样的,先观察,再考虑采取什么方式爬取,不然你无从下手。

Vite + Vue + TS 这个音乐播放器就不是常见的页面url,找了元素好久页没有发现音频的路径,而是每点击一次页面请求拿到音频直接播放。因此我们获取响应的请求,判断类型,拿到音频

res.request().resourceType():请求资源类型 资源类型为以下值中的一个:documentstylesheetimagemediafontscripttexttrackxhrfetcheventsourcewebsocketmanifestother。

根据请求拿url:

 哇,这个页面爬取的跟pc的还不完全一样!只能按照按钮先展示播放进度了,然后再依次点击 下一首,进行请求拦截。先看一下它请求的时候传了什么参数:

 请求的id与返回歌曲列表的hash值一致,这样就可以对应唯一的请求,实现歌曲名称歌手对应了。

 先将歌曲的列表信息存起来,请求的时候,分解hash,找到对应的歌曲信息

// 绑定外部方法,专门处理 url hash
function getUrlHash(url) {
  if (!url) return;

  let hash = url.split("?")[1]?.split("=")[1].replace("&quality", ""); // url 的请求 hash

  if (!hash) return; // hash 值不存在,则表示不是歌曲列表中的请求

  // 找歌曲信息
  // {
  //   "id": "0YXav",
  //   "name": "一生有你",  // 歌名
  //   "artist": [{ "id": "x54Y", "name": "水木年华" }],   歌手
  //   "hash": "b3f634HzOLrfMF9SfoFxvaAaYFEZSwZSByWMoKP4GiqF3T270"  请求
  // },
  const songsList = require("./mp3/info.json");
  const item = songsList.find((i) => i.hash === hash);
  if (item) {
    musicList.push({
      musicUrl: url,
      name: item.name,
      user: item.artist,
    });

    console.log("歌曲转存", musicList);
  }
}

需要等待时长,剩下的就是点击下一首进行其他歌曲的捕获:

这个爬取音频是几个案例中最难的,哇,搞了我一天。没想到这个网页做的这么好,夸一下!

五、 自动化测试

        这个就不多说了,无非是 进行按钮的操作、输入框输入,可以配合一些mock库,实现数据模拟,找到好的案例再给大家补充。

总结

        这几个案例大家都自己手敲的话,相信大家对puppeteer的掌握程度一定有质的提升。还是对几个案例做一下总结吧:

  1. puppeteer内部使用 page.$eval、page.evaluate会更多,在node环境中,使用page.$、page.$$更多。
  2. 内部环境就像是 console 控制台,可以随意使用 document.querySelector,但是在node中,你也想获取元素,就要使用 page.$ 获取元素了,进行 page.$().click()的操作。
  3. 在想爬取一个网页数据之前,一定先弄清楚数据来源,是静态页面还是 接口数据,还是需要我们自己发请求。
  4. 一定合理利用 page 的wait方法,可以避免一些错误,特别是 元素选择问题。
  5. 合理使用 async await,不然你都不知道错误怎么来的hhh
  6. 当然,我们设计页面时,也可以考虑一下反爬虫,如果大家感兴趣,可以单独出一篇文章。
  7. 合理利用puppeteer提供的便利,勿做其他非法之事!
  8. 合理利用puppeteer提供的便利,勿做其他非法之事!
  9. 合理利用puppeteer提供的便利,勿做其他非法之事!

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

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

相关文章

详解CPU的态

目录 1.CPU的工作过程 2.寄存器 3.CPU的上下文 4.系统调用 5.CPU的态 1.CPU的工作过程 CPU要执行的指令的地址存在寄存器中,指令存放在内存中,而CPU本质上就是一个去内存中根据地址取指令,然后执行指令的硬件。 举一个例子&#xff1a…

【雕爷学编程】Arduino动手做(22)——8X8 LED点阵MAX7219屏4

37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这…

vscode使用技巧

在使用vscode编辑代码时,在settings.json中增加配置项可以配置回车换行缩进补齐方式: 第一种:使用空格补齐: "editor.insertSpaces":true 按下回车换行后: 第二种:使用tab键补齐: …

【java】对ArrayList中的元素进行排序的几种方式

对ArrayList中的元素进行排序的几种方式 一、使用Collections工具类 1、对基本类型排序 通过Collections.sort()对基本类型排序默认是以升序排序 // 1.Collections.sort()默认按照升序排序 List<Integer> integerList new ArrayList<>(); Collections.addAll(…

每日一刷——替换空格

题目描述&#xff1a; 请实现一个函数&#xff0c;将一个字符串中的每个空格替换成“%20”。例如&#xff0c;当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 我的思路&#xff1a;从左向右循环遍历字符串&#xff0c;定义一个空串。如果遇到空格&#xf…

Ubuntu18.04 拯救者R9-7945HX 4060 配置ZED 2i代双目相机驱动+ORBSLAM2

AMD的拯救者网卡很拉&#xff0c;研究了很久除了换网卡可以解决网络问题&#xff0c;其它没找到合适的办法&#xff0c;这里我用手机USB共享网络的方式勉强上网&#xff0c;这里不得不说华为的信号桥很好用。 之前在1050ti的电脑上布置过&#xff0c;很顺利&#xff0c;这个新…

SDUT 2023 summer team contest(for 22) - 1-Gym - 102220

B - Balanced Diet 题意&#xff1a;这题题意有点难搞啊&#xff0c;就是有n个物品&#xff0c;一个有m种&#xff0c;对于第 i 种物品如果你要买它就至少买 l[i]个&#xff0c;然后就是给你n行&#xff0c;每行两个数&#xff0c;ai,bi,表示这个糖果类型为bi&#xff0c;价值为…

STM32学习笔记(十三)丨USART通用同步/异步收发器(串口外设的基本使用丨串口发送数据、串口发送+接收数据)

本篇文章包含的内容 一、STM32的USART外设1.1 STM32的USAER外设简介1.2 USART外设的结构和工作原理1.3 串口通信数据帧1.4 起始位侦测和USART的噪声判断机制1.5 波特率发生器 二、串口发送和接收数据包2.1 HEX数据包2.2 文本数据包2.3 固定包长HEX数据包接收2.4 可变包长文本数…

chrome edge svg转png

chrome edge svg转png 生成SVG blockdiag Live Preview 导出png 截图&#xff1a; 左上角截取屏幕截图

零售EDI:True Value EDI 需求分析

True Value 是一家享有盛誉的卖场&#xff0c;经营范围广泛&#xff1a;包括家居用品、工具、园艺用品等。据悉&#xff0c;True Value 已将 EDI 纳入其供应商评级中。 True Value 将 EDI 作为对其供应商的一项要求&#xff0c;这意味着如果你希望与 True Value 建立合作关系&a…

uniapp 小程序 联想地址搜索

效果图&#xff1a; qqmap-wx-jssdk.js下载 <template><view class"items"><view class"items-text">地址&#xff08;必填&#xff09;</view><input type"text" placeholder"搜索地址" maxlength&quo…

SQL-每日一题【585.2016年的投资】

题目 Insurance 表&#xff1a; 请你编写一个 SQL 查询&#xff0c;报告 2016 年 (tiv_2016) 所有满足下述条件的投保人的投保金额之和&#xff1a; 他在 2015 年的投保额 (tiv_2015) 至少跟一个其他投保人在 2015 年的投保额相同。他所在的城市必须与其他投保人都不同&#…

Revit中如何添加剖面?快速实现剖面图

一、Revit中如何添加剖面&#xff1f; 除了标高绘制所得到的楼层平面视图和立面视图之外&#xff0c;还可以添加剖面视图&#xff0c;这样可以得到任意位置一个竖向的剖切面&#xff0c;例如在楼梯细节处理中&#xff0c;楼梯处于建筑物内部&#xff0c;立面也看不到整个楼梯的…

pdf怎么翻译?有这个工具就够了

pdf怎么翻译&#xff1f;PDF文档一直是我们日常生活和工作中不可避免的一部分。但是&#xff0c;当我们需要翻译PDF文件时&#xff0c;我们往往会感到无助&#xff0c;因为PDF文档不能像其他文本文件一样直接复制和粘贴。那么今天就给大家介绍一款可以帮助我们进行PDF翻译的工具…

Pytorch框架中各文件的作用

新人在接触Pytorch以及深度学习等领域时&#xff0c;面对一个开源的代码可能无从下手&#xff0c;一个Pytorch框架相对比较负责&#xff0c;文件也多&#xff0c;其中的逻辑不免让初学者感到不知所措&#xff0c;下面大致梳理一下Pytorch各文件夹的作用和逻辑&#xff0c;其中的…

基于 Orbit 的云原生应用交付基础原则与良好实践

本文作者&#xff1a;何文强——腾讯云 CODING 高级架构师。 负责 CODING DevOps产品解决方案架构设计和技术产品布道以及 CODING 云原生技术研究与落地实践。在多个技术大会担任演讲嘉宾&#xff0c;腾讯云 CODING DevOps 课程认证出品人&#xff0c;腾讯云云原生训练营核心初…

上市公司Git分支管理规范

Git分支管理策略 主分支Master 首先&#xff0c;代码库应该有一个、且仅有一个主分支。所有提供给用户使用的正式版本&#xff0c;都在这个主分支上发布。 Git主分支的名字&#xff0c;默认叫做Master。它是自动建立的&#xff0c;版本库初始化以后&#xff0c;默认就是在主…

ASEMI快恢复二极管MUR20100CT尺寸, MUR20100CT体积

编辑-Z MUR20100CT参数描述&#xff1a; 型号&#xff1a;MUR20100CT 最大峰值反向电压(VRRM)&#xff1a;1000V 最大RMS电压(VRMS)&#xff1a;700V 最大直流阻断电压(VDC)&#xff1a;1000V 平均整流正向电流(IF)&#xff1a;20A 非重复峰值浪涌电流(IFSM)&#xff1a…

Revit中墙体的问题,门窗洞口及柱断梁墙

一、如何同时开两道相邻墙的门窗洞口 做外墙装饰的时候&#xff0c;我们很经常为了方便、简洁在已经绘制好的墙体外围再绘制一面墙体&#xff0c;并且添加上材质作为外饰面&#xff0c;提高工作效率;但是遇到有门窗洞口的墙体时&#xff0c;外饰面墙体却没办法直接被门窗剪切&a…

LeetCode142.环形链表II

142.环形链表II 目录 142.环形链表II一、哈希表二、双指针 一、哈希表 和141题.判断链表是否有环类似&#xff0c;区别在于141题只要求判断链表中是否有环&#xff0c;该题则要求我们返回入环节点 一个非常直观的思路&#xff1a;遍历链表中的每个节点&#xff0c;并将它们记…