爬蟲被封怎麼辦?用Node構建一個私人IP代理池

Nothlu發表於2019-04-27

還記得剛學爬蟲的時候,選了一個美女網站來練手,效率極高,看到什麼都想爬下來。爬得正高興呢,出現了一連串錯誤資訊,檢視後發現因為爬取太過頻繁,被網站封了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)
}
複製程式碼

大功告成:

爬蟲被封怎麼辦?用Node構建一個私人IP代理池

完整程式碼可以通過Github檢視:Proxy-Pool

也可以訪問我的網站,獲取更多文章:Nothlu

相關文章