nodejs+puppeteer+chromium爬取异步数据页面(英雄联盟英雄资料列表页+详情页)

puppeteer介绍

对于静态页面的爬取是灰常简单的,一个request+cherrico即可,今天我动手对英雄联盟官网英雄资料爬取时发现英雄列表和详情页是通过js异步渲染的数据,所以就用上了这个神器puppeteer

Puppeteer能够模拟一个浏览器的运行环境,能够请求网站信息,并运行网站内部的逻辑。然后再通过WS协议动态的获取页面内部的数据,并能够进行任何模拟的操作(点击、滑动、hover等),并且支持跳转页面,多页面管理。甚至能注入node上的脚本到浏览器内部环境运行,总之,你能对一个网页做的操作它都能做,你不能做的它也能做。

 

环境和安装

Puppeteer 至少需要 Node v6.4.0,如要使用 async / await,只有 Node v7.6.0 或更高版本才支持。 node下载地址: https://nodejs.org/zh-cn/

装puppeteer: yarn add puppeteer --ignore-scripts 或者 npm i --save puppeteer --ignore-scripts,为什么要加--ignore-scripts?因为puppeteer默认是会下载Chromium,直接下载Chromium会失败,加了--ignore-scripts 会跳过这一步

所以需要单独下载Chromium,下载地址:https://download-chromium.appspot.com/ ,没有翻墙工具的话点这里下载,还是很贴心噶^_^

把下载刚刚下载的文件解压备用

代码

const chalk = require('chalk')
count fs = require("fs")
const devices = require('puppeteer/DeviceDescriptors')//用来更改puppeteer访问设备
const puppeteer = require('puppeteer');

puppeteer.launch({
	headless: false, //不使用无头模式使用本地可视化
	executablePath: "C:/Users/Administrator/Desktop/express-requetsAPI--master/chrome-win/chrome.exe", //因为是yarn add puppeteer --ignore-scripts没有安装chromium,需要制定本地chromium的chrome.exe路径所在,刚才下载后解压后的全路径
	//设置超时时间
	timeout: 15000,
	//如果是访问https页面 此属性会忽略https错误
	ignoreHTTPSErrors: true,
	// 打开开发者工具, 当此值为true时, headless总为false
	devtools: true,
}).then(async browser => {
	function formatProgress (current, TOTAL_PAGE) { 
		let percent = (current / TOTAL_PAGE) * 100
		let done = ~~(current / TOTAL_PAGE * 40)
		let left = 40 - done
		let str = `英雄个数:${TOTAL_PAGE} 当前进度:[${''.padStart(done, '=')}${''.padStart(left, '-')}] 已完成${Math.round(percent*100) / 100}%` //padStart es7
		return str
	}
	const page = await browser.newPage()
	page.on('console', msg => {
		if (typeof msg === 'object')
			console.dir(msg)
		else
			console.log(chalk.blue(msg))
	})
	await page.setViewport({width: 1920, height: 1080});
	// await page.emulate(devices['iPhone X'])
	await page.goto('http://lol.qq.com/data/info-heros.shtml', { waitUntil: "networkidle2" })
	await page.waitFor(1000)
	// console.log(await page.$eval('#jSearchHeroDiv li a', el => el.href))
	let heros = await page.$$('#jSearchHeroDiv li a')
	let re = ""
	for(let i = 0; i < heros.length; i++){
		// if(i==5) break;
		await page.goto('http://lol.qq.com/data/info-heros.shtml', { waitUntil: "networkidle2" })
		await page.waitFor(2000)
		heros = await page.$$('#jSearchHeroDiv li a')
		await heros[i].click()
		await page.waitFor(3000)
		await handleData(i, heros.length)
		console.clear()
		console.log(chalk.yellow(formatProgress(i, heros.length)))
		console.log(chalk.yellow('第'+(i+1)+'个英雄数据加载完毕'))
		await page.waitFor(2000)
	}
	async function handleData(i, len) {
		let result = await page.evaluate(() => {
			let data = {}
			data.DATAname = document.querySelector('#DATAname').innerText
			data.DATAtitle = document.querySelector('#DATAtitle').innerText
			let tags = document.querySelectorAll('#DATAtags span')
			data.DATAtags = Array.from(tags).map(v => v.innerText)
			let keys = document.querySelectorAll('#DATAinfo dt')
			let values = document.querySelectorAll('#DATAinfo dd i')
			data.DATAinfo = []
			keys.forEach((v, i) => {
				data.DATAinfo.push({type: v.innerHTML, value: values[i].style.width})
			})
			return data
		})
		re += JSON.stringify(result) + (i == len-1 ? "" : ",\n")
	}

	let json = "[\n"+re+"\n]"
	console.log(chalk.green("所有数据抓取完毕:\n", json))
	fs.writeFile('heros_detail.txt', json, 'utf8', function(error){
		if(error){
				console.log(chalk.green(error));
				return false;
		}
		console.log(chalk.blue('数据写入文件成功!'));
	})

	// await page.screenshot({
	// 	path: './screenshot-x.png',
	// 	type: 'png',
	// 	// quality: 100, 只对jpg有效
	// 	fullPage: true,
	// 	// 指定区域截图,clip和fullPage两者只能设置一个
	// 	// clip: {
	// 	//   x: 0,
	// 	//   y: 0,
	// 	//   width: 1000,
	// 	//   height: 40
	// 	// }
	// });
	await browser.close()
}).catch(err => console.log(err))

以上代码实现思路:遍历列表页英雄总数 -> 获取每个被点击进入详情页的dom或url -> 遍历访问每个详情页 -> 写一个处理每个详情页的函数 -> 处理完每个页面之后控制台打印当前进度并将每个处理后的数据对象转成json拼接为字符串,最后处理成合法json格式的数组 -> 创建txt文件并写入结果数据 或者 写入数据库

如果需要还详细的数据,比如皮肤、英雄背景、技能介绍等,在handleData函数中处理即可
常用api:

// page.type 获取输入框焦点并输入文字

// page.click()/ElemetHandle.click() 点击一个元素

// page.keyboard.press 模拟键盘按下某个按键,目前mac上组合键无效为已知bug

// page.waitFor 页面等待,可以是时间、某个元素、某个函数

// page.frames() 获取当前页面所有的 iframe,然后根据 iframe 的名字精确获取某个想要的 iframe

// page.evaluate() 在浏览器中执行函数,相当于在控制台中执行函数,返回一个 Promise

// Array.from 将类数组对象转化为对象

// page.$$(selector) 获取一组元素,底层调用的是 document.querySelectorAll(). 返回 Promise(Array(ElemetHandle)) 元素数组.
// page.$(selector) 获取单个元素,底层是调用的是 document.querySelector() , 所以选择器的 selector 格式遵循 css 选择器规范
// page.$eval("#ele", el => el.innerHTML) 相当于在 iframe 中运行 document.queryselector 获取指定元素,并将其作为第一个参数传递
// page.$$eval(".ele", els => els.map(el => el.innerHTML)) 相当于在 iframe 中运行 document.querySelectorAll 获取指定元素数组,并将其作为第一个参数传递

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章