寫在前面
做爬蟲的小伙伴一般都繞不過代理IP這個問題.
PS:如果還沒遇到被封IP的場景,要不就是你量太小人家懶得理你,要不就是人家壓根不在乎...
爬蟲用戶自己是沒有能力維護一系列的代理服務器和代理IP的,這個成本實在有點高了。
所以公用代理服務器應運而生,現在幾大雲服務商家都提供代理IP服務,一般論個買...
同時網上也有很多代理IP共享網站,會把一些免費的代理IP放出來給大家用。
大家都是做爬蟲的,那么,是不是可以先把代理IP網站的數據爬一遍?
所以可以看到不少的爬代理IP的爬蟲,如突破反爬蟲的利器——開源IP代理池之類的項目。
這些項目都能達到抓取代理IP數據的目的,很多時候也夠用了。
然而在使用過程中我們發現了一些問題:
- 網站公布的代理IP不一定是可用的。可能代理服務器掛了,可能IP無效了...等等之類的。
- 代理IP是部分可用的。某代理IP可用代理訪問百度,但是代理訪問谷歌的時候就GG了。
- 代理連通性是好的,但是已經被訪問站識別為代理IP返回驗證碼或者辣雞數據。
proxyipcenter的解決方案
出於以上種種的原因,按耐不住的dalao(@virjar)開始自己動手擼了。
於是,proxyipcenter出來了。
這個項目做了什么東西呢?
dalao是這樣寫的:
DungProxy是一個代理IP服務,他包括一個代理IP資源server端和一系列適配中心IP資源得客戶端。
server負責代理IP資源的收集維護。
client則是一系列方便用戶使用得API,他屏蔽了代理IP下載、代理IP選取、IP綁定、IP切換等比較復雜邏輯。用戶只需要引入client即可方便使用代理IP服務
簡單來說:
server是一個代理IP爬蟲,同時負責簡單的數據清洗。
client本質是一個代理IP池,獲取數據源之后,基於需要代理訪問的網站在本地維護一個代理IP池供爬蟲使用。
以下內容轉載自http://git.oschina.net/virjar/proxyipcenter,
並已獲得相關授權。(PS:dalao寫的太好了,我都懶得重新寫一遍了...問過dalao之后直接轉載過來算了。)
proxyipcenter server
DrungProxy的代理IP都是從互聯網收集,他是架設在一個高度不可用的資源上面的服務。server會負責對這些資源進行清洗、校驗、打分,最終輸出可以被客戶端使用的IP資源。IP資源從入庫到最終判定可用生命流程如下
- IP抓取
server監聽了很多代理IP網站,這些網站包括國內外十幾家,有意思的是drungProxy的IP爬蟲是一系列網站模版。五六行配置即可實現一個簡單的網站模版,然后我們有一個上層調度模塊將會負責調起模版進行數據抓取。 - IP消重
需要消重的原因是程序運行到一定時間之后,大量IP都是數據庫里面已經存在的了,這個時候如果在數據庫進行消重邏輯將會導致大量數據庫讀寫,實際上我們的服務器是一個1塊錢的騰訊雲(曾經是),看起來是撐不住這么大的請求的(平均每天可以有10K量級)。最后在入庫前設置了一個bloomFilter消重模塊,能夠高效的檢測資源是否被入庫過。 - 位置信息完善
這個邏輯不大,通過taobaoIp接口獲取地址信息,完善IP資源元數據。taobaoIP - IP驗證
IP驗證分為好幾個步驟。我們的IP總資源有80W,檢驗一個IP是否可用一般來說需要20秒左右的時間,因為代理IP本身響應比較慢,我們會把超時時間設置得比較長。所以可以計算一下80W數據走一輪將要消耗得時間,即使在多線程並行環境下時間也是很多的。為了在一定資源下完成校驗,我們設計了如下步驟
- 端口開啟校驗,在進行可用性校驗前,首先需要檢查IP端口是否開啟。調研發現大量資源其實端口都不通,所以專門設計一個任務驗證端口是否開啟,端口開啟驗證超時時間為5秒。由於大多數資源端口都沒有開啟,所以大部分資源的校驗時間下降到5秒了。
- 可用性校驗,進行可用性校驗的需要先進行端口開啟校驗,系統中端口開啟的資源大概3W,所以校驗可用性的總資源有3W左右。可用性校驗存在如下問題,很多代理IP其實不是代理網站,想他發送請求最終不是我們預期的數據,比如他返回給我們一個代理IP認證網頁。所以我們不能根據是否能夠請求到數據來判定IP是否可用。我們的做法是在公網放置一個API接口,然后控制代理IP訪問我們自己的接口,如果能夠拿到符合我們接口的預期數據,那么認為IP可用。
- domain可用該校驗,可用性校驗通過之后IP還不是真正可用,悲傷的發現代理IP是和域名相關的。所以同一個IP在不同域名下表現可能不一樣。所以我們維護了一個域名IP池,這里面存儲各個域名下可用IP
- IP分發
IP分發是根據客戶請求分配可用IP。分發邏輯現在還沒有完全完善,但是已經實現了最迫切和有校的分發方案。分發邏輯設計是:先嘗試查詢domainIP池,再根據其他請求參數做條件匹配,再查詢系統可用IP,再隨機選擇可用填充。四個步驟如果有一個步驟得到的IP超過請求參數期待數目,則不進行接下來的動作。
IP驗證模型
再IP驗證的時候,我們設計了一個模型用來確定哪些IP應該優先驗證。模型描述如下:長期可用IP檢測頻率低,長期不可用IP檢測評率低。不穩定IP和剛加入的IP檢測頻率高。我們使用優先隊列來實現這個邏輯,所有IP根據分值放在不同優先隊列中,每次校驗的時候再不同優先隊列中拿出一定資源進行校驗(不同優先級拿出的資源數目不一樣,高優先級的對象拿出更多資源),對於同一個優先隊列,我們根據最后驗證時間排序。使上次更新時間最久的資源被優先選擇。
分發去重
分發資源的時候,設計去重問題,也就是根據相同條件,每次分發得到的IP很大可能會重復。為了規避這個問題,每次分發都會相應的下發一個資源簽名,他會記錄分發過的IP。在下次請求的時候,客戶端需要帶上這個簽名,服務器會根據簽名過濾,同時會重新對新分發的IP資源做再次簽名.
server部署
server端使用java編寫,使用maven管理項目,使用mysql作為數據庫。相關技術包括springMVC,spring,tomcat,mybatis,guava,fastjson,httpclient等。
運行server的方式很簡單
- 在項目根目錄執行maven命令(需要提前安裝maven,maven安裝方式略)
mvn install -Dmaven.test.skip=true - 在server目錄執行maven命令
mvn tomcat7:run
server配置
直接運行項目使用的是我們的默認數據庫,同時使用的是默認配置。實際上server存在一些配置用來設置運行參數。合理的運行參數能夠合理使用機器資源以及達到更好的運行效果。
項目主要有兩個配置文件需要配置:
- mysql.properties 用來配置數據庫信息
- config.properties 配置其他啟動參數,主要需要關注里面幾個url地址,還有 system.thread.的參數項。system.thread用於指定某一種類型的任務執行的線程數,如果數據小於1,則這個模塊不會啟動。但是如果這個模塊接收到了任務請求,那么他會轉發到其他服務器上面(也就是上面的兩個forward相關的url,沒辦法服務器都是臘雞服務器 )
其他的應該沒有了把,哦對了,項目存在多個profile,也就是resources.local,resources.beta,resources.prod等。他們叫做profile,是maven里面的概念,默認是resources.local生效的。如果想使用其他profile下面的配置,則增加 -Pprofile參數,如運行server mvn -Pskyee clean tomcat7:run
server接口事例
{
"data": {
"data": [
{
"id": 257,
"ip": "203.192.12.148",
"proxyIp": "203.192.12.149",
"port": 80,
"ipValue": 3418360980,
"country": "中國",
"area": "華北",
"region": "北京市",
"city": "北京市",
"isp": "",
"countryId": "CN",
"areaId": "100000",
"regionId": "110000",
"cityId": "110100",
"ispId": "-1",
"transperent": 2,
"speed": 104,
"type": 1,
"connectionScore": 1310,
"availbelScore": 8,
"connectionScoreDate": 1475641264000,
"availbelScoreDate": 1475646860000,
"createtime": 1473840886000,
"lostheader": false
},
{
"id": 654,
"ip": "120.55.245.47",
"proxyIp": "112.124.119.21",
"port": 80,
"ipValue": 2016933167,
"country": "中國",
"area": "華東",
"region": "浙江省",
"city": "杭州市",
"isp": "阿里雲",
"countryId": "CN",
"areaId": "300000",
"regionId": "330000",
"cityId": "330100",
"ispId": "1000323",
"transperent": 2,
"speed": 83,
"type": 1,
"connectionScore": 1429,
"availbelScore": 2,
"connectionScoreDate": 1475659905000,
"availbelScoreDate": 1475630273000,
"createtime": 1473840884000,
"lostheader": false
},
{
"id": 2489,
"ip": "124.193.33.233",
"proxyIp": "124.193.33.233",
"port": 3128,
"ipValue": 2093031913,
"country": "中國",
"area": "華北",
"region": "北京市",
"city": "北京市",
"isp": "鵬博士",
"countryId": "CN",
"areaId": "100000",
"regionId": "110000",
"cityId": "110100",
"ispId": "1000143",
"transperent": 2,
"speed": 3390,
"type": 1,
"connectionScore": 310,
"availbelScore": 2,
"connectionScoreDate": 1475657685000,
"availbelScoreDate": 1475661878000,
"createtime": 1473839334000,
"lostheader": false
},
{
"id": 5004,
"ip": "203.192.12.146",
"proxyIp": "203.192.12.149",
"port": 80,
"ipValue": 3418360978,
"country": "中國",
"area": "華北",
"region": "北京市",
"city": "北京市",
"isp": "",
"countryId": "CN",
"areaId": "100000",
"regionId": "110000",
"cityId": "110100",
"ispId": "-1",
"transperent": 2,
"speed": 161,
"type": 1,
"connectionScore": 1291,
"availbelScore": 10,
"connectionScoreDate": 1475638336000,
"availbelScoreDate": 1475636727000,
"createtime": 1473840882000,
"lostheader": false
},
{
"id": 5421,
"ip": "221.237.155.64",
"proxyIp": "221.237.155.64",
"port": 9797,
"ipValue": 3723336512,
"country": "中國",
"area": "西南",
"region": "四川省",
"city": "成都市",
"isp": "電信",
"countryId": "CN",
"areaId": "500000",
"regionId": "510000",
"cityId": "510100",
"ispId": "100017",
"transperent": 2,
"speed": 3238,
"type": 1,
"connectionScore": 119,
"availbelScore": -1,
"connectionScoreDate": 1475611973000,
"availbelScoreDate": 1475629954000,
"createtime": 1473840773000,
"lostheader": false
},
{
"id": 8722,
"ip": "58.243.0.162",
"proxyIp": "58.243.0.162",
"port": 9999,
"ipValue": 989003938,
"country": "中國",
"area": "華東",
"region": "安徽省",
"city": "安慶市",
"isp": "聯通",
"countryId": "CN",
"areaId": "300000",
"regionId": "340000",
"cityId": "340800",
"ispId": "100026",
"transperent": 2,
"speed": 5143,
"type": 1,
"connectionScore": 154,
"availbelScore": -3,
"connectionScoreDate": 1475665673000,
"availbelScoreDate": 1475614147000,
"createtime": 1473839836000,
"lostheader": false
},
{
"id": 11698,
"ip": "218.7.170.190",
"proxyIp": "218.7.170.190",
"port": 3128,
"ipValue": 3657935550,
"country": "中國",
"area": "東北",
"region": "黑龍江省",
"city": "綏化市",
"isp": "聯通",
"countryId": "CN",
"areaId": "200000",
"regionId": "230000",
"cityId": "231200",
"ispId": "100026",
"transperent": 2,
"speed": 3145,
"type": 1,
"connectionScore": 317,
"availbelScore": -1,
"connectionScoreDate": 1475642001000,
"availbelScoreDate": 1475524810000,
"createtime": 1473839128000,
"lostheader": false
},
{
"id": 13318,
"ip": "220.249.185.178",
"proxyIp": "220.249.185.178",
"port": 9999,
"ipValue": 3707353522,
"country": "中國",
"area": "華東",
"region": "福建省",
"city": "福州市",
"isp": "聯通",
"countryId": "CN",
"areaId": "300000",
"regionId": "350000",
"cityId": "350100",
"ispId": "100026",
"transperent": 2,
"speed": 5094,
"type": 1,
"connectionScore": 129,
"availbelScore": -1,
"connectionScoreDate": 1475615670000,
"availbelScoreDate": 1475585178000,
"createtime": 1473840539000,
"lostheader": false
},
{
"id": 57033,
"ip": "210.245.25.228",
"proxyIp": "210.245.25.228",
"port": 3128,
"ipValue": 3539278308,
"country": "越南",
"area": "",
"region": "",
"city": "",
"isp": "",
"countryId": "VN",
"areaId": "",
"regionId": "",
"cityId": "",
"ispId": "",
"transperent": 2,
"speed": 1024,
"type": 1,
"connectionScore": 488,
"availbelScore": 36,
"connectionScoreDate": 1475635386000,
"availbelScoreDate": 1475630473000,
"createtime": 1473836572000,
"lostheader": false
},
{
"id": 124334,
"ip": "60.194.72.253",
"proxyIp": "60.194.72.253",
"port": 3128,
"ipValue": 1019365629,
"country": "中國",
"area": "華北",
"region": "北京市",
"city": "北京市",
"isp": "鵬博士",
"countryId": "CN",
"areaId": "100000",
"regionId": "110000",
"cityId": "110100",
"ispId": "1000143",
"transperent": 2,
"speed": 2366,
"type": 1,
"connectionScore": 610,
"availbelScore": 16,
"connectionScoreDate": 1475643516000,
"availbelScoreDate": 1475631080000,
"createtime": 1473839561000,
"lostheader": false
}
],
"num": 10,
"sign": "9999#C99+999#9B99B99999##Y9999+9999999999999999999999t9999s99999999s9999999999999999999999999999#99999999999999GB999999999G9999s9s99999#9999999999Y9+999##99999999+99999999999999+999999999999B999+Y9999G9+99999999999YB99999999999999999999999+99Y999999999B9999G999s99G999999999#99999#9Y999s999999999#B99999999999999999999+999999Y9999999Y9999999999999Y9999Y999999999999999"
},
"status": true
}
proxyipcenter client
client 設計文檔
client 使用文檔
client運行原理
這里講述IP池的設計相關,如果您僅僅是為了使用dunproxy-client,則不必關心本文內容
client就是一個代理IP池的實現,IP池的設計基於兩個點:
-
代理IP都是不穩定的,不可靠的,需要一個機制來切換IP,盡可能使用高質量IP。
-
IP和環境關系很大,同一個IP在不同的機器下訪問不同的目標網站,其可用性表現都是不一樣的
使用IpPoolAPI獲取IP資源
獲取IP
獲取一個IP的方式是這樣的 IpPool.getInstance().bind(domain, accessUrl);
- 第一個參數是域名,可以傳遞null,傳遞null提取accessUrl schema里面的host
- 第二個參數是你當前需要訪問的url, 可以為null,為null時domain不能為空
曾經有一個綁定用戶的功能,使得同一個賬戶每次獲取的IP相同,后來覺得是過度設計,因為對於抓取場景,切換IP是很普通的需求,而且貌似沒有多少server會檢查常用IP。就算有也是小眾需求,本框架不必支持
記錄IP使用
每當使用IP的時候,需要記錄一次IP使用,也就是將IP實例的使用次數加一,用於打分機制計算IP的使用分值,評估IP可用性
方式如下:com.virjar.dungproxy.client.model.AvProxy.recordUsage
記錄IP使用失敗
每當IP使用失敗的時候,需要記錄IP使用失敗,也就是將IP實例的失敗次數加一,用於打分機制計算IP的使用分值,評估IP可用性
方式如下:com.virjar.dungproxy.client.model.AvProxy.recordFailed()
IP下線
IP下線很簡單,拿到IP實例,這樣調用com.virjar.dungproxy.client.model.AvProxy.offline()。
一般情況不建議這么做,因為IP池會自動檢查IP是否應該下線,IP池可以定制各種策略。當時,有些時候IP池的檢查機制比較緩慢,而上層業務可以明確知道本IP不可用,這個時候可以使用本API強制下線
銷毀IP池實例
IP池是單例的,同時里面維護了兩個任務線程,在業務完成的時候,需要銷毀IP池才能終止內部線程。同時也會執行一些收尾工作,如將可用IP dump。
銷毀方式是:com.virjar.dungproxy.client.ippool.IpPool.destroy
其他
IP池有其他很多擴展點,但是目前接口沒有開發完成,待后續完善
TODO 幾個主流語言的客戶端
- .NET/.NET core client (辣雞樓主還沒動手...)
- Python client(另外一個大佬好像忘了這事了...)
- Golang client(可能還是辣雞樓主動手...)
