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

李衮
• 阅读 6002

应用介绍

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

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

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

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

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

使用的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:

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

项目说明

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

  1. 抓取品牌和车系

  2. 抓取年份

  3. 抓取车型

  4. 存入本地json文件

  5. 按需写入数据库(暂时没写)

细节控制

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

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

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

项目代码

Github地址:https://github.com/iNuanfeng/...

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');
// });
点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
美凌格栋栋酱 美凌格栋栋酱
10个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Easter79 Easter79
4年前
typeScript数据类型
//布尔类型letisDone:booleanfalse;//数字类型所有数字都是浮点数numberletdecLiteral:number6;lethexLiteral:number0xf00d;letbinaryLiteral:number0b101
可莉 可莉
4年前
18个常用 webpack插件,总会有适合你的!
!(https://oscimg.oschina.net/oscnet/71317da0c57a8e8cf5011c00e302a914609.jpg)来源| https://github.com/Michaellzg/myarticle/blob/master/webpack/Plugin何为插
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
4年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Easter79 Easter79
4年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Stella981 Stella981
4年前
Linux日志安全分析技巧
0x00前言我正在整理一个项目,收集和汇总了一些应急响应案例(不断更新中)。GitHub地址:https://github.com/Bypass007/EmergencyResponseNotes本文主要介绍Linux日志分析的技巧,更多详细信息请访问Github地址,欢迎Star。0x01日志简介Lin
李衮
李衮
Lv1
黄河远上白云间,一片孤城万仞山。
文章
3
粉丝
0
获赞
0