抓取若是抓取的太快太頻繁會被源站封IP,本文會介紹下經過限流、限速和使用代理的方式來防止被封html
上篇文章,抓取「電影天堂」最新的170部電影,在抓取首頁電影list以後,會同時發出170個請求抓取電影的詳情頁,這樣在固定時間點集中爆發式的訪問頁面,很容易在日誌中被找出來,並且併發請求大了,極可能會中網站的防火牆之類的策略,IP被加到黑名單就悲劇了數據庫
先說下限流的方法,將批量的併發請求,分紅屢次固定請求個數,等上一次抓取結束後,再開始下一次抓取,直到所有抓取結束。json
這裏我使用async
模塊限制併發次數,async
主要有:集合、流程和工具三大類方法,這裏我使用eachLimit(arr, limit, iterator, [callback])
,全部修改是上篇文章的fetchContents
方法,該方法接受抓取到的170個文章的url list,此次經過eachLimit
將170個url按照3個一組併發,依次執行,具體代碼以下:數組
function fetchContents (urls) {
return new Promise((resolve, reject) => {
var results = []
async.eachLimit(urls, 3, (url, callback) => {
spider({url: url, decoding: 'gb2312'}, {
url: {
selector: '#Zoom table td a!text'
},
title: {
selector: '.title_all h1!text'
}
}).then((d) => {
results.push(d)
callback()
}, () => {
callback()
})
}, () => {
resolve(results)
})
})
}複製代碼
限流只是控制了一次併發的請求數,並無讓抓取程序慢下來,因此還須要限速,在限流的基礎上限速就變得很簡單,只須要在執行eachLimit
的callback
的時候,加上個Timer
就行了,爲了方便查看限速的效果,每次抓取成功以後,都console.log
顯示時間,因此改完的代碼以下:markdown
function fetchContents (urls) {
return new Promise((resolve, reject) => {
var results = []
async.eachLimit(urls, 3, (url, callback) => {
spider({url: url, decoding: 'gb2312'}, {
url: {
selector: '#Zoom table td a!text'
},
title: {
selector: '.title_all h1!text'
}
}).then((d) => {
var time = moment().format(‘HH:MM:ss')
console.log(`${url}===>success, ${time}`)
results.push(d)
setTimeout(callback, 2e3)
}, () => {
callback()
})
}, () => {
resolve(results)
})
})
}複製代碼
效果以下:併發
由於一些網站更新比較慢,咱們寫的抓取程序在定時腳本任務(crontab)跑的時候,可能網站尚未更新,若是不作處理會形成資源的浪費,尤爲國內很多VPS都是有流量限制的,不作控制,真金白銀就打水漂了。。async
繼續拿「電影天堂」最新更新的內容進行抓取,若是假設每五分鐘執行一次抓取腳本,那麼須要記錄下已經抓取過的文章(電影),這裏我簡單處理一下,經過一個_fetchedList.json
的文件,記錄抓取完的文章(電影)。具體思路以下:ide
_fetchedList.json
_fetchedList.json
,獲得數組Array,抓取以前判斷要抓取的url是否在這個數組內具體代碼講解以下:函數
var fs = require('fs-extra')
var path = require('path')
var uniqueArray = []
const UNIQUE_ARRAY_URL = './_fetchedList.json'
try {
uniqueArray = require(UNIQUE_ARRAY_URL)
} catch (e) {
}複製代碼
function dealListData (data) {
return new Promise((resolve, reject) => {
var urls = _.get(data, 'items')
if (urls) {
urls = urls.map(url => {
return 'http://www.dytt8.net' + url
}).filter(url => {
return uniqueArray.indexOf(url) === -1
})
// 若是爲空就reject
urls.length ? resolve(urls) : reject('empty urls')
} else {
reject(urls)
}
})
}複製代碼
uniqueArray
長度是300,不要無限增長function addUniqueArray (url) {
uniqueArray.push(url)
if (uniqueArray.length > 300) {
// 超長就刪掉多餘的
uniqueArray.shift()
}
}複製代碼
uniqueArray
數組內容到json
文件:fetchList().then(dealListData).then(fetchContents).then((d) => {
console.log(d, d.length)
// json落地
fs.writeJson(path.join(__dirname, UNIQUE_ARRAY_URL), uniqueArray)
}).catch((e) => {
console.log(e)
})複製代碼
爲了迷惑被抓取的網站,除了假裝User-Agent等方法,最重要的是使用代理服務,若是有錢的主能夠買代理,而後用,對於咱們作demo,那就直接抓取代理吧!下面代碼是抓取快代理網站的代理代碼:工具
var spider = require('../lib/spider')
function fetchProxy () {
return spider({
url: 'http://www.kuaidaili.com/proxylist/1/'
}, {
selector: '#index_free_list tbody tr',
handler: function ($tr, $) {
var proxy = []
$tr.find('td').each(function (i) {
proxy[i] = $(this).html().trim()
})
if (proxy[0] && proxy[1] && /\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}/.test(proxy[0]) && /\d{2,6}/.test(proxy[1])) {
} else {
// console.log(proxy);
}
return proxy
}
})
}
fetchProxy().then(data => {
console.log(data.map(p => p.join(',')))
})複製代碼
抓取以後的代理不必定直接就能夠用,還須要測試下代理是否能夠訪問成功咱們要抓取的網站,先寫個checkProxy(proxy)
的方法,用於檢測使用傳入的proxy是否抓取成功:
function checkProxy (proxy) {
return spider({
url: 'http://www.dytt8.net/index.htm',
proxy: proxy,
timeout: 5e3,
decoding: 'gb2312'
}, {
items: {
selector: '.co_area2 .co_content2 ul a!attr:href'
}
})
}複製代碼
而後將從快代理網站抓取到的代理一次傳入進去:
fetchProxy().then(data => {
var len = data.length
var succArray = []
data.forEach(p => {
checkProxy(`http://${p[0]}:${p[1]}`).then(() => {
succArray.push(p)
}).finally(done).catch(e => void (e))
})
function done () {
len--
if (len === 0) {
console.log(succArray)
}
}
})複製代碼
這裏最後console.log
出來的就是經過代理抓取成功的代理,能夠存入到數據庫,之後抓取使用。
最後在簡單說下代理的維護,抓取到了代理,由於是免費的,通常過一段時間就會不能用了,因此在使用的時候,能夠將代理放到一個數據庫中維護,數據庫中有字段:succCount
和failCount
用於記錄每次使用該代理成功和失敗的次數。每次使用代理抓取的時候,要有個反饋機制,若是成功就succCount
+1 ,失敗就failCount
+1。當失敗次數過多的時候,這個代理就不要再使用了。
本系列寫到第三篇了,後面還會有一些常見問題解答。週末看到一篇用Python抓知乎導出txt或者markdown的文章,之後手癢就會放出番外篇 😁
敬請你們關注同時穿插更新的「Hybrid開發」專題!
-eof-
@三水清
未經容許,請勿轉載,不用打賞,喜歡請轉發和關注
感受有用,歡迎關注個人公衆號,每週一篇原創技術文章