来一条node爬虫

作者: 姜治宇 | 来源:发表于2020-12-19 13:35 被阅读0次

用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();
        })
    })
}

相关文章

  • 来一条node爬虫

    用node写个爬虫真的肥肠煎蛋,今天就来玩一下。 物料 首先准备物料。 我们需要准备两个第三方包,一个是axios...

  • [译]Node Crawler:强大的Node开源爬虫

    自从Node横空出世后,很快有人就用它来开发爬虫,网上也常见Node爬虫教程。然而,很难看到一个通用的、功能丰富的...

  • node爬虫之路(一)

    最近对爬虫很感兴趣,我们node.js也是可以写爬虫。所以写一个node爬虫系列,记录我的爬虫之路,感兴趣的同学可...

  • node爬虫快速入门

    node爬虫 初入前端,刚刚接触node,对于耳闻已久的node爬虫非常神往,所以有了这篇文章,项目代码在文章末尾...

  • node入门场景之——爬虫

    边做边学效率更高,爬虫是node的适用场景之一,关于爬虫的另一篇文章node爬虫进阶之——登录为了验证“经验总结、...

  • node爬虫之天猫数据(一)

    说起爬虫,最开始想到的就是python scrapy库,但现在我们用node来写一个爬虫来爬天猫数据首先创建一个目...

  • node 爬虫

    clawer.js

  • node爬虫

    node爬虫用到的第三方模块 Cheerio 服务端的jQueryhttps://segmentfault.c...

  • Node爬虫

    使用cheerio爬虫模块抓取页面后获取元素信息跟jQuery基本一样

  • node爬虫

    /** 教程:https://blog.csdn.net/Qc1998/article/details/83154...

网友评论

    本文标题:来一条node爬虫

    本文链接:https://www.haomeiwen.com/subject/ubiqnktx.html