项目背景

最近刚刚用vue完成了一个商城的项目。截下来就是填充数据的问题了,我们缺图片信息,只有想办法从淘宝弄了。第一次写个小爬虫还是挺兴奋的。python不会,只好用node实现了,查了下目前有两种方案:

  1. superagent + cheerio
  2. phantomjs

superagent+cheerio

superagent是服务器端异步请求模块的一种实现,它支持restful形式的api。内部依赖nodejs原生的请求api,适用于nodejs环境下。

1
2
3
4
5
6
7
8
9
10
11
12
request
.post('/api/pet')
.send({ name: 'Manny', species: 'cat' })
.set('X-API-Key', 'foobar')
.set('Accept', 'application/json')
.end(function(res){
if (res.ok) {
alert('yay got ' + JSON.stringify(res.body));
} else {
alert('Oh no! error ' + res.text);
}
});

cheerio则是服务器端html解析器的一种实现,服务器端不像浏览器,带有html解析器。所以在操作html文档数据的时候,需要这种工具。

说到这里,思路也很清晰了:利用superagent异步拉取淘宝的商品详情页html。再利用cheerio解析其中需要的图文详情dom获得图片url。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var express = require('express');
var bodyParser = require('body-parser');
var cheerio = require('cheerio');
var superagent = require('superagent');
var fs = require('fs');
var app = express();
app.use(bodyParser.json({limit: '1mb'})); //body-parser 解析json格式数据
app.use(bodyParser.urlencoded({ //此项必须在 bodyParser.json 下面,为参数编码
extended: true
}));
app.get('/index.html', function(req, res, next){
res.send(fs.readFileSync('index.html').toString());
});
app.post('/result', function(req, res, next){
// console.log(req.body.urls.split('\r\n'));
var urls = req.body.urls.split('\r\n');
urls.forEach(function(url) {
superagent.get(url).end(function (err, sres) {
if (err) { return next(err); }
var $ = cheerio.load(sres.text);
var items = [];
$('.mui-custommodule .mui-custommodule-item img').each(function(idx, element){
var ele = $(element);
item.push({
imgsrc: ele.attr('src')
})
});
var se = [];
se.push('页面地址:', url, '\n');
se.push('页面html:', sres.text, '\n');
se.push('页面结果:', JSON.stringify(items), '\n');
res.send(se.join(''));
});
});
});
app.listen(3000, function () {
console.log('app is listening at port 3000');
});

这里在index.html页面新建了一个form和textarea,textarea以换行符输入多个详情页面的url。要注意在使用express时,如果需要提取post提交的参数时,需要使用body-parser。

结果

最终结果并不如我所料。抓取到的页面url总是404,经过一番google。发现有如下原因:

  1. 淘宝页面dom采用js动态生成,抓取不到;
  2. 淘宝反爬虫策略可以检测出这种基础的盗取数据方式

phantomjs

PhantomJS是一个无界面的,可脚本编程的WebKit浏览器引擎。它原生支持多种web 标准:DOM 操作,CSS选择器,JSON,Canvas 以及SVG。可以看做是一个无界面的浏览器。既然浏览器可以正常打开淘宝页面,phantomjs应该也可以;而且它还提供了操作dom的能力,只要等待页面加载完毕,dom生成之后,也就可以拿到数据了。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
var webPage = require('webpage');
var page = webPage.create();
var pageTb = webPage.create();
var pageImg = webPage.create();
var tbUrl = system.args[2];
page.settings.userAgent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36";
pageTb.open(tbUrl, function(status) {
// 由于是拉取异步数据,我们打开页面后,等待12s再去操作dom,获取交易量
setTimeout(function() {
var result = pageTb.evaluate(function() { //evaluate在页面中执行js代码(获取某个元素等),open方法和evaluate方法中的console不会执行
var temp = [];
Array.prototype.forEach.call(document.querySelectorAll('.mui-custommodule-item img'), function(item){
temp.push(item.dataset.ksLazyload); //data自定义属性只能通过dataset获取,不能通过attributes和直接.访问到
})
return temp;
});
//生成该页面的url
var resultLen = result.length;
pageImg.viewportSize = { width: 375 };
function getImgs(){
pageImg.open(result[0], function(status) {
if (status !== 'success') {
console.log('Unable to load the address!');
getImgs();
} else {
window.setTimeout(function () {
var size = pageImg.evaluate(function() {
var img = document.querySelector('img');
var h = img.getAttribute('height'), w = img.getAttribute('width');
return { height: h/w*750, width: 750}; //阿里服务器会自动将width和height加到图片上
});
pageImg.viewportSize = size;
pageImg.render( resultLen-result.length+'.jpg', {format: 'jpg', quality: '100'});
getImgs();
}, 200);
}
result.shift(); //删除第一个元素
})
if(result.length<=0) phantom.exit();
}
getImgs()
//生成当前页面截图
// pageTb.render("xuqintb2.png");
// phantom.exit();
}, 2000);
});

注意phantom可以用npm安装,但是不是node直接可以执行的,执行命令如下:

1
phantom --ssl-protocol=any "https://detail.m.tmall.com/item.htm?id=40586936292"

由于是打开https协议头的网页,所以执行js文件时,需要添加”–ssl-protocol=any”参数。详情页面的url作为参数传入。
可以看出上面phantom先加载页面,根据ksLazyload这个图片懒加载属性获取图片真实url;之后递归加载每张图片,利用render方法生成图片。

最终拉取下来效果是这样: