項目Github地址:https://github.com/iNuanfeng/node-spider/javascript
nodejs爬蟲,爬取汽車之家全部車型數據 http://www.autohome.com.cn/car/html
包括品牌,車系,年份,車型四個層級。java
superagent, request, iconv; (網絡請求模塊,iconv用於gbk轉碼)node
cheerio; (和jQuery同樣的API,處理請求來的html,省去正則匹配)mysql
eventproxy, async; (控制併發請求,async控制得更細)git
async控制併發請求數量爲10個(避免封IP與網絡錯誤)github
模擬sleep使間隔100ms(不設間隔偶爾會出現dns錯誤)sql
去除express模塊,該爲控制檯直接開啓爬蟲(數據量大,打開網頁來開啓爬蟲可能會因爲超時而從新發起訪問)數據庫
最終使用的模塊爲: request
, iconv
, cheerio
, async
express
最後寫入到數據庫mysql或mongoDB
寫入data.json:
app.js是爬蟲主程序,分步驟抓取數據。
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'); // });