使用代理服務(wù)器一直是爬蟲防BAN最有效的手段,但網(wǎng)上的免費(fèi)代理往往質(zhì)量很低,大部分代理完全不能使用,剩下能用的代理很多也只有幾分鐘的壽命,沒法直接用到爬蟲項(xiàng)目中。
下面簡單記錄一下我用scrapy+redis實(shí)現(xiàn)動(dòng)態(tài)代理池的過程。
我對(duì)“動(dòng)態(tài)代理池” 的需求
我的爬蟲項(xiàng)目需要7*24小時(shí)監(jiān)控若干個(gè)頁面,考慮了一下希望代理池能滿足下面幾個(gè)要求:
- 始終保持一個(gè)相對(duì)穩(wěn)定的代理數(shù)量
- 始終保持池內(nèi)代理的高可靠率(希望90%的代理都能用)
- 盡可能減少對(duì)爬蟲項(xiàng)目代碼的更改
參考過的項(xiàng)目
找過一些現(xiàn)成的輪子,但發(fā)現(xiàn)或多或少都不太符合我的需求
kohn/HttpProxyMiddleware
一個(gè)“被動(dòng)”選擇代理的方式。在scrapy middleware中加入大量代碼,讓爬蟲在代理失效后再去代理網(wǎng)站找代理,會(huì)有下面幾個(gè)問題:
- 大多數(shù)情況下始終使用一個(gè)代理去訪問,造成流量集中在一個(gè)IP上,可能導(dǎo)致代理IP被BAN。
- 切換代理的過程比較耗時(shí),導(dǎo)致爬蟲性能下降比較厲害
- 所有邏輯都嵌套在了爬蟲項(xiàng)目里面,有兼容性和可移植性方面的問題(有多個(gè)爬蟲的話每個(gè)都要改一遍..)
- 基于python2寫的,我的項(xiàng)目在python3上..
jhao104/proxy_pool
這個(gè)幾乎和我想要的一樣,但還是因?yàn)橄旅鎺c(diǎn)原因沒有使用:
- 基于SSDB,這邊沒有現(xiàn)成的SSDB,因?yàn)橐呀?jīng)在用Redis了不想專門去裝一個(gè)
- 只管定時(shí)收集代理,在收集過程中做一次驗(yàn)證。但事實(shí)上收集到的代理即便通過了驗(yàn)證,也可能活不過10分鐘,時(shí)間一長代理池內(nèi)就會(huì)有很多過期的代理,需要在爬蟲代碼中處理這些過期代理(爬蟲代碼調(diào)用delete刪除)。
設(shè)計(jì)思路和代碼
看了那么多方案,我就在想:能不能讓代理池具有自我檢查自我修復(fù)的功能?這樣我們的爬蟲就只需隨機(jī)拿一個(gè)代理用就可以了,就算恰巧拿到一個(gè)失效代理,只要做一次retry,代理池的修復(fù)機(jī)制可以保證retry的時(shí)候失效的代理已經(jīng)被移走了,不會(huì)再次取到。
簡單規(guī)劃一下:
- 我們需要一個(gè)自檢程序,它每10S跑一次,保證代理池內(nèi)所有代理至少在10S內(nèi)有效
- 我們需要一個(gè)代理獲取的程序,當(dāng)代理池內(nèi)代理數(shù)量過低時(shí)(閾值定為<5),訪問免費(fèi)代理網(wǎng)站補(bǔ)充新代理
- 我們需要一個(gè)調(diào)度程序,用來監(jiān)控代理池?cái)?shù)量并調(diào)用上面兩個(gè)程序,維持代理池平衡。
- 作為私有代理池就維護(hù)在redis了(SET),讓爬蟲直接從redis里取代理。
對(duì)應(yīng)的代碼就有這么三個(gè)部分:
- 一個(gè)scrapy爬蟲去爬代理網(wǎng)站,獲取免費(fèi)代理,驗(yàn)證后入庫 (proxy_fetch)
- 一個(gè)scrapy爬蟲把代理池內(nèi)的代理全部驗(yàn)證一遍,若驗(yàn)證失敗就從代理池內(nèi)刪除 (proxy_check)
- 一個(gè)調(diào)度程序用于管理上面兩個(gè)爬蟲 (start.py)
強(qiáng)行畫個(gè)流程圖:

爬蟲部分全部用scrapy寫了,其實(shí)沒必要,用requests就夠了..但做的時(shí)候剛學(xué)scrapy,就順便練練手了..
start.py的調(diào)度方式比較粗暴,直接起兩個(gè)線程,在線程內(nèi)用os.system調(diào)用scrapy爬蟲(畢竟輕量級(jí)代理池嘛..好吧其實(shí)就是我懶圖個(gè)方便..)
另外還有幾個(gè)調(diào)度策略需要說一下:
- 每次調(diào)用proxy_fetch后會(huì)自動(dòng)設(shè)定一個(gè)“保護(hù)時(shí)間”10分鐘,在保護(hù)期內(nèi)除非代理池只剩一個(gè)代理了,否則不會(huì)觸發(fā)proxy_fetch,避免頻繁調(diào)用。
- 加入了一個(gè)“刷新時(shí)間”24小時(shí),保證每24小時(shí)內(nèi)至少執(zhí)行proxy_fetch一次
- 驗(yàn)證程序設(shè)定timeout為5秒,5秒內(nèi)沒訪問到測(cè)試頁面就認(rèn)為驗(yàn)證失敗
其他細(xì)節(jié)可以看源碼:arthurmmm/hq-proxies
部署和使用
需要先改一下配置文件hq-proxies.yml,把Redis的地址密碼之類的填上,改完后放到/etc/hq-proxies.yml下。
在配置文件中也可以調(diào)整相應(yīng)的閾值和免費(fèi)代理源和測(cè)試頁面。
測(cè)試頁面需要頻繁訪問,為了節(jié)省流量我在某云存儲(chǔ)上丟了個(gè)helloworld的文本當(dāng)測(cè)試頁面了,云存儲(chǔ)有流量限制建議大家換掉。。驗(yàn)證方式很粗暴,比較一下網(wǎng)頁開頭字符串。。
另外寫了個(gè)Dockerfile可以直接部署到Docker上(python3用的是Daocloud的鏡像),跑容器的時(shí)候記得把hq-proxies.yml映射到容器/etc/hq-proxies.yml下。
手工部署的話跑pip install -r requirements.txt安裝依賴包
在scrapy中使用代理池的只需要添加一個(gè)middleware,每次爬取時(shí)從redis SET里用srandmember隨機(jī)獲取一個(gè)代理使用,代理失效和一般的請(qǐng)求超時(shí)一樣retry,代理池的自檢特性保證了我們r(jià)etry時(shí)候再次拿到失效代理的概率很低。middleware代碼示例:
class DynamicProxyMiddleware(object):
def process_request(self, request, spider):
redis_db = StrictRedis(
host=LOCAL_CONFIG['REDIS_HOST'],
port=LOCAL_CONFIG['REDIS_PORT'],
password=LOCAL_CONFIG['REDIS_PASSWORD'],
db=LOCAL_CONFIG['REDIS_DB']
)
proxy = redis_db.sismember(PROXY_SET, proxy):
logger.debug('使用代理[%s]訪問[%s]' % (proxy, request.url))
request.meta['proxy'] = proxy
在其他爬蟲框架下使用也是類似的。
最后放一張萌萌噠日志菌觸發(fā)proxy_fetch時(shí)候的截圖:
