每天下班之后,都会刷一刷豆瓣。其中的一个重要原因就是我关注的友邻们的相册经常更新好看的图片。正好这段时间在学习爬虫,就打算用node做个爬虫,把友邻的相册爬下来,下面是爬取的过程。
爬虫原理介绍
网络爬虫,又叫做网络蜘蛛、自动检索工具。是一种“自动化浏览网络”的程序,或者说是一种网络机器人。
它们被广泛用于互联网搜索引擎或其它类似网站,以获取或更新这些网站的内容和检索方式。
世界上第一个网络爬虫是由麻省理工学院(MIT)的学生马修.格雷(Matthew Gray)在1993年写成的。
他给这个程序起了个名字“互联网漫游者”(“www wanderer”)。之后的网络爬虫越来越复杂,但是原理是一样的。
爬虫的工作流程
- 首先选取种子URL;
- 将这些URL放入待抓取URL队列;
- 从待抓取URL队列中取出待抓取在URL,解析DNS,并且得到主机的ip,并将URL对应的网页下载下来,存储进已下载网页库中。此外,将这些URL放进已抓取URL队列。
-
分析已抓取URL队列中的URL,分析其中的其他URL,并且将URL放入待抓取URL队列,从而进入下一个循环。
本次爬虫用到的模块/框架
express
可以设置中间件来响应 HTTP 请求。
定义了路由表用于执行不同的 HTTP 请求动作。
可以通过向模板传递参数来动态渲染 HTML 页面。
superagent
它是nodejs里一个客户端请求代理模块,可以很方便的处理get,post,put,delete,head请求
cheerio
可以理解为服务端的jQuery
request
用来处理http请求
fs
fs是filesystem的缩写,该模块提供本地文件的读写能力
大致的思路
先使用express
搭建一个node服务;使用superagent
向目标页面发送请求,并获取返回的页面;使用cheerio
处理获取的页面元素,使用fs
读写本地文件,把图片下载到本地。
实现的过程
首先安装需要的框架和模块;
1. 搭建一个node服务
新建一个名为index.js的文件,输入如下内容:
const express = require ('express')
const app = express()
let server = app.listen(3000, function () {
let host = server.address().address
let port = server.address().port
console.log('your app is running at http://%s:%s', host, port)
})
打开命令行,切换到代码所在目录,并执行node index.js
,如果执行成功,则如下图:
2. 分析相册页面的结构
以这个相册为例,无意间看到的
打开F12控制面板,查看相册图片所在的HTML结构,会发现它们都包含在一个class名字为photolst
的div中。
3. 使用superagent
向目标地址发送请求,并用cheerio
处理结构
代码如下:
// 向目标地址发送请求
superagent.get('https://www.douban.com/photos/album/139203394/').end((err, res) => {
if (err) { // 失败...
console.log(`相册获取失败${err}`)
} else { // 成功...
pic = getBeautyPic(res)
}
})
定义一个名为getBeautyPic
的方法来处理获取的内容
// 处理爬取到的图片
let getBeautyPic = (res) => {
let pic = []
// 访问成功,包含图片的dom结构就会在res里
// 使用cheerio的load方法,把HTMLdocument作为参数传入,就可以使用类似jQuery的方法进行操作
let $ = cheerio.load(res.text)
// 找到目标数据所在dom结构,获取数据
$('.photolst .photo_wrap img').each((idx, ele) => {
pic.push($(ele).attr('src'))
})
// 把图片的链接放到一个数组中返回
return pic
}
此时的代码如下图:
const express = require ('express')
const superagent = require ('superagent')
const cheerio = require ('cheerio')
const request = require ('request')
const app = express()
// 创建一个服务
let server = app.listen(3000, function () {
let host = server.address().address // ?
let port = server.address().port
console.log('you app is running at http://%s:%s', host, port)
})
// 爬取豆瓣的相册
superagent.get('https://www.douban.com/photos/album/139203394/').end((err, res) => {
if (err) {
console.log(`相册获取失败${err}`)
} else {
pic = getBeautyPic(res)
}
})
// 处理爬取到的图片
let getBeautyPic = (res) => {
let pic = []
// 访问成功,包含图片的dom结构就会在res里
// 使用cheerio的load方法,把HTMLdocument作为参数传入,就可以使用类似jQuery的方法进行操作
let $ = cheerio.load(res.text)
// 找到目标数据所在dom结构,获取数据
$('.photolst .photo_wrap img').each((idx, ele) => {
pic.push($(ele).attr('src'))
})
return pic
}
此时已经把目标页面的图片链接都放在了pic这个数组中,接下来,把图片下载到本地就可以了。
4. 下载图片到本地
定义一个下载图片的方法:
// 爬取图片到本地
let download = function(uri, filename, callback){
request.head(uri, function(err, res, body){
request(uri).pipe(fs.createWriteStream(filename)).on('close', callback);
})
}
上面的这个方法依赖于request
模块和fs
模块,需要安装一下。
因为图片不止一张,所以循环获取。
整理后的代码如下:
const express = require ('express')
const superagent = require ('superagent')
const cheerio = require ('cheerio')
const request = require ('request')
const fs = require ('fs')
const app = express()
// 创建一个服务
let server = app.listen(3000, function () {
let host = server.address().address // ?
let port = server.address().port
console.log('your app is running at http://%s:%s', host, port)
})
// 爬取豆瓣的相册
superagent.get('https://www.douban.com/photos/album/139203394/').end((err, res) => {
if (err) {
console.log(`相册获取失败${err}`)
} else {
pic = getBeautyPic(res)
for (const uri of pic) { // 循环下载
let filename = uri.substring(uri.lastIndexOf('/')+1) // 截取图片的名字
download(uri, filename, function(){
console.log(`${filename}下载完成`);
});
}
}
})
// 爬取图片到本地
let download = function(uri, filename, callback){
request.head(uri, function(err, res, body){
request(uri).pipe(fs.createWriteStream(filename)).on('close', callback);
});
};
// 处理爬取到的图片
let getBeautyPic = (res) => {
let pic = []
// 访问成功,包含图片的dom结构就会在res里
// 使用cheerio的load方法,把HTMLdocument作为参数传入,就可以使用类似jQuery的方法进行操作
let $ = cheerio.load(res.text)
// 找到目标数据所在dom结构,获取数据
$('.photolst .photo_wrap img').each((idx, ele) => {
pic.push($(ele).attr('src'))
})
return pic
}
5. 在node中执行文件
重启node服务,会看到图片一个个都开始下载了:
程序执行完毕后,打开本地文件夹,就会看到多了一些图片,这些图片就是刚刚下载的了。
这个程序还有非常多的地方需要做优化,比如当前页面图片下载完后,应该自动开始下一个页面图片的下载、用promise改写、对错误的处理...等等 后面会继续改进.