• nodejs爬虫——汽车之家所有车型数据


    应用介绍

    项目Github地址:https://github.com/iNuanfeng/node-spider/

    nodejs爬虫,爬取汽车之家所有车型数据 http://www.autohome.com.cn/car/

    包括品牌,车系,年份,车型四个层级。

    使用的node模块:

    superagent, request, iconv; (网络请求模块,iconv用于gbk转码)

    cheerio; (和jQuery一样的API,处理请求来的html,省去正则匹配)

    eventproxy, async; (控制并发请求,async控制得更细)

    async控制并发请求数量为10个(避免封IP与网络错误)

    模拟sleep使间隔100ms(不设间隔偶尔会出现dns错误)

    去除express模块,该为控制台直接开启爬虫(数据量大,打开网页来开启爬虫可能会由于超时而重新发起访问)

    最终使用的模块为: request, iconv, cheerio, async

    最后写入到数据库mysql或mongoDB

    写入data.json:

    项目说明

    app.js是爬虫主程序,分步骤抓取数据。

    1. 抓取品牌和车系
    2. 抓取年份
    3. 抓取车型
    4. 存入本地json文件
    5. 按需写入数据库(暂时没写)

    细节控制

    http://www.autohome.com.cn/3128 在售款有2016,2017同时存在

    有的车系在售有2016,停售也有2016

    抓取失败时重新抓取该页面

    项目代码

    Github地址:https://github.com/iNuanfeng/node-spider/

    app.js:

    var express = require('express'),
      app = express(),
      request = require('request'),
      iconv = require('iconv-lite'),
      cheerio = require('cheerio'),
      async = require("async"), // 控制并发数,防止被封IP
      fs = require('fs');
    
    var fetchData = []; // 存放爬取数据
    
    /**
     * 睡眠模拟函数
     * @param  {Number} numberMillis 毫秒
     */
    function sleep(numberMillis) {
      var now = new Date();
      var exitTime = now.getTime() + numberMillis;
      while (true) {
        now = new Date();
        if (now.getTime() > exitTime)
          return;
      }
    }
    
    /**
     * 爬取品牌 & 车系
     */
    function fetchBrand(req, res) {
      var pageUrls = []; // 存放爬取网址
      var count = 0; // 总数
      var countSuccess = 0; // 成功数
    
      var chars = ['A', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'W', 'X', 'Y', 'Z'];
    
      for (var char of chars) {
        count++;
        pageUrls.push('http://www.autohome.com.cn/grade/carhtml/' + char + '.html');
      }
    
      var curCount = 0;
      var reptileMove = function(url, callback) {
        var startTime = Date.now(); // 记录该次爬取的开始时间
    
        request({
          url: url,
          encoding: null // 关键代码
        }, function(err, res, body) {
          if (err || res.statusCode != 200) {
            console.error(err);
            console.log('抓取该页面失败,重新抓取该页面..')
            reptileMove(series, callback);
            return false;
          }
    
          var html = iconv.decode(body, 'gb2312')
          var $ = cheerio.load(html);
          var curBrands = $('dl');
          for (var i = 0; i < curBrands.length; i++) {
            var obj = {
              name: curBrands.eq(i).find('dt div a').text(),
              sub: []
            }
            fetchData.push(obj);
    
            var curSeries = curBrands.eq(i).find('h4 a');
            for (var j = 0; j < curSeries.length; j++) {
              var obj = {
                name: curSeries.eq(j).text(),
                sub: [],
                url: curSeries.eq(j).attr('href')
              }
              fetchData[fetchData.length - 1].sub.push(obj);
            }
          }
    
          countSuccess++;
          var time = Date.now() - startTime;
          console.log(countSuccess + ', ' + url + ', 耗时 ' + time + 'ms');
          callback(null, url + 'Call back content');
        });
      };
    
      // 使用async控制异步抓取   
      // mapLimit(arr, limit, iterator, [callback])
      // 异步回调
      async.mapLimit(pageUrls, 1, function(url, callback) {
        reptileMove(url, callback);
      }, function(err, result) {
        console.log('----------------------------');
        console.log('品牌车系抓取完毕!');
        console.log('----------------------------');
        fetchYear(req, res);
      });
    
    }
    
    /**
     * 爬取年份
     */
    function fetchYear(req, res) {
      var count = 0; // 总数
      var countSuccess = 0; // 成功数
      var seriesArr = [];
      // 轮询所有车系
      for (var brand of fetchData) {
        for (var series of brand.sub) {
          count++;
          seriesArr.push(series);
        }
      }
    
      var curCount = 0;
      var reptileMove = function(series, callback) {
        var startTime = Date.now(); // 记录该次爬取的开始时间
        curCount++; // 并发数
    
        request({
          url: series.url,
          encoding: null // gbk转码关键代码
        }, function(err, res, body) {
          if (err || res.statusCode != 200) {
            console.error(err);
            console.log('抓取该页面失败,重新抓取该页面..')
            reptileMove(series, callback);
            return false;
          }
    
          var html = iconv.decode(body, 'gb2312')
          var $ = cheerio.load(html);
    
          // 页面默认的数据
          var itemList = $('.interval01-list li');
          itemList.each(function(){
            var year = $(this).find('a').eq(0).text().substr(0, 4);
            var name = $(this).find('a').eq(0).text();
            var flag = false;
            for (item of series.sub) {
              if (item.name == year) {
                item.sub.push(name);
                flag = true;
              }
            }
            if (!flag) {
              var obj = {
                name: year,
                sub: [$(this).find('a').eq(0).text()],
                url: ''
              };
              series.sub.push(obj);
            }
          });
    
          // 下拉框中的年份抓取
          var curYears = $('.cartype-sale-list li');
          curYears.each(function(){
            var year = $(this).text().substr(0, 4);
            var flag = false;
    
            var href = series.url;
            var s = href.split('/')[3]; // 从url中截取所需的s参数
            var y = ($(this).find('a').attr('data'))
            var url = 'http://www.autohome.com.cn/ashx/series_allspec.ashx?s='
                      + s + '&y=' + y;
            
            for (item of series.sub) {
              if (item.name == year) {
                item.url = url;
                flag = true;
              }
            }
            if (!flag) {
              var obj = {
                name: year,
                sub: [],
                url: url
              };
              series.sub.push(obj);
            }
          })
          
          curCount--;
          countSuccess++;
          var time = Date.now() - startTime;
          console.log(countSuccess + ', ' + series.url + ', 耗时 ' + time + 'ms');
    
          sleep(50);
          callback(null, series.url + 'Call back content');
        });
      };
    
      console.log('车系数据总共:' + count + '条,开始抓取...')
    
      // 使用async控制异步抓取   
      // mapLimit(arr, limit, iterator, [callback])
      // 异步回调
      async.mapLimit(seriesArr, 10, function(series, callback) {
        reptileMove(series, callback);
      }, function(err, result) {
        // 访问完成的回调函数
        console.log('----------------------------');
        console.log('车系抓取成功,共有数据:' + countSuccess);
        console.log('----------------------------');
        fetchName(req, res);
      });
    }
    
    /**
     * 爬取型号
     */
    function fetchName(req, res) {
      var count = 0; // 总数
      var countSuccess = 0; // 成功数
      var yearArr = [];
      // 轮询所有车系
      for (var brand of fetchData) {
        for (var series of brand.sub) {
          for (var year of series.sub) {
            if (year.url) {
              count++;  // 过滤没有url的年款
              yearArr.push(year);
            }
          }
        }
      }
    
      var curCount = 0;
      var reptileMove = function(year, callback) {
        var startTime = Date.now(); // 记录该次爬取的开始时间
        curCount++; // 并发数
        // console.log(curCount + ': ' + series.url);
    
        request({
          url: year.url,
          encoding: null // gbk转码关键代码
        }, function(err, res, body) {
          if (err || res.statusCode != 200) {
            console.error(err);
            console.log('抓取该页面失败,重新抓取该页面..')
            console.log(year)
            reptileMove(year, callback);
            return false;
          }
    
          console.log(countSuccess + ', 抓取: ' + year.url)
          var html = iconv.decode(body, 'gb2312')
          try {
            var data = JSON.parse(html)
          } catch(e) {
            console.log('error... 忽略该页面');
            // reptileMove(series, callback);
            curCount--;
            callback(null, year.url + 'Call back content');
            return false;
          }
          var specArr = data.Spec;
          for (var item of specArr) {
            year.sub.push(item.Name);
          }    
          
          curCount--;
          countSuccess++;
          var time = Date.now() - startTime;
          // sleep(100);
          callback(null, year.url + 'Call back content');
        });
      };
    
      console.log('车型数据总共:' + count + '条,开始抓取...')
    
      // 使用async控制异步抓取   
      // mapLimit(arr, limit, iterator, [callback])
      // 异步回调
      async.mapLimit(yearArr, 20, function(year, callback) {
        reptileMove(year, callback);
      }, function(err, result) {
        // 访问完成的回调函数
        console.log('----------------------------');
        console.log('车型抓取成功,共有数据:' + countSuccess);
        console.log('----------------------------');
        // res.send(fetchData);
        var t = JSON.stringify(fetchData);
        fs.writeFileSync('data.json', t);
      });
    }
    
    /**
     * 爬虫入口
     */
    fetchBrand();
    
    // 开启express路由,用于浏览器调试
    // app.get('/', fetchBrand);
    // var server = app.listen(3000, function() {
    //   console.log('listening at 3000');
    // });
    
  • 相关阅读:
    byvoid
    soa文章摘抄
    也谈设计模式,架构,框架和类库的区别
    GoF设计模式三作者15年后再谈模式
    陈梓涵:我们为什么要学习设计模式
    陈梓涵:关于编程的胡扯
    hung task机制
    iscsi target tgt架构
    iscsi target IET架构
    ISCSI工作流程target和initiator
  • 原文地址:https://www.cnblogs.com/woodk/p/6431645.html
Copyright © 2020-2023  润新知