愛好三維立體圖多年疫赎,近期打算將網(wǎng)絡(luò)上能找到的資源收集一下盛撑。本著“偷懶至上”的原則,寫一簡單爬蟲腳本解放手指捧搞。作為前端狗抵卫,不敢忘本職工作。于是一式三份胎撇,分別用Callback
介粘,Promise
和Async
實(shí)現(xiàn)一遍,權(quán)當(dāng)學(xué)習(xí)ES6/7了晚树。源碼戳我
callback形式
- 目標(biāo)網(wǎng)站:http://www.3wtu.com/
- 流程簡述:
圖片url分別存儲在http://www.3wtu.com/picture/${i}.html (9 < i <183)
這些網(wǎng)頁姻采。首先遍歷這些網(wǎng)址,分別執(zhí)行獲取圖片url => 獲取圖片數(shù)據(jù) => 保存至本地爵憎。 - 不相關(guān)技術(shù)點(diǎn):編碼轉(zhuǎn)換慨亲。
1. 取圖片鏈接
首先我們封裝一個(gè)單次請求的方法。由于我們的目標(biāo)網(wǎng)站使用的gb2312
的編碼纲堵,因此我引入iconv模塊用來解碼巡雨。注意,不可用chunk += chunk
取代chunks.push(chunk)
席函,前者隱含了操作chunk += chunk.toString('utf8')
铐望。 詳見github.com/ashtuchkin/iconv-lite。
了解Nodejs的朋友應(yīng)該對cheerio模塊不會陌生茂附,它相當(dāng)于一個(gè)服務(wù)端的JQuery正蛙。
const http = require("http")
const fs = require("fs")
const cheerio = require("cheerio")
const iconv = require('iconv-lite')
var domian = 'http://www.3wtu.com'
var config = {
dirPath: __dirname + '/' + 'imagesByNormal/', // 圖片存儲目錄
interval: 300, // 單次請求的時(shí)間間隔
}
function getPicsUrl(url, callback) {
http.get(url, function(res) {
var chunks = []
res.on("data" ,function(chunk) {
chunks.push(chunk)
})
res.on("end",function() {
// 轉(zhuǎn)編碼后的html
var decodedBody = iconv.decode(Buffer.concat(chunks), 'gb2312')
// 服務(wù)端版本的JQuery
var $ = cheerio.load(decodedBody, { decodeEntities: false })
// 圖片的絕對地址
var pic = domian + $('.detailed-pic img').attr('src')
// 圖片名字
var name = $('.detailed-title h4').html()
callback({ url: pic, name: name })
})
})
}
2. 請求圖片數(shù)據(jù)
拿到圖片的鏈接之后,我們就需要請求圖片的數(shù)據(jù)营曼。
function getPicData(pic, callback) {
// 文件類型后綴名
var fileType = pic.url.split('.').pop()
// 命名時(shí)帶上3位時(shí)間戳乒验,降低重名的概率
var diff = new Date().getTime().toString().substring(10)
// 圖片路徑與名字
var name = config.dirPath + pic.name + '#' + diff + '.' + fileType
// 請求圖片數(shù)據(jù)
http.get(pic.url, function(res) {
var data = ''
res.setEncoding('binary')
res.on('data', function(chunk) {
data += chunk
})
res.on('end', function() {
callback(data, name)
})
})
}
3. File System 下載圖片至本地
最后把圖片存儲到我們的本地。
function download(data, name, callback) {
fs.writeFile(name, data, 'binary', callback)
}
4.啟動(dòng)
以上三步就是針對目標(biāo)網(wǎng)站將一張圖片爬下來的全部過程蒂阱。
現(xiàn)在我們只要啟動(dòng)遍歷所有的目標(biāo)網(wǎng)站即可
for (var i = 10; i < 183; i++) {
(function (index) {
var interval = (index - 10) * config.interval + Math.random() * 100
var url = 'http://www.3wtu.com/picture/' + index + '.html'
setTimeout(function () { // 等待锻全,防止請求太快
getPicsUrl(url, function(picLink) { // 網(wǎng)頁 => 圖片url
getPicData(picLink, function (picData) { // 圖片url => 圖片數(shù)據(jù)
download(picData.data, picData.name, function (err) { // 圖片數(shù)據(jù) => 本地圖片
if (err) {
console.log(err)
} else {
console.log(picData.name + ' downloaded successfully')
}
})
})
})
}, interval)
})(i)
}
以上代碼重點(diǎn)看setTimeout
狂塘,getPicsUrl
,getPicData
鳄厌,download
四連回調(diào)荞胡。這樣寫代碼是不是特別地不舒服呢?如果再多幾個(gè)嵌套回調(diào)了嚎,代碼的可讀性就會非常差泪漂。感受到了痛點(diǎn),才能更好的理解“我們需要一些新東西取解決痛點(diǎn)”歪泳。而新東西就是指Promise
和Async
萝勤。
Promise與Async
本文主要目的在于結(jié)合實(shí)例闡述Promise
和Async
對開發(fā)效率及體驗(yàn)的友好度。如果一點(diǎn)都不了解Promise
的朋友呐伞,可以先看看阮一峰老師的ES6標(biāo)準(zhǔn)入門敌卓。
另外,這里將Promise和Async放在一起荸哟,就是希望大家不要把兩者對立起來假哎。Promise
本身是用于封裝異步操作,同時(shí)提供了流程控制的API鞍历。而Async
函數(shù)只是對異步操作的流程控制舵抹,比Promise
更加的直觀和簡潔,進(jìn)一步提高了代碼可讀性劣砍。如果讀到這句話一點(diǎn)概念也沒有惧蛹,可以先戳這里Generator,都說Async
是Generator
的語法糖刑枝,學(xué)習(xí)Async
之前還是需要對Generator
有一定了解的香嗓。(其實(shí)我覺得語法糖這個(gè)說法不太好,Class
那種東西才是純粹的語法糖好么装畅?)
- 目標(biāo)網(wǎng)站:http://www.360doc.com/content/13/0905/08/11561215_312316659.shtml
- 流程簡述:
首先從目標(biāo)網(wǎng)站中獲取所有的圖片url靠娱,遍歷url數(shù)組 => 獲取圖片數(shù)據(jù) => 保存至本地 。 - 不相關(guān)技術(shù)點(diǎn):設(shè)置Request Header模擬瀏覽器行為掠兄。
1. 將異步請求封裝成Promise
首先我們需要把上述的異步函數(shù)封裝成Promise
像云。
- 定時(shí)器
function _setTimeout(i) {
var interval = i * config.interval + Math.random() * 100
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, interval)
})
}
- 獲取圖片url數(shù)組
function getPicsUrl(url) {
console.log(`開始向${url}請求圖片地址...`)
var html = ''
return new Promise(resolve => {
http.get(url, res => {
res.on('data', data => { html += data })
res.on('end', data => {
var $ = cheerio.load(html)
var $pics = $('#artContent img')
var pics = [].slice.call($pics).map(pic => {
return pic.attribs.src
})
console.log(`圖片鏈接獲取完畢,共${pics.length}張圖片蚂夕。`)
resolve(pics)
})
})
})
}
- 獲取圖片數(shù)據(jù)
以下這段代碼運(yùn)用到了設(shè)置header模擬瀏覽器迅诬,我對這方面并無過多的了解,僅僅是針對需求而解決問題一種方案婿牍。就不過多解釋了侈贷。(基礎(chǔ)的http常識是必須具備的,只是應(yīng)用層面上各取所需就好等脂,學(xué)習(xí)是需要成本的俏蛮,應(yīng)該珍惜時(shí)間才對)
function getPicData(urlStr) {
var name = urlStr.substring(56)
var urlJson = url.parse(urlStr)
var data = ''
var option = {
hostname: urlJson.hostname,
path: urlJson.pathname,
// 對方網(wǎng)站有限制爬蟲撑蚌,需要設(shè)置header模擬瀏覽器
headers: {
"User-Agent": `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36`,
"Referer": `http://www.360doc.com/content/13/0905/08/11561215_312316659.shtml`,
}
}
return new Promise(resolve => {
http.get(option, res => {
res.setEncoding('binary')
res.on('data', chunk => { data += chunk })
res.on('end', () => {
resolve({name, data})
})
})
})
}
- 下載圖片至本地
function download(pic) {
return new Promise(resolve => {
fs.writeFile(config.dirPath + pic.name, pic.data, 'binary', err => {
resolve({ err: err, name: pic.name })
})
})
}
2. Promise的流程控制
是不是很熟悉JQuery return this
的鏈?zhǔn)讲僮鳎?code>Promise的流程控制就特別相似,這樣寫代碼是不是比嵌套回調(diào)舒服很多吧嫁蛇?
getPicsUrl(domain).then(picsArr => { // 獲取圖片url數(shù)組
for (let i = 0; i < picsArr.length; i++) {
_setTimeout(i) // 定時(shí)器等待
.then(() => getPicData(picsArr[i])) // 獲取圖片數(shù)據(jù)
.then(pic => download(pic)) // 下載圖片至本地
.then(resJson => {
console.log(resJson.err || `${resJson.name} downloaded successfully`)
})
}
})
3. Async的流程控制
是不是已經(jīng)非常接近同步代碼了锨并?畢竟號稱異步編程之終極方案的~
async function crawler() {
if ( !isExit(config.dirPath) ) {
fs.mkdirSync(config.dirPath)
}
var picsArr = await getPicsUrl(domain) // 獲取圖片url數(shù)組
for (let i = 0; i < picsArr.length; i++) {
await _setTimeout() // 定時(shí)器等待
var pic = await getPicData(picsArr[i]) // 獲取圖片數(shù)據(jù)
var resJson = await download(pic) // 下載圖片至本地
console.log(resJson.err || `${resJson.name} downloaded successfully`)
}
}
作品展示
源碼戳我
展示一下成果露该,一共330張三維立體圖
如果看到這里你對三維立體圖感興趣睬棚,可以試試看破解下面這張。
原文首發(fā)于我的博客:https://www.vq0599.com/p/3
轉(zhuǎn)載請注明出處