上一篇部落格,介紹了Linux 抓取網頁的執行個體,其中在抓取google play國外網頁時,需要用到Proxy 伺服器
代理的用途
其實,除了抓取國外網頁需要用到IP代理外,還有很多情境會用到代理:
- 通過代理訪問一些國外網站,繞過被某國防火牆過濾掉的網站
- 使用教育網的Proxy 伺服器,可以訪問到大學或科研院所的內部網站資源
- 利用設定代理,把請求通過Proxy 伺服器下載快取後,再傳回本地,提高訪問速度
- 駭客發動攻擊時,可以通過使用多重代理來隱藏原生IP地址,避免被跟蹤(當然,魔高一尺,道高一丈,終究會被traced)
代理的原理
代理服務的原理是本地瀏覽器(Browser)發送請求的資料,不是直接發送給網站伺服器(Web Server)
而是通過中間的Proxy 伺服器(Proxy)來代替完成,如:
IP代理篩選系統
問題分析
- 因為不可能每天都遍曆測試全球2^32數量級的IP地址,來看哪個IP可用,因此首要工作就是尋找待選的代理IP源?
- 初步確定了待選代理IP源,如何確定這裡面的每一個IP是真的可用?
- 尋找到的待選代理IP源,是以什麼格式儲存的?需要進行文本預先處理嗎?
- 選擇並確定了某個代理IP可用,但在下載網頁過程中可能會又突然失效了,如何繼續抓取剩下的網頁?
- 如果重新選擇了一個可用的代理IP完成了剩下的網頁抓取,為了方便下次使用,需要將它更新到12國抓取指令碼中,該如何?呢?
- 上篇部落格中提到過,在抓取遊戲排名網頁和遊戲網頁的過程中,都需要使用代理IP來下載網頁,如果遇到上面的代理IP突然失效,該如何解決?
- 如果一個代理IP並沒有失效,但是它抓取網頁的速度很慢或極慢,24小時內無法完成對應國家的網頁抓取任務,該怎麼辦?需要重新篩選一個更快的嗎?
- 如果把所有代理IP源篩選一遍後,仍然沒有一個可用的代理IP,該怎麼辦?是繼續迴圈再篩選一次或多次,還是尋找新的代理IP源?
分析解決一個實際問題時,將會遇到各種問題,有些問題甚至是方案設計之初都難以想到的(如代理IP抓取網頁速度過慢),我的體會是:動手實踐比純理論更重要!
方案設計
總體思路:尋找並縮小篩選的IP代理源——》檢測代理IP是否可用——》IP可用則記錄下來抓取網頁——》代理IP故障則重新篩選——》繼續抓取網頁——》完成
1、IP代理源
選擇有兩個原則:可用和免費,經過深入調研和搜尋,最後確定兩個網站的IP代理比較靠譜:freeproxylists.net 和 xroxy.com
從國家數、IP代理數量、IP代理可用率、IP代理文字格式設定等多方面綜合考量,IP代理源主要選自前者,後者作為補充,在後來的實踐測試表明這種初選方案基本滿足需求
2、文本預先處理
從freeproxylists.net擷取的代理IP,有IP地址、連接埠、類型、匿名性、國家...等等參數,而我們需要的僅僅是IP+Port,因此需要對初選的IP代理源做文本預先處理
文本空格處理命令:
sed -e "s/\s\{2,\}/:/g" $file_input > $file_split
sed -i "s/ /:/g" $file_split
合并代理IP(ip:port)命令:
proxy_ip=$(echo $line | cut -f 1 -d ":")
proxy_port=$(echo $line | cut -f 2 -d ":")
proxy=$proxy_ip":"$proxy_port
3、檢測IP代理
文本預先處理代理IP為標準格式(ip:port)後,需要進行代理IP篩選測試,看哪些可用哪些不可用(由於擷取的IP代理源有一些不能使用或下載過慢,需要過濾掉)
curl抓取網頁檢測IP代理是否可用命令:
cmd="curl -y 60 -Y 1 -m 300 -x $proxy -o $file_html$index $url_html"
$cmd
4、儲存IP代理
檢測一個代理IP是否可用,如果可用,則儲存下來。
判斷一個代理IP是否可用的標準,是通過判斷步驟3中下載的網頁($file_html$index)是否有內容,具體命令如下:
if [ -e ./$file_html$index ]; then
echo $proxy >> $2
break;
fi
5、IP代理抓取網頁
利用步驟4儲存的代理IP抓取網頁,通過代理IP抓取12國排名網頁和遊戲網頁,具體命令如下:
proxy_cmd="curl -y 60 -Y 1 -m 300 -x $proxy -o $proxy_html $proxy_http"
$proxy_cmd
6、IP代理故障
IP代理故障有多種情況,在上面的問題分析中已經列出了幾條,下面將詳細分析如下:
a、代理IP在抓取的網頁過程中,突然失效,無法繼續完成網頁抓取
b、代理IP沒有失效,但是抓取網頁很慢,無法在一天24小時內完成網頁抓取,導致無法產生遊戲排名每日報表
c、代理IP全部失效,無論是輪詢檢測一遍或多遍後,都無法完成當天的網頁抓取任務
d、由於整個網路路由擁塞,導致代理IP抓取網頁很慢或無法抓取,誤判為代理IP全部失效,如何恢複和糾正
7、重新檢測IP代理
在網頁抓取過程中,面對步驟6的IP代理故障,設計一套合理、高效的代理IP抓取恢複機制,是整個IP代理篩選系統的核心和關鍵
其故障恢複的輪詢篩選流程如下:
流程中,需要注意幾點:
a、首先檢測上次IP代理,這是因為上次(昨天)的IP代理完成了所有網頁抓取任務,其可用機率相對比較高,所以優先考慮其今天是否也可用。如果不可用,則另選其它
b、如果上次代理IP今天不可用,則重新遍曆檢測代理IP源,一旦檢測到有可用,則不再迴圈下去,更新可用IP代理並儲存其在IP源的位置,方便下次從此處開始遍曆
c、如果流程b新選的代理IP突然失效或網速過慢,則在b記錄的IP源位置繼續篩選後面的代理IP是否可用。如可用,則繼續抓取網頁;如不可用,則再次遍曆整個IP源
d、如果再次遍曆了整個代理IP源,仍然沒有代理IP可用,則反覆輪詢遍曆整個代理IP源,直到有代理IP可用或今天24時過去(即今日整天都找不到可用代理IP)
e、對流程d中全部代理IP失效且整日找不到可用代理IP,無法完成當日網頁抓取這一特殊情況,在次日淩晨重新啟動網頁抓取總控指令碼前,需要先殺死流程d在背景迴圈進程,防止今日和次日的兩個後台網頁抓取程式同時運行(相當於兩個非同步後台抓取進程),造成抓取網頁排名資料陳舊或錯誤、佔用網速頻寬等。其實現殺死當日僵死的後台抓取進程,請見上一篇部落格 Linux 抓取網頁執行個體 ——》 自動化總控指令碼 ——》kill_curl.sh指令碼,其原理是kill -9 進程號,關鍵指令碼代碼如下:
while [ ! -z $(ps -ef | grep curl | grep -v grep | cut -c 9-15) ]
do
ps -ef | grep curl | grep -v grep | cut -c 15-20 | xargs kill -9
ps -ef | grep curl | grep -v grep | cut -c 9-15 | xargs kill -9
done
8、完成網頁抓取
通過上述的IP代理篩選系統,篩選出12國可用的免費代理IP,完成每日12國網頁排名和遊戲網頁的抓取任務
之後,就是對網頁中遊戲屬性資訊的進行提取、處理,產生每日報表、郵件定時發送和趨勢圖查詢等,詳見我的上一篇部落格:Linux 抓取網頁執行個體
指令碼功能實現
IP代理篩選的基本過程比較簡單,其資料格式和實現步驟如下:
首先,到 freeproxylists.net 網站,收集可用的代理IP源(以美國為例),其格式如下:
接著,清除中的空格,具體實現命令請見上面【方案設計】——》【2、文本預先處理】,文本預先處理後的格式如下:
然後,測試文本預先處理後的代理IP是否可用具體命令請見上面【方案設計】——》【3、檢測IP代理】,檢測代理IP後的格式如下:
下面介紹shell指令碼實現文本預先處理和網頁篩選的詳細步驟
1、文本預先處理
# file processlog='Top800proxy.log'dtime=$(date +%Y-%m-%d__%H:%M:%S)function select_proxy(){ if [ ! -d $dir_split ]; then mkdir $dir_split fi if [ ! -d $dir_output ]; then mkdir $dir_output fi if [ ! -e $log ]; then touch $log fi echo "================== Top800proxy $dtime ==================" >> $log for file in `ls $dir_input`; do echo $file >> $log file_input=$dir_input$file echo $file_input >> $log file_split=$dir_split$file"_split" echo $file_split >> $log rm -rf $file_split touch $file_split sed -e "s/\s\{2,\}/:/g" $file_input > $file_split sed -i "s/ /:/g" $file_split file_output=$dir_output$file"_out" echo $file_output >> $log proxy_output "$file_split" "$file_output" echo '' >> $log done echo '' >> $log}
指令碼功能說明:
if語句,判斷並建立用於儲存處理IP源中間結果的檔案夾$dir_split 和 $dir_output ,前者儲存【指令碼功能實現】中文本預先處理後的文字格式設定,後者儲存檢測後可用的代理IP
sed -e語句,把輸入文本(指令碼功能實現的圖1)中的多個空格,修改為一個字元“:”
sed -i語句,進一步把文本中的多餘空格,轉換為一個字元":"
轉換的中間結果,都儲存到檔案夾 $dir_split
後面的file_output三行,以檔案參數的形式"$file_split",傳給代理IP檢測函數(proxy_output),篩選出可用的代理IP
2、代理IP篩選
index=1file_html=$dir_output"html_"cmd=''function proxy_output(){ rm -rf $2 touch $2 rm -rf $file_html* index=1 while read line do proxy_ip=$(echo $line | cut -f 1 -d ":") proxy_port=$(echo $line | cut -f 2 -d ":") proxy=$proxy_ip":"$proxy_port echo $proxy >> $log cmd="curl -y 60 -Y 1 -m 300 -x $proxy -o $file_html$index $url_html" echo $cmd >> $log $cmd if [ -e ./$file_html$index ]; then echo $proxy >> $2 break; fi index=`expr $index + 1` done < $1 rm -rf $file_html*}
指令碼功能說明:
代理IP篩選函數proxy_output頭三行,清除先前篩選的結果,作用是初始化
while迴圈,主要是遍曆以參數形式傳入的文本預先處理後的"$file_split",檢測代理IP是否可用,其步驟如下:
a、首先拼接出代理IP的(ip:port)格式,其實現是通過cut分割文本行,然後提取出第一個欄位(ip)和第二個欄位(port),拼接成(ip:port)
b、通過curl構造出抓取網頁的命令cmd,執行網頁下載命令$cmd
c、通過檢測網頁下載命令執行後,是否產生了網頁下載檔案,來判斷拼接出的代理IP($proxy)是否有效。若有效,則儲存此代理IP到"$file_output"中並退出遍曆(break)
d、如果當前代理IP無效,則讀取下一行代理IP,繼續檢測
代理IP抓取網頁執行個體:
利用上面的代理IP系統,篩選出來免費代理IP,抓取遊戲排名網頁的執行個體如下(指令碼片段):
index=0 while [ $index -le $TOP_NUM ] do url=$url_start$index$url_end url_cmd='curl -y 60 -Y 1 -m 300 -x '$proxy' -o '$url_output$index' '$url echo $url_cmd date=$(date "+%Y-%m-%d___%H-%M-%S") echo $index >> $log echo $url"___________________$date" >> $log $url_cmd # done timeout file seconds=0 while [ ! -f $url_output$index ] do sleep 1 echo $url_output$index"________________no exist" >> $log $url_cmd seconds=`expr $seconds + 1` echo "seconds____________"$seconds >> $log if [ $seconds -ge 5 ]; then select_proxy url_cmd='curl -y 60 -Y 1 -m 300 -x '$proxy' -o '$url_output$index' '$url seconds=0 fi done index=`expr $index + 24` done
指令碼功能說明:
上面shell指令碼程式碼片段,是用來抓取網頁的,其中最核心的一行是 select_proxy
其作用是上述介紹過的,當代理IP突然失效、抓取網頁過慢、全部代理IP都無效、或無法完成當天的網頁抓取工作,用來重新篩選代理IP,恢複網頁抓取的一段核心代碼
其設計實現流程,如上述的【方案設計】——》【7、重新檢測IP代理】,其實現原理可參照上述的【代理IP篩選】的指令碼,在此不再貼出其源指令碼代碼