還記得剛學爬蟲的時候,選了一個美女網站來練手,效率極高,看到什麼都想爬下來。爬得正高興呢,出現了一連串錯誤信息,查看後發現由於爬取太過頻繁,被網站封了ip,那時起就有了構建代理ip池的念頭。javascript
網上搜索一下代理ip就會發現有不少網站提供,可是穩定好用的都要收費,免費倒也有一堆,但大多數都不能用。並且我寫的通常都是小爬蟲,極少有爬取上白g數據的時候,用收費的代理ip有點浪費。java
因此,寫了這個代理ip池,從各大代理ip網站爬取收集免費的代理ip,而後一一進行測試,從中篩選出高速可用的ip。得益於Node的異步架構,速度很是快,能夠直接在本身的爬蟲裏調用,每次爬取前獲取最新的代理ip,之後媽媽就不再用擔憂個人爬蟲被封了。jquery
接下來會分爲三個部分來說解,怎麼下載,怎麼用和怎麼寫,若是隻是想用的話看前兩篇就夠了。git
有兩種途徑,一個是經過Github:Card007/Proxy-Pool;github
另外一種是經過npm添加:npm install ip-proxy-pool;sql
兩種方式均可以,推薦github,有個使用說明,後期我還會進行更新,歡迎start。數據庫
//導入本地模塊
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
複製代碼
如今來講說本身怎麼寫一個代理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