Node 实现爬虫 ?
前言:
- 什么是爬虫 ?
通过模拟浏览器的请求,服务器就会对根据我们的请求返回我们想要的数据。将数据解析出来,并且进行保存。
-
爬虫一般步骤 ?
目标:确定你想要获取的数据
确定你想要获取的数据在那个页面(一般详细的数据会在详情页)。
确定在哪些页面可以链接到这些页面(一般分类列表页面会有详情页的链接数据)。
寻找页面之间数据之间的规律 。
分析页面:
获取数据的方式(正则,或者 cheerio 库)。
数据是通过 ajax 请求的数据,还是 html 自带的数据 。
如果是通过 ajax 请求的数据,需要分析这个 ajax 请求的链接是什么链接,一般请求的数据都为 json 格式的数据,会比较容易解析 。
如果数据在 html 里面,就用 cheerio 通过选择器将内容选中 。
编写单个数据获取的案例:(多个循环)
解析出分类页的链接地址 。
解析出列表页的链接地址 。
解析出详情页的链接地址 。
解析详情页里面想要获取的数据 。
将数据保存起来(本地文件 / 数据库)。
如果遇到阻碍,进行对反爬虫对抗:
User-Agent 是否是正常浏览器的信息,将请求头设置成跟浏览器一样的内容 。
因为爬虫爬取速度过快,会导致封号(IP),可以降低速度进行解决。或者可以使用代理进行解决 。
设置验证码,只有通过人为的验证码后,才将凭证给到你。就需要浏览器的真实操作,可以使用*浏览器帮助。
-
请求数据的库 ?
axios 。
request 。
puppetter *浏览器(完全模拟浏览器)。
- Node 通过正则爬取电影数据:
// 0,请求数据方法封装
let request = require('request')
function req(path) {
return new Promise(function(resolve, reject) {
request.get(path, function(err, response, body) {
if (err) {
reject(err)
} else {
resolve({ response, body })
}
})
})
}
// 1,获取起始页的所有分类
let httpUrl = 'https://www.1905.com/vod/list/n_1_t_1/o3p1.html'
async function getClassUrl() {
// 请求拿到爬取数据
let { response, body } = await req(httpUrl)
//console.log(body)
// 解析正文内容
let reg = /<span class="search-index-L">类型(.*?)<div class="grid-12x">/igs
let result = reg.exec(body)[1]
let reg1 = /<a href="javascript\:void\(0\);" onclick="location\.href='(.*?)';return false;" >(.*?)<\/a>/igs
var res;
while (res = reg1.exec(result)) {
getMovies(res[1], res[2])
}
}
getClassUrl()
// 2,获取分类里的电影链接
async function getMovies(url, movieType) {
let { response, body } = await req(url)
let reg = /<a class="pic-pack-outer" target="_blank" href="(.*?)" .*?><img/igs
let arrList = []
var res;
while (res = reg.exec(body)) {
arrList.push(res[1])
getInfo(res[1])
}
//console.log(movieType, arrList)
}
// 3,根据电影链接获取电影的详细信息
async function getInfo(url) {
let { response, body } = await req(url)
console.log('--', body)
let reg = /<span id="playerBoxIntroCon" class="active">(.*?)<a/igs
//console.log('--', reg.exec(body))
var res;
let arrList = []
while (res = reg.exec(body)) {
arrList.push(res[1])
}
//console.log(arrList)
}
- Node 通过 cheerio 库爬取表情包:
// Node 爬取表情包
// cheerio 是 jquery 核心功能的一个快速灵活而又简单的实现,主要是为了在服务器端需要对DOM元素进行操作的地方
// cheerio 是 node.js 的提取页面模块,为服务器特别定制
// 1,安装 npm i cheerio
// 2, 导入 require('cheerio)
const axios = require('axios')
const cheerio = require('cheerio')
const fs = require('fs')
const path = require('path')
let httpUrl = 'https://www.doutula.com/article/list/?page=1'
axios.get(httpUrl).then((res) => {
// cheerio 解析 html
let $ = cheerio.load(res.data)
$('#home .col-sm-9>a').each((index,item) => {
let pageUrl = $(item).attr('href')
parsePage(pageUrl,index)
})
})
async function parsePage(url,index){
let res = await axios.get(url)
let $ = cheerio.load(res.data)
$('.pic-content img').each((i,item) => {
let imgUrl = $(item).attr('src')
let extName = path.extname(imgUrl)
let imgPath = './img/pic-'+index+'-'+'M'+'-'+i+extName
// 创建写入图片流
let ws = fs.createWriteStream(imgPath)
axios.get(imgUrl,{responseType:'stream'}).then((res) => {
res.data.pipe(ws)
res.data.on('close',()=>{
ws.close()
})
})
})
}
- Node 通过 cheerio 库爬取音乐:
// cheerio 爬取音乐
// 目标:下载音乐
// 1,获取音乐相关的信息,通过音乐相关的信息获取 mp3 列表
let axios = require('axios')
let cheerio = require('cheerio')
let fs = require('fs')
// 选定的网页路径
let httpUrl = 'https://www.xiami.com/list?page=1&query=%7B%22dataType%22%3A%22recommend%22%7D&scene=main&type=collect'
// 拿到总的页面数据
axios.get(httpUrl).then((res) => {
// 页面数据解析
let $ = cheerio.load(res.data)
let obj = {}
$('.adaptive-list>.collect-item').each((index,item) => {
let mp3Url = 'https://ww.xiami.com'+$(item).find('.wrapper>a').attr('href')
let name = $(item).find('.info>.name>a').text()
let author = $(item).find('.info>.author>a').text()
obj.url = mp3Url
obj.name = name
obj.author = author
console.log('数据:',obj)
// 具体的文件下载保存
downLoad(obj)
})
})
// 文件下载,保存到指定目录下操作
function downLoad (obj){
axios.get(obj.url,{responseType:'stream'}).then(function(res){
// 写入流方式进行
let ws = fs.createWriteStream('./mp3/'+obj.name+'.mp3')
res.data.pipe(ws)
})
}
- Node 对于 puppeteer 库的基础使用:
// puppeteer 库的使用(无头浏览器)
let puppeteer = require('puppeteer')
// 打开浏览器
async function test(){
// puppeteer.launch(options) 实例开启浏览器
// 可以传入一个options对象,配置为*面浏览器(性能更高,更快),也可以配置为有界面浏览器(一般用于调试开发)
let options = {
// 设置为有界面,为true,为*面
headless:false,
// 设置视窗的宽高
defaultViewport:{
width:1200,
height:800
}
}
let browser = await puppeteer.launch(options)
// 打开页面,返回新的页面对象
let page = await browser.newPage()
// 访问页面
await page.goto('https://www.dytt8.net/index.htm')
// 截屏
await page.screenshot({path:'screenshot.png'})
// 获取页面内容
page.$$eval('#menu li a',(elements) => {
elements.forEach((item,index) => {
console.log(item.innerHTML)
})
})
page.on('console',(e) => {
console.log(e)
})
}
test()
- Node 通过 puppeteer 库爬取电子书:
let puppeteer = require('puppeteer')
let axios = require('axios')
let url = require('url')
let httpUrl = 'https://sobooks.cc/';
(async function(){
// 开发测试阶段浏览器配置
let debugOptions = {
// 设置视窗的宽高
defaultViewport:{
width:1200,
height:800
},
// 设置为有界面,如果为true,即为*面
headless:false,
// 设置放慢每个步骤的毫秒数
slowMo:250
}
// 无头浏览器配置,效率更高(开发完后使用)
let options = {
headless:true
}
// 开启一个浏览器
let browser = await puppeteer.launch(debugOptions)
// 目标: 获取https://sobooks.cc/ 所有书名和电子书的链接
// 进入网站,获取整个网站列表页的页数
async function getAllNum(){
let page = await browser.newPage()
// 截取谷歌请求
await page.setRequestInterception(true)
// 监听请求事件,并对请求进行拦截
page.on('request',interceptedRequest => {
// 通过url模块对请求的地址进行解析
let urlObj = url.parse(interceptedRequest.url())
if(urlObj.hostname == "googleads.g.doubleclick.net"){
// 如果是谷歌的广告请求,放弃此次请求
interceptedRequest.abort()
}else{
interceptedRequest.continue()
}
})
await page.goto(httpUrl)
// 设置选择器,获取总页数
let pageNum = await page.$eval('.pagination li:last-child span',(element) => {
let text = element.innerHTML
text = text.substring(1,text.length-2).trim()
return text
})
page.close()
return pageNum
}
let pageNum = await getAllNum()
// 获取列表页的所有链接
async function pageList(num){
let pageListUrl = 'https://sobooks.cc/page/'+num
let page = await browser.newPage()
// 截取谷歌请求
await page.setRequestInterception(true)
// 监听请求事件,并对请求进行拦截
page.on('request',interceptedRequest => {
// 通过url模块对请求的地址进行解析
let urlObj = url.parse(interceptedRequest.url())
if(urlObj.hostname == "googleads.g.doubleclick.net"){
// 如果是谷歌的广告请求,放弃此次请求
interceptedRequest.abort()
}else{
interceptedRequest.continue()
}
})
await page.goto(pageListUrl)
// $eval 找一个符合条件的元素,$$eval 找所有符合条件的元素
let arrList = await page.$$eval('.card .card-item .thumb-img>a',(elements) => {
let arr = []
elements.forEach((element,index) => {
var obj = {
href:element.getAttribute('href'),
title:element.getAttribute('title')
}
arr.push(obj)
})
return arr
})
page.close()
// 通过获取的数组的地址和标题去请求书籍的详情页
arrList.forEach((pageObj,i) => {
//getPageInfo(pageObj)
})
//return arrList
}
let pageArr = await pageList(1)
//console.log('pageArr',pageArr)
// 进入每个电子书的详情页获取下载电子书的网盘地址
async function getPageInfo(pageObj){
let page = await browser.newPage()
// 截取谷歌请求
await page.setRequestInterception(true)
// 监听请求事件,并对请求进行拦截
page.on('request',interceptedRequest => {
// 通过url模块对请求的地址进行解析
let urlObj = url.parse(interceptedRequest.url())
if(urlObj.hostname == "googleads.g.doubleclick.net"){
// 如果是谷歌的广告请求,放弃此次请求
interceptedRequest.abort()
}else{
interceptedRequest.continue()
}
})
await page.goto(pageObj.href)
//$() 只能获取属性,不能获取文本
let eleA = await page.$('.dltable tr:nth-child(3) a:last-child')
let aHref = await eleA.getProperty('href')
aHref = aHref._remoteObject.value
console.log('aHref',aHref)
}
getPageInfo({href:"https://sobooks.cc/books/14620.html"})
//
})()
总结:使用 正则,分析好页面结构,基本的数据都能爬取的到,但是使用它爬取数据过程比较比较繁琐。个人比较 推荐 使用 cheerio 库 去做数据的爬取,它用法简单,和 jQuery 用法一致,对于熟悉前端开发的同学而言,基本没有新的学习成本,也更好理解。但是也存在局限性,比如一些网站有较强的反爬虫机制,可以考虑使用 puppeteer 库 去操作。它的本质原理,就是模拟浏览器去请求页面,然后在进行数据的爬取,具体使用细节可以去查看对应的官方文档。