在使用scrapy爬蟲做性能優化時,一定要根據不同網站的特點來進行優化,不要使用一種固定的模式去爬取一個網站,這個是真理,以下是對58同城的爬取優化策略:
一、先來分析一下影響scrapy性能的settings設置(部分常用設置):
1,DOWNLOAD_TIMEOUT,下載超時,默認180S,若超時則會被retry中間件進行處理,重新加入請求隊列
2019-04-18 20:23:18 [scrapy.downloadermiddlewares.retry] DEBUG: Retrying <GET https://bj.58.com/ershoufang/37767297466392x.shtml> (failed 1 times): User timeout caused connection failure: Getting https://bj.58.com/ershoufang/37767297466392x.shtml took longer than 180.0 seconds..
一般來說訪問一個網站都是以ms作為單位的,180S確實有些太長了,而且,由於默認設置了最大並發數為16,導致這些request沒有下載到東西還一直占據着並發數,在我的日志文件中就能大量看到retry日志
2.DOWNLOAD_DELAY,該選項默認為0,即在下載是並發執行,若設置為x,則每隔 0.5*random~1.5*random 秒下載下一個url,影響當然很大,很多時候還是建議給個正值,避免直接把服務器弄炸了,而且在IP准備不充分情況下,也有利於爬蟲的持續運行,對雙方都有好處
3.CONCURRENT_REQUESTS ,默認為16,下載器下載的並發數,建議調高,根據scrapy發揮到cpu核心性能80~90%取適應值,若服務器有反爬措施,自身准備IP又不充足情況下,建議調低;
scrapy 是基於 twisted 的異步 IO,只用到單線程,若考慮部署一台服務器專用於爬蟲,請選擇更高的單核性能,關於scrapy單線程的證明:
設置CONCURRENT_REQUESTS=100,讓scrapy爬取一個超過100個鏈接的頁面,檢測下載前后線程數量,windows上直接在任務管理器就能看到了,沒有放對比圖,就我的觀測來看,爬取前后線程數量是沒有增加的
4.CONCURRENT_REQUESTS_PER_IP ,下載器下載時針對每個IP的並發數,默認是0,這里0指不限制的意思,取值視IP充足情況和CONCURRENT_REQUESTS決定,若DOWNLOAD_DELAY非零,搭配使用可以實現針對IP做並發下載的download_delay,如CONCURRENT_REQUESTS=16,CONCURRENT_REQUESTS_PER_IP =3,DOWNLOAD_DELAY = 2
當前下載器正在並發下載16個Request,ip:https://201.182.248.10:8080被隨機分配到4個Request中,那么有一個使用這個ip代理Request從下載隊列中出局,並且在2秒鍾內下一個Request攜帶的代理仍是這個ip時,仍然會被出局,這還是我的一個猜想,有空做一個合適的模型證明一下。
二、優化方案
在執行以下每個策略之前都會清除掉上一次保存在數據庫中的item,並且重新獲取一定數量的優秀代理供使用,這次優化評判標准是爬取到50條ITEM,比較哪種設置的爬蟲用到的時間更短,
初始設置:
CLOSESPIDER_ITEMCOUNT = 50
DOWNLOAD_DELAY=0.5
CONCURRENT_REQUESTS = 16
結果:
用時:7分21秒
策略1:比原設置更少的DOWNLOAD_TIMEOUT
設置:
CLOSESPIDER_ITEMCOUNT = 50
DOWNLOAD_DELAY=0.5
CONCURRENT_REQUESTS = 16
DOWNLOAD_TIMEOUT = 15
結果:
用時:2分20秒
結果分析:更少的DOWNLOAD_TIMEOUT換來更多的重試次數,但是由於下載超時大大減少,使scrapy性能大大提升。這個結果同時提供一種后續繼續優化思路:逐漸減小DOWNLOAD_TIMEOUT的值,使得爬蟲用時減少而重試次數增加的較慢,那么這應該就是一個合適的DOWNLOAD_TIMEOUT取值
策略2:比策略1更少的DOWNLOAD_DELAY,直接注釋,使用默認0延遲
設置:
CLOSESPIDER_ITEMCOUNT = 50
#DOWNLOAD_DELAY=0.5
CONCURRENT_REQUESTS = 16
DOWNLOAD_TIMEOUT = 15
結果:
用時:2分19秒
結果分析:似乎和策略1結果差別不大,應該是策略1中設置的TIMEOUT_DELAY較小,在我把策略1換更大的TIMEOUT_DELAY之后,爬取用了10多分鍾,后來沒等,直接關閉了,就沒上結果圖片了。
策略3:提供比策略2更大的CONCURRENT_REQUESTS
設置:
CLOSESPIDER_ITEMCOUNT = 50
#DOWNLOAD_DELAY=0.5
CONCURRENT_REQUESTS = 50
DOWNLOAD_TIMEOUT = 15
結果:
用時:4分25秒
結果分析:這和前面的scrapy性能分析完全不符,並發數越大應該爬取速度更快才對,現在用了原來近2倍時間。來分析一下原因,可以看到,對比策略2的日志圖片,策略3請求數量明顯增大,223 -- >318,重試次數也明顯增大,29->101,並且,此項目中使用的ip都是從本地文件中的ip隨機獲得的,且並沒有在獲取一個ip之后刪除掉這個ip,可以這樣分析:並發增多 -- >攜帶同一ip的並發請求也增多 -- > 被ban的ip也會增多 --> redirect到firewall的請求增多 -- > Request數量增多 --> retry數量增多,這樣很好解釋了日志中的結果。這個結果同時提供我們啟示:要想增大並發數以提高爬蟲效率,必須保證單次請求IP更高有效性,為了解決這個問題,后面我會借鑒git大神寫的Flask線程池,提高單次請求成功率。
策略4:CONCURRENT_REQUESTS_PER_IP,對ip做並發限制
CLOSESPIDER_ITEMCOUNT = 50
#DOWNLOAD_DELAY=0.5
CONCURRENT_REQUESTS = 16
CONCURRENT_REQUESTS_PER_IP = 1
DOWNLOAD_TIMEOUT = 15
結果:
用時:35分16秒
結果分析:scrapy對使用相同ip的並發請求的處理機制對這個結果有一定影響,更加根本原因還是和策略3一樣,源於ip有效性較差,下篇我將會使用flask搭建線程池以比較這四種策略的性能差異
總結:
從本例看,策略1應該就是最好的方案了,在允許少量下載延遲的情況下,也不需要大並發,照樣提供更好的性能,這也正如開頭所說的,對scrapy做性能優化還是要針對具體項目,具體條件。