来一条node爬虫

用node写个爬虫真的肥肠煎蛋,今天就来玩一下。

物料

首先准备物料。

cnpm i axios
cnpm i cheerio

我们需要准备两个第三方包,一个是axios,用来发送请求的,当然request包也行,看个人喜好了;另一个是cheerio,这货是用来解析dom的,跟jquery的用法一样一样的。

爬虫的实现思路

也就是说,我们通过axios请求过来的html的标签数据,然后用cheerio包来提取我们需要的内容,紧接着我们可以用fs包的流来读取数据,然后写入我们的磁盘,这就是一个完整的爬虫需要做的事情。

预热

先预热一下。

const axios = require('axios');
const cheerio = require('cheerio');

let httpUrl = 'http://www.adoutu.com/picture/list/1';

axios.get(httpUrl).then(res=>{
    // console.log(res.data); //获取数据
    let $ = cheerio.load(res.data); //将dom节点导入cheerio解析
    $('.list-group .list-group-item a').each((i,ele)=>{ // 遍历dom节点
        let url = $(ele).attr('href'); //获取a标签的href属性信息
        console.log(url);
    })
})

实现

预热完毕,开始干活。

const axios = require('axios');
const cheerio = require('cheerio');
const fs = require('fs');
const path = require('path');
let httpUrl = 'http://www.adoutu.com/picture/list/1';

axios.get(httpUrl).then(res=>{
    // console.log(res.data); //获取数据
    let $ = cheerio.load(res.data);
    $('.list-group .list-group-item a').each((i,ele)=>{ // 遍历dom节点
        let aUrl = $(ele).attr('href'); //获取a标签的href属性信息
        
        parsePage('http://www.adoutu.com' + aUrl);
    })
})

async function parsePage(url) {
    let res = await axios.get(url);
    let $ = cheerio.load(res.data);
    let imgUrl = $('.detail-picture img').attr('src');
   
    let urlObj = path.parse(imgUrl);
    let ws = fs.createWriteStream(`./img/${urlObj['name']}${urlObj['ext']}`);
    axios.get(imgUrl,{responseType:'stream'}).then(res=>{ //指定获取二进制流
        res.data.pipe(ws);
        res.data.on('close',()=>{
            ws.close();
        })
    })
}

分页

如果有分页,我们可以先获取分页总数,然后循环发请求即可。

const axios = require('axios');
const cheerio = require('cheerio');
const fs = require('fs');
const path = require('path');

let httpUrl = 'http://www.adoutu.com/picture/list/1';

spider();
//循环发起请求
async function spider() {
    let page = await getNum(); 
    for (let i = 1; i <= page; i++) {
        if(i < 5) // 如果page太大,会存在崩溃问题
            getData(i)
        else 
            break;    

    }
}
//获取每页数据
async function getData(page) {
    let url = 'http://www.adoutu.com/picture/list/' + page;
    console.log(url);
    let res = await axios.get(url);
    let $ = cheerio.load(res.data);

    $('.list-group .list-group-item a').each((i, ele) => { // 遍历dom节点
        let aUrl = $(ele).attr('href'); //获取a标签的href属性信息
        // console.log(aUrl);
        parsePage('http://www.adoutu.com' + aUrl);
    })

}
// 获取分页数
async function getNum() {
    let res = await axios.get(httpUrl);
    let $ = cheerio.load(res.data);

    let count = $('.pagination li').length;
    // console.log(count);
    let pageNum = $('.pagination li').eq(count - 2).find('a').text();
    // console.log(pageNum);
    return pageNum;
}
//解析并存储数据
async function parsePage(url) {
    let res = await axios.get(url);
    let $ = cheerio.load(res.data);
    let imgUrl = $('.detail-picture img').attr('src');

    let urlObj = path.parse(imgUrl);
    let ws = fs.createWriteStream(`./img/${urlObj['name']}${urlObj['ext']}`);
    axios.get(imgUrl, { responseType: 'stream' }).then(res => {
        res.data.pipe(ws);
        res.data.on('close', () => {
            ws.close();
        })
    })
}

延迟执行

上面我们发现了一个问题,就是分页过多的情况下,程序就会报错,为什么呢?
因为我们一次性发送的请求太多了,for循环是同步执行的,刷的一下发那么多请求,不挂才怪。怎么办呢?
这问题能难倒你吗?让他延迟执行呗。
不过请注意,这个延迟执行也没那么简单,比如这么写:

async function spider() {
    let page = await getNum(); 
    for (let i = 1; i <= page; i++) {
       setTimeout(()=>{
        getData(i)  
       },2000); 

    }
}
...

这么写是不行的,因为这就等于你在事件队列中创建了n个getData()函数,然后等待2s后执行这n个getData()函数,其实还是同时执行的。
那怎么搞?
我们可以让请求拉开距离执行:

async function spider() {
    let page = await getNum(); 
    for (let i = 1; i <= page; i++) {
       setTimeout(()=>{
        getData(i)  
       },2000*i); 

    }
}

这样写还是在事件队列建立了n个getData(),不过他们的执行时间不一致了,分别是等待2s、4s…依次类推的执行,这样就拉开了请求之间的距离。
这个等待场景还是蛮常用的哈,写那么多定时器还是很烦人的哈,我们可以将这个逻辑用promise封装起来。

//等待
function sleep(time){
    var timer;
    return new Promise((resolve,reject)=>{
        timer = setTimeout(()=>{
            clearTimeout(timer);
            resolve('请求延迟'+time)
        },time)
    })
}

然后我们利用这个等待函数就可以写出比较优雅的代码了:

const axios = require('axios');
const cheerio = require('cheerio');
const fs = require('fs');
const path = require('path');

let httpUrl = 'http://www.adoutu.com/picture/list/1';

spider();
//循环发起请求
async function spider() {
    let page = await getNum(); 
    for (let i = 1; i <= page; i++) {
        await sleep(2000*i) //每个请求等待2s、4s....后执行
        getData(i) 

    }
}
//等待
function sleep(time){
    var timer;
    return new Promise((resolve,reject)=>{
        timer = setTimeout(()=>{
            clearTimeout(timer);
            resolve('请求延迟'+time)
        },time)
    })
}
//获取每页数据
async function getData(page) {
    let url = 'http://www.adoutu.com/picture/list/' + page;
    console.log(url);
    let res = await axios.get(url);
    let $ = cheerio.load(res.data);

    $('.list-group .list-group-item a').each(async (i, ele) => { // 遍历dom节点
        let aUrl = $(ele).attr('href'); //获取a标签的href属性信息
        // console.log(aUrl);
        await parsePage('http://www.adoutu.com' + aUrl);
    })

}
// 获取分页数
async function getNum() {
    let res = await axios.get(httpUrl);
    let $ = cheerio.load(res.data);

    let count = $('.pagination li').length;
    // console.log(count);
    let pageNum = $('.pagination li').eq(count - 2).find('a').text();
    // console.log(pageNum);
    return pageNum;
}
//解析并存储数据
async function parsePage(url) {
    let res = await axios.get(url);
    let $ = cheerio.load(res.data);
    let imgUrl = $('.detail-picture img').attr('src');

    let urlObj = path.parse(imgUrl);
    let ws = fs.createWriteStream(`./img/${urlObj['name']}${urlObj['ext']}`);
    axios.get(imgUrl, { responseType: 'stream' }).then(res => {
        res.data.pipe(ws);
        res.data.on('close', () => {
            ws.close();
        })
    })
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值