Puppeteer 爬取动态生成的网页实战


Posted in Javascript onNovember 14, 2018

Puppeteer 相关介绍与安装不过多介绍,可通过以下链接进行学习

一、Puppeteer

开源地址

英文文档

中文社区

二、爬取动态网页

1. 需求

首先,了解下我们的需求: 爬取zoomcharts 文档中 Net Chart 目录下所有访问连接对应的页面,并保存到本地

Puppeteer 爬取动态生成的网页实战

2. 研究 ZoomCharts 文档页面结构

首先,我们得研究透 ZoomCharts 页面如何加载,以及左侧导航的 DOM 树结构,才好进行下一步操作

页面首次加载

Puppeteer 爬取动态生成的网页实战

页面首次加载,左侧导航第一个目录 Introduction 高亮,从控制台可看出,该元素增加了 active 类,同时 li[data-section="net-chart"] 节点下只有一个元素节点 a

点击 Net Chart 目录

Puppeteer 爬取动态生成的网页实战

点击 Net Chart 目录, Net Chart 目录高亮,下拉显示子目录,查看控制台,其元素节点增加 active 类,并增加 ul 子元素节点, 此时,第一个子目录节点也只有一个子元素节点 a

结论

不难发现, 左侧目录是动态生成的,而不是静态写死的,只有点击父级目录,其子目录才会生成显示,同时,父级目录元素上的 drop 类表明存在子级目录

3. 编写主程序

通过上面分析,得出大概流程如下

  • 从上到下,遍历 Net Chart 目录的 DOM 树,当找到 a.drop 的元素节点,模拟鼠标点击事件 click ,生成子目录节点
  • 找到 Net Chart 目录下所有的 a 链接,生成一个数组
  • 遍历数组,访问每一个子目录页面,保存页面的 html 文件到本地

接下来实现每个具体流程

项目初始化

安装 puppeteer , rimraf (文件夹操作时需用到)

npm i -S puppeteer rimraf

新建 test.js 文件并引入

const puppeteer = require('puppeteer');
const chalk = require('chalk');
const path = require('path');
const https = require('https');
const fs = require('fs');
const rm = require('rimraf');

const settings = {
 headless: false
}

function resolve(dir, dir2 = '') {
	return path.posix.join(__dirname, './', dir, dir2);
}

async function main () {
 const browser = await puppeteer.launch(settings); // 创建一个Browser 对象
 try {
  const page = await browser.newPage(); // 使用 Browser 创建 Page 
  page.setDefaultNavigationTimeout(600000);
  // 监听 console 
  page.on('console', msg => {
   for (let i = 0; i < msg.args().length; ++i) {
    console.log(`${i}: ${msg.args()[i]}`);
   }
  });
  
  <!-- main start -->
  // main 区域
  
  <!-- end start-->
  console.log('服务正常结束')
 } catch (error) {
  console.log('服务出现错误:')
  console.log(error)
 } finally {
  
 }
}

main()

接下来所有代码都在 main 区域内完成, 完整代码可访问github代码仓库 查看,下面仅列出每部分的思路

创建文件夹,用于保存爬取的文件

  • 定义文件输出路径
  • 根据路径生成文件夹
  • 当文件夹已经存在,先删除,再新建

实现 Net Chart 目录下所有 a.drop 元素的点击事件

这部分涉及到DOM 操作, 只有在 page.evaluate() 中才能访问真实的 DOM 元素,同时,在 page.evaluate() 中不能直接调用外面定义的函数,可将函数传递进去,或将函数绑定到 window 对象上

await page.evaluate(async () => {
 const rootNode = document.querySelector('#menu > ul > li:nth-child(5) > ul > li:nth-child(5)');
 await window.walkDOM(rootNode)
})

此时,绑定到 window 对象上的 walkDOM 函数需要在 page.evaluateOnNewDocument 函数中定义才能生效

await page.evaluateOnNewDocument(() => {
 // 遍历DOM
 window.walkDOM = (node) => {
  if (node === null) {
   return
  }
  if (node.tagName === 'A' && node.className.indexOf('drop') > -1) {
   node.click() // 点击事件
  }
  node = node.firstElementChild
  while (node) {
   walkDOM(node)
   node = node.nextElementSibling
  }
 }
})

当Net Chart 目录下所有 a.drop 元素点击过后, Net Chart 目录下所有后代子目录都会加载生成,接下来操作就简单了

获取Net Chart 目录下所有 a 元素

  • 通过 document.querySelectorAll() 查找到所有 a 元素,保存到数组
  • 遍历数组,对数组每一项进行处理成 {href: '',text: ''} 对象
  • 返回对象数组

遍历对象数组, 访问每一个链接,下载其HTML文件

  • 跳转每一个链接,下载需要的html到指定文件夹
  • 当 HTML 中存在 img 时,下载所有图片

4. 总结

第一次使用Puppeteer也是磕磕绊绊,花费不少时间,期间也参考了不少文章,还需多多练习

代码仓库

代码仓库

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery 表单验证扩展(三)
Oct 20 Javascript
javascript 得到文件后缀名的思路及实现
May 09 Javascript
jQuery鼠标经过方形图片切换成圆边效果代码分享
Aug 20 Javascript
html+js+highcharts绘制圆饼图表的简单实例
Aug 04 Javascript
运用js教你轻松制作html音乐播放器
Apr 17 Javascript
jQuery的ready方法实现原理分析
Oct 26 Javascript
用javascript获取任意颜色的更亮或更暗颜色值示例代码
Jul 21 Javascript
vue.js template模板的使用(仿饿了么布局)
Aug 13 Javascript
js Math数学简单使用操作示例
Mar 13 Javascript
JavaScript设计模式--简单工厂模式实例分析【XHR工厂案例】
May 23 Javascript
在vscode 中设置 vue模板内容的方法
Sep 02 Javascript
解决vue中provide inject的响应式监听
Apr 19 Vue.js
React和Vue中监听变量变化的方法
Nov 14 #Javascript
详解jQuery获取特殊属性的值以及设置内容
Nov 14 #jQuery
浅谈vue中关于checkbox数据绑定v-model指令的个人理解
Nov 14 #Javascript
js html实现计算器功能
Nov 13 #Javascript
JavaScript使用类似break机制中断forEach循环的方法
Nov 13 #Javascript
小程序登录态管理的方法示例
Nov 13 #Javascript
Vuex 使用 v-model 配合 state的方法
Nov 13 #Javascript
You might like
第四节 构造函数和析构函数 [4]
2006/10/09 PHP
phpcms实现验证码替换及phpcms实现全站搜索功能教程详解
2017/12/13 PHP
laravel-admin 管理平台获取当前登陆用户信息的例子
2019/10/08 PHP
jQuery实现的立体文字渐变效果
2010/05/17 Javascript
Nodejs使用mysql模块之获得更新和删除影响的行数的方法
2014/03/18 NodeJs
js写出遮罩层登陆框和对联广告并自动跟随滚动条滚动
2014/04/29 Javascript
js数组如何添加json数据及js数组与json的区别
2015/10/27 Javascript
JS设置cookie、读取cookie
2016/02/24 Javascript
Easyui 之 Treegrid 笔记
2016/04/29 Javascript
多种jQuery绑定事件的实现方式
2016/06/13 Javascript
javascript事件的绑定基础实例讲解(34)
2017/02/14 Javascript
js实现导航吸顶效果
2017/02/24 Javascript
JS实现求数组起始项到终止项之和的方法【基于数组扩展函数】
2017/06/13 Javascript
jQuery实现表格冻结顶栏效果
2017/08/20 jQuery
vue中eventbus被多次触发以及踩过的坑
2017/12/02 Javascript
nodejs实现超简单生成二维码的方法
2018/03/17 NodeJs
对Vue.js之事件的绑定(v-on: 或者 @ )详解
2018/09/15 Javascript
使用微信小程序开发弹出框应用实例详解
2018/10/18 Javascript
python进阶教程之函数参数的多种传递方法
2014/08/30 Python
python访问mysql数据库的实现方法(2则示例)
2016/01/06 Python
python爬取哈尔滨天气信息
2018/07/14 Python
python assert的用处示例详解
2019/04/01 Python
Python如何实现在字符串里嵌入双引号或者单引号
2020/03/02 Python
Bergfreunde丹麦:登山装备网上零售商
2017/02/26 全球购物
int *p=NULL和*p= NULL有什么区别
2014/10/23 面试题
生产部统计员岗位职责
2014/01/05 职场文书
心理健康教育制度
2014/01/27 职场文书
《雾凇》教学反思
2014/02/17 职场文书
活动总结怎么写
2014/04/28 职场文书
2014年社区综治工作总结
2014/11/17 职场文书
2014个人年度工作总结范文
2014/12/24 职场文书
食品仓管员岗位职责
2015/04/01 职场文书
MySQL复制问题的三个参数分析
2021/04/07 MySQL
Java基础之详解HashSet的使用方法
2021/06/30 Java/Android
安装Windows Server 2012 R2企业版操作系统并设置好相关参数
2022/04/29 Servers
怎么禁用Win11输入法 最新Win11输入法关闭教程
2022/08/05 数码科技