爬蟲被封怎麼辦?用Node構建一個私人IP代理池
還記得剛學爬蟲的時候,選了一個美女網站來練手,效率極高,看到什麼都想爬下來。爬得正高興呢,出現了一連串錯誤資訊,檢視後發現因為爬取太過頻繁,被網站封了ip,那時起就有了構建代理ip池的念頭。
網上搜索一下代理ip就會發現有很多網站提供,但是穩定好用的都要收費,免費倒也有一堆,但大多數都不能用。而且我寫的一般都是小爬蟲,極少有爬取上白g資料的時候,用收費的代理ip有點浪費。
所以,寫了這個代理ip池,從各大代理ip網站爬取收集免費的代理ip,然後一一進行測試,從中篩選出高速可用的ip。得益於Node的非同步架構,速度非常快,可以直接在自己的爬蟲裡呼叫,每次爬取前獲取最新的代理ip,以後媽媽就再也不用擔心我的爬蟲被封了。
接下來會分為三個部分來講解,怎麼下載,怎麼用和怎麼寫,如果只是想用的話看前兩篇就夠了。
##1.如何下載
有兩種途徑,一個是通過Github:Card007/Proxy-Pool;
另一種是通過npm新增:npm install ip-proxy-pool;
兩種方式都可以,推薦github,有個使用說明,後期我還會進行更新,歡迎start。
##2.如何使用
//匯入本地模組 var proxy = require('./proxy_pool.js') //如果通過npm安裝 //var proxy = require('ip-proxy-pool') //主程式,爬取ip+檢查ip var proxys = proxy.run //不爬取,只檢查資料庫裡現有的ip var check = proxy.check //提取資料庫裡所有的ip var ips = proxy.ips //ips接收一個處理函式,然後向這個函式傳遞兩個引數,一個為錯誤資訊,另一個為資料庫裡的所有ip ips((err,response)=>{ console.log(response) }) //如果希望爬取的ip多一點可以修改check函式裡的timeout 複製程式碼
##3.怎麼手動寫一個代理ip池
現在來說說自己怎麼寫一個代理ip池,以西刺為例,用到的工具和方法基本上和上一篇爬取豆瓣top250一樣,先是爬取西刺網站前5頁的所有免費ip,然後儲存在sqlite資料庫裡,然後通過一一使用爬取好的代理ip訪問某個網址,返回200的則是可用,返回其它數字的則刪除,來看程式碼:
//匯入相應的庫 var request = require('request') var cheerio = require('cheerio') var sqlite3 = require('sqlite3') //生成網址,西刺網址以尾號數字作為分頁連結 var ipUrl = function(resolve){ var url = 'http://www.xicidaili.com/nn/' var options = { url:'http://www.xicidaili.com/nn/', headers, } //用個簡單的for迴圈即可獲得所有需要的連結,然後將連結一一放到爬取網路的requestProxy裡 for (let i = 1; i <= 5; i++) { options.url = url + i requestProxy(options) } } //連結網路 var requestProxy = function(options){ //這裡使用了Promise來控制非同步 return new Promise((resolve, reject) => { request(options, function(err, response, body){ if(err === null && response.statusCode === 200){ //返回200說明爬取成功,loadHtml為解析函式,會將我們需要的資訊爬取出來存在資料庫裡 loadHtml(body) resolve() } else { console.log('連結失敗') resolve() } }) }) } 複製程式碼
接下來要說到Node的大坑,非同步,由於非同步架構,需要用到Promise來控制,比如在這個代理ip池裡,會出現reqeust函式還沒有爬完的時候就開始執行驗證函式,很容易出錯,所以我們需要分為兩組,一組為非同步爬取網站爬取,另一組為非同步驗證代理ip,所以我們來改造一下上面的程式碼:
//生成網址 var ipUrl = function(resolve){ var url = 'http://www.xicidaili.com/nn/' var options = { url:'http://www.xicidaili.com/nn/', headers, } var arr = [] for (let i = 1; i <= 5; i++) { options.url = url + i arr.push(requestProxy(options)) } //Promise.all接收一個數組,直到數組裡所有的函式執行完畢才執行後面then裡的內容 //實際上放這裡有點多餘,後期會改過來,先將就 Promise.all(arr).then(function(){ resolve() }) } //連結網路 var requestProxy = function(options){ return new Promise((resolve, reject) => { request(options, function(err, response, body){ if(err === null && response.statusCode === 200){ loadHtml(body) resolve() } else { console.log('連結失敗') resolve() } }) }) } 複製程式碼
接下來分析一下網頁內容,這裡我們只需要ip,埠,和型別即可:
//分析網頁內容 var loadHtml = function(data){ var l = [] var e = cheerio.load(data) e('tr').each(function(i, elem){ l[i] = e(this).text() }) for (let i = 1; i < l.length; i ++){ //在提取到想要的內容後發現太亂,需要額外的函式進行處理優化 clearN(l[i].split(' ')) } } //提取優化檔案資料, var clearN = function(l){ var index = 0 for (let i = 0; i < l.length; i++) { if(l[i] === '' || l[i] === '\n'){ }else{ var ips = l[i].replace('\n','') if (index === 0){ var ip = ips console.log('爬取ip:' + ip) } else if(index === 1){ var port = ips } else if(index === 4){ var type = ips } index += 1 } } //存入資料庫 insertDb(ip, port, type) } 複製程式碼
接著來實現資料庫的儲存刪除功能:
//開啟資料庫 var db = new sqlite3.Database('Proxy.db', (err) => { if(!err){ console.log('開啟成功') } else { console.log(err) } }) db.run('CREATE TABLE proxy(ip char(15), port char(15), type char(15))',(err) => {}) //新增資料檔案 var insertDb = function(ip, port, type){ db.run("INSERT INTO proxy VALUES(?, ?, ?)",[ip,port,type]) } //刪除資料庫檔案 var removeIp = function(ip){ db.run(`DELETE FROM proxy WHERE ip = '${ ip }'`, function(err){ if(err){ console.log(err) }else { console.log('成功刪除:'+ip) } }) } //從資料庫提取所有ip var allIp = function(callback){ return db.all('select * from proxy', callback) } 複製程式碼
接著將資料庫裡的ip提取出來,進行測速篩選:
//從資料庫提取出來的ip會通過這個類建立一個物件 var Proxys = function(ip,port,type){ this.ip = ip this.port = port this.type = type } //提取所有ip,通過check函式檢查 var runIp = function(resolve){ var arr = [] allIp((err,response) => { for (let i = 0; i < response.length; i++) { var ip = response[i] var proxy = new Proxys(ip.ip, ip.port, ip.type) arr.push(check(proxy, headers)) } Promise.all(arr).then(function(){ allIp((err, response)=>{ console.log('\n\n可用ip為:') console.log(response) }) }) }) } //檢測ip var check = function(proxy, headers){ return new Promise((resolve, reject) => { request({ //檢測網址為百度的某個js檔案,速度快,檔案小,非常適合作為檢測方式 url:'http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js', proxy: `${proxy.type.toLowerCase()}://${proxy.ip}:${proxy.port}`, method:'GET', //這裡延遲使用了2000,如果希望通過檢測的ip多一些,可以適當延長 timeout: 2000, headers,} ,function(err, response,body){ if(!err && response.statusCode == 200){ console.log(proxy.ip+' 連結成功:') resolve() } else { console.log(proxy.ip+' 連結失敗') removeIp(proxy.ip) resolve() } } ) }) } 複製程式碼
最後,來寫幾個執行函式:
var run = function(){ new Promise(ipUrl).then(runIp) } var rcheck= function(){ runIp() } var ips = function(callback){ allIp(callback) } 複製程式碼
大功告成:

完整程式碼可以通過Github檢視: Proxy-Pool
也可以訪問我的網站,獲取更多文章:Nothlu