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

還記得剛學爬蟲的時候,選了一個美女網站來練手,效率極高,看到什麼都想爬下來。爬得正高興呢,出現了一連串錯誤信息,查看後發現由於爬取太過頻繁,被網站封了ip,那時起就有了構建代理ip池的念頭。javascript

網上搜索一下代理ip就會發現有不少網站提供,可是穩定好用的都要收費,免費倒也有一堆,但大多數都不能用。並且我寫的通常都是小爬蟲,極少有爬取上白g數據的時候,用收費的代理ip有點浪費。java

因此,寫了這個代理ip池,從各大代理ip網站爬取收集免費的代理ip,而後一一進行測試,從中篩選出高速可用的ip。得益於Node的異步架構,速度很是快,能夠直接在本身的爬蟲裏調用,每次爬取前獲取最新的代理ip,之後媽媽就不再用擔憂個人爬蟲被封了。jquery

接下來會分爲三個部分來說解,怎麼下載,怎麼用和怎麼寫,若是隻是想用的話看前兩篇就夠了。git

1.如何下載

有兩種途徑,一個是經過Github:Card007/Proxy-Pool;github

另外一種是經過npm添加:npm install ip-proxy-pool;sql

兩種方式均可以,推薦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的則是可用,返回其它數字的則刪除,來看代碼:npm

//導入相應的庫
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

相關文章
相關標籤/搜索