因?yàn)樗阉饕娴牧餍?,網(wǎng)絡(luò)爬蟲已經(jīng)成了很普及網(wǎng)絡(luò)技術(shù),除了專門做搜索的Google,Yahoo,微軟,百度以外,幾乎每個(gè)大型 門戶網(wǎng)站都有自己的搜索引擎,大大小小叫得出來(lái)名字得就幾十種,還有各種不知名的幾千幾萬(wàn)種,對(duì)于一個(gè)內(nèi)容型驅(qū)動(dòng)的網(wǎng)站來(lái)說(shuō),受到網(wǎng)絡(luò)爬蟲的光顧是不可避 免的。
一些智能的搜索引擎爬蟲的爬取頻率比較合理,對(duì)網(wǎng)站資源消耗比較少,但是很多糟糕的網(wǎng)絡(luò)爬蟲,對(duì)網(wǎng)頁(yè)爬取能力很差,經(jīng)常并發(fā)幾十上百個(gè)請(qǐng)求循環(huán)重復(fù) 抓取,這種爬蟲對(duì)中小型網(wǎng)站往往是毀滅性打擊,特別是一些缺乏爬蟲編寫經(jīng)驗(yàn)的程序員寫出來(lái)的爬蟲破壞力極強(qiáng),造成的網(wǎng)站訪問壓力會(huì)非常大,會(huì)導(dǎo)致網(wǎng)站訪問 速度緩慢,甚至無(wú)法訪問。
手工識(shí)別和拒絕爬蟲的訪問
相當(dāng)多的爬蟲對(duì)網(wǎng)站會(huì)造成非常高的負(fù)載,因此識(shí)別爬蟲的來(lái)源IP是很容易的事情。最簡(jiǎn)單的辦法就是用netstat檢查80端口的連接:
netstat -nt | grep youhostip:80 | awk '{print $5}' | awk -F":" '{print $1}'| sort | uniq -c | sort -r -n
這行shell可以按照80端口連接數(shù)量對(duì)來(lái)源IP進(jìn)行排序,這樣可以直觀的判斷出來(lái)網(wǎng)頁(yè)爬蟲。一般來(lái)說(shuō)爬蟲的并發(fā)連接非常高。
如果使用lighttpd做Web Server,那么就更簡(jiǎn)單了。lighttpd的mod_status提供了非常直觀的并發(fā)連接的信息,包括每個(gè)連接的來(lái)源IP,訪問的URL,連接狀 態(tài)和連接時(shí)間等信息,只要檢查那些處于handle-request狀態(tài)的高并發(fā)IP就可以很快確定爬蟲的來(lái)源IP了。
拒絕爬蟲請(qǐng)求既可以通過內(nèi)核防火墻來(lái)拒絕,也可以在web server拒絕,比方說(shuō)用iptables拒絕:
iptables -A INPUT -i eth0 -j DROP -p tcp --dport 80 -s 84.80.46.0/24
直接封鎖爬蟲所在的C網(wǎng)段地址。這是因?yàn)橐话闩老x都是運(yùn)行在托管機(jī)房里面,可能在一個(gè)C段里面的多臺(tái)服務(wù)器上面都有爬蟲,而這個(gè)C段不可能是用戶寬帶上網(wǎng),封鎖C段可以很大程度上解決問題。
通過識(shí)別爬蟲的User-Agent信息來(lái)拒絕爬蟲
有很多爬蟲并不會(huì)以很高的并發(fā)連接爬取,一般不容易暴露自己;有些爬蟲的來(lái)源IP分布很廣,很難簡(jiǎn)單的通過封鎖IP段地址來(lái)解決問題;另外還有很多 各種各樣的小爬蟲,它們?cè)趪L試Google以外創(chuàng)新的搜索方式,每個(gè)爬蟲每天爬取幾萬(wàn)的網(wǎng)頁(yè),幾十個(gè)爬蟲加起來(lái)每天就能消耗掉上百萬(wàn)動(dòng)態(tài)請(qǐng)求的資源,由于 每個(gè)小爬蟲單獨(dú)的爬取量都很低,所以你很難把它從每天海量的訪問IP地址當(dāng)中把它準(zhǔn)確的挖出來(lái)。
這種情況下我們可以通過爬蟲的User-Agent信息來(lái)識(shí)別。每個(gè)爬蟲在爬取網(wǎng)頁(yè)的時(shí)候,會(huì)聲明自己的User-Agent信息,因此我們就可以 通過記錄和分析User-Agent信息來(lái)挖掘和封鎖爬蟲。我們需要記錄每個(gè)請(qǐng)求的User-Agent信息,對(duì)于Rails來(lái)說(shuō)我們可以簡(jiǎn)單的在 app/controllers/application.rb里面添加一個(gè)全局的before_filter,來(lái)記錄每個(gè)請(qǐng)求的User-Agent信 息:
logger.info "HTTP_USER_AGENT #{request.env["HTTP_USER_AGENT"]}"
然后統(tǒng)計(jì)每天的production.log,抽取User-Agent信息,找出訪問量最大的那些User-Agent。要注意的是我們只關(guān)注那 些爬蟲的User-Agent信息,而不是真正瀏覽器User-Agent,所以還要排除掉瀏覽器User-Agent,要做到這一點(diǎn)僅僅需要一行 shell:
grep HTTP_USER_AGENT production.log | grep -v -E 'MSIE|Firefox|Chrome|Opera|Safari|Gecko' | sort | uniq -c | sort -r -n | head -n 100 > bot.log
統(tǒng)計(jì)結(jié)果類似這樣:
57335 HTTP_USER_AGENT Baiduspider+(+http://www.baidu.com/search/spider.htm)
56639 HTTP_USER_AGENT Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
42610 HTTP_USER_AGENT Mediapartners-Google
19131 HTTP_USER_AGENT msnbot/2.0b (+http://search.msn.com/msnbot.htm)
從日志就可以直觀的看出每個(gè)爬蟲的請(qǐng)求次數(shù)。要根據(jù)User-Agent信息來(lái)封鎖爬蟲是件很容易的事情,lighttpd配置如下:
$HTTP["useragent"] =~ "qihoobot|^Java|Commons-HttpClient|Wget|^PHP|Ruby|Python" {
url.rewrite = ( "^/(.*)" => "/crawler.html" )
}
使用這種方式來(lái)封鎖爬蟲雖然簡(jiǎn)單但是非常有效,除了封鎖特定的爬蟲,還可以封鎖常用的編程語(yǔ)言和HTTP類庫(kù)的User-Agent信息,這樣就可以避免很多無(wú)謂的程序員用來(lái)練手的爬蟲程序?qū)W(wǎng)站的騷擾。
還有一種比較常見的情況,就是某個(gè)搜索引擎的爬蟲對(duì)網(wǎng)站爬取頻率過高,但是搜索引擎給網(wǎng)站帶來(lái)了很多流量,我們并不希望簡(jiǎn)單的封鎖爬蟲,僅僅是希望降低爬蟲的請(qǐng)求頻率,減輕爬蟲對(duì)網(wǎng)站造成的負(fù)載,那么我們可以這樣做:
$HTTP["user-agent"] =~ "Baiduspider+" {
connection.delay-seconds = 10
}
對(duì)百度的爬蟲請(qǐng)求延遲10秒鐘再進(jìn)行處理,這樣就可以有效降低爬蟲對(duì)網(wǎng)站的負(fù)載了。
通過網(wǎng)站流量統(tǒng)計(jì)系統(tǒng)和日志分析來(lái)識(shí)別爬蟲
有些爬蟲喜歡修改User-Agent信息來(lái)偽裝自己,把自己偽裝成一個(gè)真實(shí)瀏覽器的User-Agent信息,讓你無(wú)法有效的識(shí)別。這種情況下我們可以通過網(wǎng)站流量系統(tǒng)記錄的真實(shí)用戶訪問IP來(lái)進(jìn)行識(shí)別。
主流的網(wǎng)站流量統(tǒng)計(jì)系統(tǒng)不外乎兩種實(shí)現(xiàn)策略:一種策略是在網(wǎng)頁(yè)里面嵌入一段js,這段js會(huì)向特定的統(tǒng)計(jì)服務(wù)器發(fā)送請(qǐng)求的方式記錄訪問量;另一種策 略是直接分析服務(wù)器日志,來(lái)統(tǒng)計(jì)網(wǎng)站訪問量。在理想的情況下,嵌入js的方式統(tǒng)計(jì)的網(wǎng)站流量應(yīng)該高于分析服務(wù)器日志,這是因?yàn)橛脩魹g覽器會(huì)有緩存,不一定 每次真實(shí)用戶訪問都會(huì)觸發(fā)服務(wù)器的處理。但實(shí)際情況是,分析服務(wù)器日志得到的網(wǎng)站訪問量遠(yuǎn)遠(yuǎn)高于嵌入js方式,極端情況下,甚至要高出10倍以上。
現(xiàn)在很多網(wǎng)站喜歡采用awstats來(lái)分析服務(wù)器日志,來(lái)計(jì)算網(wǎng)站的訪問量,但是當(dāng)他們一旦采用Google Analytics來(lái)統(tǒng)計(jì)網(wǎng)站流量的時(shí)候,卻發(fā)現(xiàn)GA統(tǒng)計(jì)的流量遠(yuǎn)遠(yuǎn)低于awstats,為什么GA和awstats統(tǒng)計(jì)會(huì)有這么大差異呢?罪魁禍?zhǔn)拙褪?把自己偽裝成瀏覽器的網(wǎng)絡(luò)爬蟲。這種情況下awstats無(wú)法有效的識(shí)別了,所以awstats的統(tǒng)計(jì)數(shù)據(jù)會(huì)虛高。
其實(shí)作為一個(gè)網(wǎng)站來(lái)說(shuō),如果希望了解自己的網(wǎng)站真實(shí)訪問量,希望精確了解網(wǎng)站每個(gè)頻道的訪問量和訪問用戶,應(yīng)該用頁(yè)面里面嵌入js的方式來(lái)開發(fā)自己 的網(wǎng)站流量統(tǒng)計(jì)系統(tǒng)。自己做一個(gè)網(wǎng)站流量統(tǒng)計(jì)系統(tǒng)是件很簡(jiǎn)單的事情,寫段服務(wù)器程序響應(yīng)客戶段js的請(qǐng)求,分析和識(shí)別請(qǐng)求然后寫日志的同時(shí)做后臺(tái)的異步統(tǒng) 計(jì)就搞定了。
通過流量統(tǒng)計(jì)系統(tǒng)得到的用戶IP基本是真實(shí)的用戶訪問,因?yàn)橐话闱闆r下爬蟲是無(wú)法執(zhí)行網(wǎng)頁(yè)里面的js代碼片段的。所以我們可以拿流量統(tǒng)計(jì)系統(tǒng)記錄的 IP和服務(wù)器程序日志記錄的IP地址進(jìn)行比較,如果服務(wù)器日志里面某個(gè)IP發(fā)起了大量的請(qǐng)求,在流量統(tǒng)計(jì)系統(tǒng)里面卻根本找不到,或者即使找得到,可訪問量 卻只有寥寥幾個(gè),那么無(wú)疑就是一個(gè)網(wǎng)絡(luò)爬蟲。
分析服務(wù)器日志統(tǒng)計(jì)訪問最多的IP地址段一行shell就可以了:
grep Processing production.log | awk '{print $4}' | awk -F'.' '{print $1"."$2"."$3".0"}' | sort | uniq -c | sort -r -n | head -n 200 > stat_ip.log
然后把統(tǒng)計(jì)結(jié)果和流量統(tǒng)計(jì)系統(tǒng)記錄的IP地址進(jìn)行對(duì)比,排除真實(shí)用戶訪問IP,再排除我們希望放行的網(wǎng)頁(yè)爬蟲,比方Google,百度,微軟msn爬蟲等等。最后的分析結(jié)果就就得到了爬蟲的IP地址了。以下代碼段是個(gè)簡(jiǎn)單的實(shí)現(xiàn)示意:
whitelist = []
IO.foreach("#{RAILS_ROOT}/lib/whitelist.txt") { |line| whitelist << line.split[0].strip if line }
realiplist = []
IO.foreach("#{RAILS_ROOT}/log/visit_ip.log") { |line| realiplist << line.strip if line }
iplist = []
IO.foreach("#{RAILS_ROOT}/log/stat_ip.log") do |line|
ip = line.split[1].strip
iplist << ip if line.split[0].to_i > 3000 && !whitelist.include?(ip) && !realiplist.include?(ip)
end
Report.deliver_crawler(iplist)
分析服務(wù)器日志里面請(qǐng)求次數(shù)超過3000次的IP地址段,排除白名單地址和真實(shí)訪問IP地址,最后得到的就是爬蟲IP了,然后可以發(fā)送郵件通知管理員進(jìn)行相應(yīng)的處理。
網(wǎng)站的實(shí)時(shí)反爬蟲防火墻實(shí)現(xiàn)策略
通過分析日志的方式來(lái)識(shí)別網(wǎng)頁(yè)爬蟲不是一個(gè)實(shí)時(shí)的反爬蟲策略。如果一個(gè)爬蟲非要針對(duì)你的網(wǎng)站進(jìn)行處心積慮的爬取,那么他可能會(huì)采用分布式爬取策略, 比方說(shuō)尋找?guī)装偕锨€(gè)國(guó)外的代理服務(wù)器瘋狂的爬取你的網(wǎng)站,從而導(dǎo)致網(wǎng)站無(wú)法訪問,那么你再分析日志是不可能及時(shí)解決問題的。所以必須采取實(shí)時(shí)反爬蟲策 略,要能夠動(dòng)態(tài)的實(shí)時(shí)識(shí)別和封鎖爬蟲的訪問。
要自己編寫一個(gè)這樣的實(shí)時(shí)反爬蟲系統(tǒng)其實(shí)也很簡(jiǎn)單。比方說(shuō)我們可以用memcached來(lái)做訪問計(jì)數(shù)器,記錄每個(gè)IP的訪問頻度,在單位時(shí)間之內(nèi), 如果訪問頻率超過一個(gè)閥值,我們就認(rèn)為這個(gè)IP很可能有問題,那么我們就可以返回一個(gè)驗(yàn)證碼頁(yè)面,要求用戶填寫驗(yàn)證碼。如果是爬蟲的話,當(dāng)然不可能填寫驗(yàn) 證碼,所以就被拒掉了,這樣很簡(jiǎn)單就解決了爬蟲問題。
用memcache記錄每個(gè)IP訪問計(jì)數(shù),單位時(shí)間內(nèi)超過閥值就讓用戶填寫驗(yàn)證碼,用Rails編寫的示例代碼如下:
ip_counter = Rails.cache.increment(request.remote_ip)
if !ip_counter
Rails.cache.write(request.remote_ip, 1, :expires_in => 30.minutes)
elsif ip_counter > 2000
render :template => 'test', :status => 401 and return false
end
這段程序只是最簡(jiǎn)單的示例,實(shí)際的代碼實(shí)現(xiàn)我們還會(huì)添加很多判斷,比方說(shuō)我們可能要排除白名單IP地址段,要允許特定的User-Agent通過,要針對(duì)登錄用戶和非登錄用戶,針對(duì)有無(wú)referer地址采取不同的閥值和計(jì)數(shù)加速器等等。
此外如果分布式爬蟲爬取頻率過高的話,過期就允許爬蟲再次訪問還是會(huì)對(duì)服務(wù)器造成很大的壓力,因此我們可以添加一條策略:針對(duì)要求用戶填寫驗(yàn)證碼的 IP地址,如果該IP地址短時(shí)間內(nèi)繼續(xù)不停的請(qǐng)求,則判斷為爬蟲,加入黑名單,后續(xù)請(qǐng)求全部拒絕掉。為此,示例代碼可以改進(jìn)一下:
before_filter :ip_firewall, :except => :test
def ip_firewall
render :file => "#{RAILS_ROOT}/public/403.html", :status => 403 if BlackList.include?(ip_sec)
end
我們可以定義一個(gè)全局的過濾器,對(duì)所有請(qǐng)求進(jìn)行過濾,出現(xiàn)在黑名單的IP地址一律拒絕。對(duì)非黑名單的IP地址再進(jìn)行計(jì)數(shù)和統(tǒng)計(jì):
ip_counter = Rails.cache.increment(request.remote_ip)
if !ip_counter
Rails.cache.write(request.remote_ip, 1, :expires_in => 30.minutes)
elsif ip_counter > 2000
crawler_counter = Rails.cache.increment("crawler/#{request.remote_ip}")
if !crawler_counter
Rails.cache.write("crawler/#{request.remote_ip}", 1, :expires_in => 10.minutes)
elsif crawler_counter > 50
BlackList.add(ip_sec)
render :file => "#{RAILS_ROOT}/public/403.html", :status => 403 and return false
end
render :template => 'test', :status => 401 and return false
end
如果某個(gè)IP地址單位時(shí)間內(nèi)訪問頻率超過閥值,再增加一個(gè)計(jì)數(shù)器,跟蹤他會(huì)不會(huì)立刻填寫驗(yàn)證碼,如果他不填寫驗(yàn)證碼,在短時(shí)間內(nèi)還是高頻率訪問,就 把這個(gè)IP地址段加入黑名單,除非用戶填寫驗(yàn)證碼激活,否則所有請(qǐng)求全部拒絕。這樣我們就可以通過在程序里面維護(hù)黑名單的方式來(lái)動(dòng)態(tài)的跟蹤爬蟲的情況,甚 至我們可以自己寫個(gè)后臺(tái)來(lái)手工管理黑名單列表,了解網(wǎng)站爬蟲的情況。
關(guān)于這個(gè)通用反爬蟲的功能,我們開發(fā)一個(gè)開源的插件:https://github.com/csdn-dev/limiter
這個(gè)策略已經(jīng)比較智能了,但是還不夠好!我們還可以繼續(xù)改進(jìn):
1、用網(wǎng)站流量統(tǒng)計(jì)系統(tǒng)來(lái)改進(jìn)實(shí)時(shí)反爬蟲系統(tǒng)
還記得嗎?網(wǎng)站流量統(tǒng)計(jì)系統(tǒng)記錄的IP地址是真實(shí)用戶訪問IP,所以我們?cè)诰W(wǎng)站流量統(tǒng)計(jì)系統(tǒng)里面也去操作memcached,但是這次不是增加計(jì)數(shù) 值,而是減少計(jì)數(shù)值。在網(wǎng)站流量統(tǒng)計(jì)系統(tǒng)里面每接收到一個(gè)IP請(qǐng)求,就相應(yīng)的cache.decrement(key)。所以對(duì)于真實(shí)用戶的IP來(lái)說(shuō),它 的計(jì)數(shù)值總是加1然后就減1,不可能很高。這樣我們就可以大大降低判斷爬蟲的閥值,可以更加快速準(zhǔn)確的識(shí)別和拒絕掉爬蟲。
2、用時(shí)間窗口來(lái)改進(jìn)實(shí)時(shí)反爬蟲系統(tǒng)
爬蟲爬取網(wǎng)頁(yè)的頻率都是比較固定的,不像人去訪問網(wǎng)頁(yè),中間的間隔時(shí)間比較無(wú)規(guī)則,所以我們可以給每個(gè)IP地址建立一個(gè)時(shí)間窗口,記錄IP地址最近 12次訪問時(shí)間,每記錄一次就滑動(dòng)一次窗口,比較最近訪問時(shí)間和當(dāng)前時(shí)間,如果間隔時(shí)間很長(zhǎng)判斷不是爬蟲,清除時(shí)間窗口,如果間隔不長(zhǎng),就回溯計(jì)算指定時(shí) 間段的訪問頻率,如果訪問頻率超過閥值,就轉(zhuǎn)向驗(yàn)證碼頁(yè)面讓用戶填寫驗(yàn)證碼。
最終這個(gè)實(shí)時(shí)反爬蟲系統(tǒng)就相當(dāng)完善了,它可以很快的識(shí)別并且自動(dòng)封鎖爬蟲的訪問,保護(hù)網(wǎng)站的正常訪問。不過有些爬蟲可能相當(dāng)狡猾,它也許會(huì)通過大量 的爬蟲測(cè)試來(lái)試探出來(lái)你的訪問閥值,以低于閥值的爬取速度抓取你的網(wǎng)頁(yè),因此我們還需要輔助第3種辦法,用日志來(lái)做后期的分析和識(shí)別,就算爬蟲爬的再慢, 它累計(jì)一天的爬取量也會(huì)超過你的閥值被你日志分析程序識(shí)別出來(lái)。
總之我們綜合運(yùn)用上面的四種反爬蟲策略,可以很大程度上緩解爬蟲對(duì)網(wǎng)站造成的負(fù)面影響,保證網(wǎng)站的正常訪問。