博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
通过爬妹子图片来学习async/await
阅读量:5894 次
发布时间:2019-06-19

本文共 4985 字,大约阅读时间需要 16 分钟。

hot3.png

async/await是什么?

js一个重大的特点就是异步,但是人类的思维模式天然是同步的。所以要把异步和同步的流程化统一起来,就会有回调中再回调的情况出现。当业务逻辑复杂的时候,回调的嵌套过多,代码复杂度增加,可读性降低,维护起来也复杂,调试也复杂,这就是回调地狱。前端界一直在不遗余力的解决这个问题,于是衍生出了非常多的解决方案:jquery的deferred、es6中的promise/generator、rx系列的rxjs、以及es7的async/await。

爬虫中异步场景非常的多,所以必须选择一些解决方案。由于我使用的node版本为7.7,天生支持async/await,所以就采用了async/await,顺便学习下验证下async/await。

下面借用mdn上的例子来简单介绍一下async/await的用法:

function resolveAfter2Seconds(x) {  return new Promise(resolve => {    setTimeout(() => {      resolve(x);    }, 2000);  });};var add = async function(x) {  var a = await resolveAfter2Seconds(20);  var b = await resolveAfter2Seconds(30);  return x + a + b;};add(10).then(v => {  console.log(v);  // prints 60 after 4 seconds.});

简单来说就是,在申明的一个async函数中可以进行对异步函数(返回一个Promise对象)使用await操作符,在异步函数resolve之后再执行下一步。

爬虫开始

首先爬虫采用的第三方模块有request、cheerio,爬取的网站为

先引入相关模块,定义爬取的总页数,创建存放图片的文件夹:

const fs = require('fs');const path = require('path');const request = require('request');const cheerio = require('cheerio');let sumPage = 141; // 定义总页数let baseFolder = path.join(__dirname, '/pics');if(!fs.existsSync(baseFolder)) {    fs.mkdirSync(baseFolder);}

然后写一个函数来获取当前页中的所有封面的链接:

// 获取当前页的所有的封面urlfunction getCoverUrlsByPage(currentPage) {    let url = 'http://www.mzitu.com/page/' + currentPage;    let options = {        url: url,        headers: {            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'        }    };    return new Promise((resolve, reject) => {        request(options, (err, res, body) => {            if(err) {                reject(err);            }            let $ = cheerio.load(body, {decodeEntities: false});            let coverUrls = [];            $('#pins li > a').each((i, item) => {                coverUrls.push({                    url: $(item).attr('href'),                    title: $(item).next().find('a').text()                });            });            resolve(coverUrls);        })    }) }

由于每个封面下有很多张图片,而每一张图片展示在了一个单独的页面中,所以得获取到所有图片的链接,然后在去链接中爬去到对应图片的地址

function getPicSrcs(href) {    let options = {        url: href,        headers: {            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'        }    };    return new Promise((resolve, reject) => {        request(options, async (err, res, body) => {            if(err) {                reject(err);            }            let $ = cheerio.load(body, {decodeEntities: false});            let pages = $('.pagenavi a').eq(-2).text();            let srcs = [];            for(let i = 1; i <= pages; i++) {                let src = await getPicSrc(href + '/' + i);                srcs.push(src)            }            resolve(srcs);        })    });}function getPicSrc(href) {    let options = {        url: href,        headers: {            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'        }    };    return new Promise((resolve, reject) => {        request(options, (err, res, body) => {            if(err) {                reject(err);            }            let $ = cheerio.load(body, {decodeEntities: false});            let src = $('.main-image img').attr('src');            resolve(src);        })    })}

这样我们就获取到了所有的图片的地址,下面就是下载图片:

// 下载整个封面中的图片async function downloadPics(srcs, folderName) {    for(let i = 0; i < srcs.length; i++) {        let filename = './pics/' + folderName + '/' + (i + 1) + '.jpg';        await downloadPic(srcs[i], filename);    }}// 下载单张图片function downloadPic(src, filename) {    return new Promise((resolve, reject) => {        var stream = fs.createWriteStream(filename);        request(src).pipe(stream).on('close', () => {            resolve();        })    })}

在下载图片之前,我们希望把每个封面的图片单独放在一个以封面名命名的文件夹中,所以还要创建文件夹

function createFolder(name) {    let new_name = name.replace(/[\\/:*?"<>|]/g, '');    let filepath = path.join(baseFolder, new_name);    if(!fs.existsSync(filepath)) {        fs.mkdirSync(filepath);    }    return new_name;}

最后我们需要把这些函数全部组织在一起:

async function run(currentPage=1) {    console.log('开始下载第' + currentPage + '页');    let coverUrls = await getCoverUrlsByPage(currentPage); // 获取到当前页的所有链接    for(let i = 0; i < coverUrls.length; i++) {        let item = coverUrls[i];        let folderName = createFolder(item.title); // 创建当前文件夹        // 获取当前封面下的所有图片        let picUrls = await getPicSrcs(item.url);        downloadPics(picUrls, folderName);        console.log('【' + item.title + '】下载完毕!')    }    console.log('第' + currentPage + '页下载完毕');    console.log('-------------------------------------');    if(currentPage < sumPage) {        run(++currentPage);    }}

最后只要执行run函数即可:

run();

问题

爬虫爬去到第二页时就会报错,猜测是网站限制爬虫的一些策略导致的(这种网站爬的人肯定特别多),由于只是随便写写,目的是验证下async/await,就没有纠结这个问题,以后如果有兴趣的话再进行改进。


更新:程序运行过程中经常会报两个异常,一个是连接超时,一个是cheerio解析错误,导致程序中断。因此在涉及这两个问题的地方给程序加上异常处理之后,程序就能够完美运行了~

完整代码可见

转载于:https://my.oschina.net/prettypice/blog/874709

你可能感兴趣的文章
将Java应用部署到SAP云平台neo环境的两种方式
查看>>
==与equal的区别
查看>>
hduoj1091A+B for Input-Output Practice (III)
查看>>
数据批量导入Oracle数据库
查看>>
C#开源项目介绍
查看>>
调用lumisoft组件发邮件 不需要身份验证 不需要密码
查看>>
DW 正则
查看>>
【ios6.0 自学瞎折腾】(三)表示图填充数据
查看>>
清理(委托类型实例)事件处理(实例)的函数及Lambda表达式
查看>>
Create UML diagrams online in seconds, no special tools needed.yUML
查看>>
Java中的反射机制(四) 利用反射访问私有
查看>>
抓屏原理
查看>>
Crash/Instance Recovery与Media Recovery的本质区别
查看>>
【组成原理】——运算器
查看>>
ASP.NET Dynamic Data Part.2(自定义动态数据网站)
查看>>
《Effective Java》读书笔记09--谨慎地覆盖clone方法
查看>>
AMF Message 及 AMF3 OBJECT 对象格式
查看>>
类对象Java设计模式之十八(中介者模式)
查看>>
Qt 写文件失败
查看>>
Gridview控件导出Excel之后图片无法显示
查看>>