應用介紹
項目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是爬蟲主程序,分步驟抓取數據。
- 抓取品牌和車系
- 抓取年份
- 抓取車型
- 存入本地json文件
- 按需寫入數據庫(暫時沒寫)
細節控制
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');
// });