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