应用介绍
项目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\');
// });