zoukankan      html  css  js  c++  java
  • Node异步爬虫引出的异步流程控制的一些问题

    前记:
    想写一个电影天堂的爬虫,因为node很长时间落下,就想用node去写一下。结果遇到了一些列的问题,这些问题归根到底就是异步流程控制的问题,在以前就一直会接触到很多回调地狱,Promise为什么会出现诸如此类的话题,现在终于是深刻体会到了!

    开始的代码是:

    const cheerio = require('cheerio');
    const http = require('http');
    const iconv = require('iconv-lite');
    
    let baseUrl = "http://www.ygdy8.net/html/gndy/dyzz/list_23_";
    let Host = "http://www.ygdy8.net/";
    let titleHref = [];
    const totalPage = 1; //指定爬多少页数据
    let res = [];
    //获取页面电影数据
    function getTitleHref(url,page) {
      let startUrl = url+page+".html";
      http.get(startUrl,function(res) {
        let chunks = [];
    
        res.on('data',function(chunk){
          chunks.push(chunk);
        });
        res.on('end',function(){
          let title = [];
          let html = iconv.decode(Buffer.concat(chunks),'gb2312');
          let $ = cheerio.load(html, {decodeEntities: false});
          // console.log($);
          $('.co_content8 .ulink').each(function(i,d) {
            let $d = $(d);
            titleHref.push({
              href: $d.attr('href')
            });
          });
          console.log(titleHref);
        });
        if(page <= totalPage) {
          getTitleHref(url,++page);
        }else {
          console.log(page);
          getLink(titleHref);
        }
       
      });
    }
    
    //获取种子链接
    function getLink(titleHref) {
      console.log('进入getLink');
    
      titleHref.forEach(function(v,k) {
        console.log('~~~~~~~~~~~~~~~~~~~~');
        let infoUrl = Host + v.href;
        console.log(infoUrl);
        // try {
          http.get(infoUrl,function(res) {
            console.log('进入getlink http');
            
            let chunks = [];
            res.on('data',function(chunk) {
              chunks.push(chunk);
            });
            res.on('end', function(){
              let html = iconv.decode(Buffer.concat(chunks),'gb2312');
              let $ = cheerio.load(html, {decodeEntities: false});
              
              
              let reg = /.*译  名/;
              let info = '';
              let bt = '';
              let textInfo = $('.co_content8 #Zoom p').eq(0).text();
              info = textInfo.match(reg)[0];
              bt = $('#Zoom td').children('a').attr('href');
              res.push({
                Info:info,
                Bt:bt
              });
              console.log(res);
            })
            //怎么捕获错误!!!
            //res.on('error',function(){
            //  console.log('error');
            //})
          })
      // }catch(e) {
      //   console.log(e);
      // }
      });
    };
    
    getTitleHref(baseUrl,1)
    

    所以写node代码切记大多数都是异步的,上面代码就出了一个问题:
    这里写图片描述

    当前代码就不能保证下面的代码, 在 res.end 后执行,因为res.end在异步队列里可能没执行完,就进入了下面的if,就算最后进入getLink后就会出现titleHref.forEach进不去的情况的,因为titleHref是空的。

    当时遇到这个问题如果不考虑到异步流程控制的解决流程的话,一个解决方案是在each函数里,获取到一个titleHref就getLink下,titileHref定义成局部函数,getLink函数放在each里面,这样就保证titleHref不会是空的了。然后代码如下:

    const cheerio = require('cheerio');
    const http = require('http');
    const iconv = require('iconv-lite');
    
    let baseUrl = "http://www.ygdy8.net/html/gndy/dyzz/list_23_";
    let Host = "http://www.ygdy8.net/";
    
    const totalPage = 2; //指定爬多少页数据
    let ans = [];
    //获取页面电影数据
    function getTitleHref(url,page) {
      let startUrl = url+page+".html";
      http.get(startUrl,function(res) {
        const { statusCode } = res;
        let chunks = [];
        res.on('data',function(chunk){
          chunks.push(chunk);
        });
        res.on('end',function(){
          let title = [];
          
          let html = iconv.decode(Buffer.concat(chunks),'gb2312');
          let $ = cheerio.load(html, {decodeEntities: false});
          // console.log($);
          $('.co_content8 .ulink').each(function(i,d) {
            let $d = $(d);
            let titleHref = [];
            titleHref.push({
              href: $d.attr('href')
            });
            getLink(titleHref);
          });
          // console.log(ans);
        });  
      });
    }
    
    
    // /*
    //获取种子链接
    function getLink(titleHref) {
      console.log('进入getLink');
      console.log(titleHref);
      if(titleHref) {
        titleHref.forEach(function(v,k) {
          console.log('~~~~~~~~~~~~~~~~~~~~');
          let infoUrl = Host + v.href;
          // console.log(infoUrl);
        
            http.get(infoUrl,function(res) {
              const { statusCode } = res;
              const contentType = res.headers['content-type'];
            
              let error;
              if (statusCode !== 200) {
                error = new Error('请求失败。
    ' +
                                 `状态码: ${statusCode}`);
              } 
              if (error) {
                console.error(error.message);
                // 消耗响应数据以释放内存
                res.resume();
                return;
              }
              console.log('进入getlink http');
              let chunks = [];
              res.on('data',function(chunk) {  
                chunks.push(chunk);
              });
              res.on('end', function(){
                try {
                  let html = iconv.decode(Buffer.concat(chunks),'gb2312');
                  let $ = cheerio.load(html, {decodeEntities: false});
                  let bt = '';
                  bt = $('#Zoom td').children('a').attr('href');
                  // console.log(bt);
                  // console.log(typeof bt)
                  ans.push(bt);
                  // cb(ans);
                }catch (e) {
                  console.error('bt',e.message);
                }
              })
            }).on('error', (e) => {
              console.error(`错误: ${e.message}`);
            });
        });
      }
    };
    // */
    for(let i = 1; i <= totalPage; i++) {
      getTitleHref(baseUrl,i);
      console.log(ans);
    };
    
    
    

    但是这样的代码你还会发现一个问题,我们最后保存的bt链接的ans结果,打印的还是空的,同样是异步的问题,我们如果要存入数据库或者需要ans数据的话,我们不知道何时返回了这个数据。

    所以最终我们还是要用到ES6/7提出的方案Promise和async/await。
    修改之后代码如下:

    const cheerio = require('cheerio')
    const http = require('http')
    const iconv = require('iconv-lite')
    
    const baseUrl = 'http://www.ygdy8.net/html/gndy/dyzz/list_23_'
    const Host = 'http://www.ygdy8.net/'
    
    const totalPage = 2 //指定爬多少页数据
    let ans = []
    //获取页面电影数据
    function getTitleHref(url, page) {
      return new Promise((resolve, reject) => {
        let startUrl = url + page + '.html'
    
        http.get(startUrl, function(res) {
          const { statusCode } = res
          let chunks = []
          res.on('data', function(chunk) {
            chunks.push(chunk)
          })
          res.on('end', function() {
            let title = []
    
            let html = iconv.decode(Buffer.concat(chunks), 'gb2312')
            let $ = cheerio.load(html, { decodeEntities: false })
    
            let titleHref = []
            $('.co_content8 .ulink').each(function(i, d) {
              let $d = $(d)
              titleHref.push({
                href: $d.attr('href')
              })
            })
    
            resolve(getLink(titleHref))
          })
        })
      })
    }
    
    // /*
    //获取种子链接
    function getLink(titleHref, cb) {
      console.log('进入getLink')
      console.log(titleHref)
      if (titleHref) {
        return Promise.all(
          titleHref.map(function(v, k) {
            return new Promise((resolve, reject) => {
              console.log('~~~~~~~~~~~~~~~~~~~~')
              let infoUrl = Host + v.href
    
              http
                .get(infoUrl, function(res) {
                  const { statusCode } = res
                  const contentType = res.headers['content-type']
    
                  let error
                  if (statusCode !== 200) {
                    error = new Error('请求失败。
    ' + `状态码: ${statusCode}`)
                  }
                  if (error) {
                    console.error(error.message)
                    // 消耗响应数据以释放内存
                    res.resume()
                    return
                  }
                  let chunks = []
                  res.on('data', function(chunk) {
                    chunks.push(chunk)
                  })
                  res.on('end', function() {
                    try {
                      let html = iconv.decode(Buffer.concat(chunks), 'gb2312')
                      let $ = cheerio.load(html, { decodeEntities: false })
                      let bt = ''
                      bt = $('#Zoom td')
                        .children('a')
                        .attr('href')
                      resolve(bt)
                    } catch (e) {
                      reject(e)
                    }
                  })
                })
                .on('error', e => {
                  reject(e)
                })
            })
          })
        )
      } else {
        return Promise.resolve()
      }
    }
    
    async function main() {
      // */
      let results = await Promise.all(
        new Array(totalPage).fill().map((_, i) => getTitleHref(baseUrl, i + 1))
      )
    
      ans = ans.concat(...results)
      console.log('get data:', ans)
    }
    
    main()
    

    每个函数都封装成Promise,最后在主函数中用await强制同步得到最后的结果results。(注意:1。new Array出来的是稀疏数组empty,最后fill()一下填充成undefine,2。47行传递的已经不是一个只有一条数据的数组了,而是将一个页面each执行完成后的汇总,所以在函数内部会有Promise.all
    3。93行则聊胜于无,即使return null也会正确的触发resolve的,这么写只是提高一些可读性罢了。)

    Promise和async/await整理可以看我的这篇博客Promise和async/await用法整理

    代码:github传送

    这次告诉我实践很重要!要把所学和书中所看运用到业务和代码逻辑中!

  • 相关阅读:
    Linked List Cycle leetcode java (链表检测环)
    Remove Duplicates from Sorted List II leetcode java
    Remove Duplicates from Sorted List leetcode java
    Merge Two Sorted Lists leetcode java
    Swap Nodes in Pairs leetcode java
    Median of Two Sorted Array leetcode java
    阿里云最便宜的四种域名注册
    nohup和&后台运行,进程查看及终止
    ipv6转ipv4 NAT64与DNS64基本原理概述
    ros使用pppoe拨号获取ipv6,并且下发IPV6的dns到客户机win7
  • 原文地址:https://www.cnblogs.com/zhangmingzhao/p/9234951.html
Copyright © 2011-2022 走看看