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 相关文章推荐
EXTJS FORM HIDDEN TEXTFIELD 赋值 使用value不好用的问题
Apr 16 Javascript
window.location.reload()方法刷新页面弹出要再次显示该网页对话框
Apr 24 Javascript
javascript 实现map集合
Apr 03 Javascript
基于Bootstrap重置输入框内容按钮插件
May 12 Javascript
js获取当前年月日-YYYYmmDD格式的实现代码
Jun 01 Javascript
jquery checkbox的相关操作总结
Oct 17 Javascript
简单实现Vue的observer和watcher
Dec 21 Javascript
Angular.JS利用ng-disabled属性和ng-model实现禁用button效果
Apr 05 Javascript
laravel5.4+vue+element简单搭建的示例代码
Aug 29 Javascript
bootstrap table支持高度百分比的实例代码
Feb 28 Javascript
解决layui下拉框监听问题(监听不到值的变化)
Sep 28 Javascript
解决vue项目运行提示Warnings while compiling.警告的问题
Sep 18 Javascript
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
php中截取中文字符串的代码小结
2011/07/17 PHP
phpcms实现验证码替换及phpcms实现全站搜索功能教程详解
2017/12/13 PHP
js 判断浏览器类型 去全角、半角空格 自动关闭当前窗口
2009/04/10 Javascript
JQuery的Alert消息框插件使用介绍
2010/10/09 Javascript
火狐4、谷歌12不支持Jquery Validator的解决方法分享
2011/06/20 Javascript
JS面向对象编程浅析
2011/08/28 Javascript
js利用事件的阻止冒泡实现点击空白模态框的隐藏
2014/01/24 Javascript
用js传递value默认值的示例代码
2014/09/11 Javascript
JavaScript实现瀑布流布局
2020/06/28 Javascript
nodeJs爬虫获取数据简单实现代码
2016/03/29 NodeJs
JS实现登录页面记住密码和enter键登录方法推荐
2016/05/10 Javascript
JavaScript中setTimeout的那些事儿
2016/11/14 Javascript
javascript中href和replace的比较(详解)
2016/11/25 Javascript
详解JavaScript树结构
2017/01/09 Javascript
JS中去掉array中重复元素的方法
2017/05/26 Javascript
JavaScript与Java正则表达式写法的区别介绍
2017/08/15 Javascript
基于jquery实现五星好评
2017/11/18 jQuery
vue2.0父子组件间传递数据的方法
2018/08/16 Javascript
JS+CSS+HTML实现“代码雨”类似黑客帝国文字下落效果
2020/03/17 Javascript
[49:42]DOTA2上海特级锦标赛主赛事日 - 3 胜者组第二轮#2Secret VS EG第一局
2016/03/04 DOTA
深入理解Python中装饰器的用法
2016/06/28 Python
python XlsxWriter模块创建aexcel表格的实例讲解
2018/05/03 Python
Python序列化与反序列化pickle用法实例
2019/11/11 Python
Python直接赋值及深浅拷贝原理详解
2020/09/05 Python
three.js模拟实现太阳系行星体系功能
2019/09/03 HTML / CSS
车库门开启器、遥控器和零件:Chamberlain
2019/04/09 全球购物
英国最大的独立摄影零售商:Park Cameras
2019/11/27 全球购物
Ray-Ban雷朋奥地利官网:全球领先的太阳眼镜品牌
2020/10/12 全球购物
北承题目(C++)
2012/05/16 面试题
机关党员2014全国两会学习心得体会
2014/03/10 职场文书
大学新闻系应届生求职信
2014/06/02 职场文书
国际政治学专业推荐信
2014/09/26 职场文书
有限责任公司股东合作协议书
2014/12/02 职场文书
关于分班的感言
2015/08/04 职场文书
简单总结SpringMVC拦截器的使用方法
2021/06/28 Java/Android
大脑的记忆过程在做数据压缩,不同图形也有共同的记忆格式
2022/04/29 数码科技