SHELL網絡爬蟲實例剖析

原創作品,允許轉載,轉載時請務必以超鏈接形式標明文章 原始出處 、作者信息和本聲明。否則將追究法律責任。http://nolinux.blog.51cto.com/4824967/1552472

       前天簡單分享了用 shell 寫網絡爬蟲的一些見解,今天特地把代碼發出來與51博友分享,還是那句話,愛技術、愛開源、愛linux。

       針對腳本的注解和整體構思,我會放到腳本之后為大家詳解。

#!/bin/bash
#
# This script is used to grab the data on the specified industry websites
# Written by sunsky
# Mail : 274546888@qq.com
# Date : 2014-09-14 3:06:00
#
 
if [ `echo $UID` != 0 ];then
  echo 'Please use the root to execute the script!'
fi
if [ ! -f /dataimg/years ];then
  echo 'Please give date file, the file path for/dataimg/years .'
fi
if [ ! -d $TMP_DIR ];then
  mkdir -p $TMP_DIR
fi
if [ ! -d $URL_MD5_DIR ];then
  mkdir -p $URL_MD5_DIR
fi
if [ ! -d $HTML_DIR ];then
  mkdir -p $HTML_DIR
fi
 
ROOT_DIR="/dataimg"                  # 指定腳本運行根目錄
TMP_DIR="$ROOT_DIR/tmp"              # 生成商品詳細頁url之前的臨時數據存放目錄
URL_MD5_DIR="$ROOT_DIR/url_md5"      # 記錄商品詳細頁url的MD5值的目錄
HTML_DIR="$ROOT_DIR/html"            # 存放下載下來的商品詳細頁目錄
URL_MD5="$URL_MD5_DIR/md5.$year"     # 負責記錄商品詳細頁url的md5值
WEB_URL="https://www.redhat.sx/"     # 所爬網站的主頁url
REPORT="$ROOT_DIR/report"            # 負責記錄采集的url綜合信息
CURL="curl -A 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.102 Safari/537.36' --referer http://www.redhat.sx"
OPT0="/dataimg/years"                                                   # 年份信息 
OPT1="$TMP_DIR/${X1_MD5}"                                               # 品牌信息     
OPT2="$TMP_DIR/${X1_MD5}_${X2_MD5}"                                     # 車型信息
OPT3="$TMP_DIR/${X1_MD5}_${X2_MD5}_${X3_MD5}"                           # 裝飾信息
OPT4="$TMP_DIR/${X1_MD5}_${X2_MD5}_${X3_MD5}_${X4_MD5}"                 # 部位分類信息
OPT5="$TMP_DIR/${X1_MD5}_${X2_MD5}_${X3_MD5}_${X4_MD5}_${URL_LIST_MD5}" # 商品詳情頁url信息
 
FIFO_FILE="/tmp/$$.fifo"
mkfifo $FIFO_FILE
exec 9<>$FIFO_FILE
rm -f $FIFO_FILE
 
num=10
for ((i=0;i<$num;i++));do
echo
done >&9
 
while read X1;do
{
  URL1="${WEB_URL}/model/YMMTSelects.cfc?method=getMakes&PassYear=$X1"
  X1_MD5=`echo $URL1|cksum|cut -d' ' -f1`
  if ! ls $OPT1 >&/dev/null;then
    $CURL -s $URL1|awk 'BEGIN{RS="<"}{print $0}'|awk -F'>' '{print $2}'|sed '1,9d'|sed '$d'|grep -v '^$' > $OPT1
  fi
  while read X2;do
    X2=`echo $X2|sed 's# #%20#g'`
    URL2="${URL1}&PassMakeName=$X2"
    #X2_MD5=`echo $URL|cksum|cut -d' ' -f1`
    if ! ls $OPT2 >&/dev/null;then
      $CURL -s $URL2|awk 'BEGIN{RS="<"}{print $0}'|awk -F'>' '{print $2}'|sed '1,6d'|sed '$d'|grep -v '^$' > $OPT2
    fi
    while read X3;do
      X3=`echo $X3|sed 's#[[:space:]]#%20#g'`
      URL3="${URL2}&PassModel=$X3"
      X3_MD5=`echo $URL3|cksum|cut -d' ' -f1`
      if ! ls $OPT3 >&/dev/null;then
        $CURL -s $URL3|sed 's#[[:space:]]##g'|awk 'BEGIN{RS="<|=|>"}{print $0}'|egrep '^[0-9]+$' > $OPT3
      fi
      while read X4;do
        X4=`echo $X4|sed 's# #%20#g'`
        URL4="${URL3}&PassVehicleID=$X4"
        X4_MD5=`echo $URL4|cksum|cut -d' ' -f1`
        if ! ls "${OPT4}" >&/dev/null;then
          $CURL -s $URL4|awk 'BEGIN{RS="<"}{print $0}'|awk -F'[>;]' '{print $2}'|sed -e '1,3d' -e '$d' -e '/^$/d' > $OPT4
        fi
        while read X5;do
          X5=`echo $X5|sed 's# #%20#g'`
          URL_LIST="${WEB_URL}index.cfm?fuseaction=store.sectionSearch&YMMTyears=$X1&YMMTmakenames=$X2&YMMTmodelnames=$X3&YMMTtrimnames=$X4&YMMTsectionnames=$X5"
          URL_LIST_MD5=`echo "$URL_LIST"|md5sum|awk '{print $1}'`
          if ! grep -q $URL_LIST_MD5 "$URL_MD5" ;then
            $CURL -s "$URL_LIST" > "$URL_MD5_DIR/$URL_LIST_MD5"
            NUM=`grep 'View page' "$URL_MD5_DIR/$URL_LIST_MD5"|wc -l`
            NUM2=$(($NUM/2))
            echo > $OPT5
            grep 'a href="index.cfm?fuseaction=store.PartInfo&PartNumbe' "$URL_MD5_DIR/$URL_LIST_MD5"|cut -d'"' -f2 > $OPT5
            while [ $NUM2 -ge 2 ];do
              URL_LIST=`grep "View page $NUM2" "$URL_MD5_DIR/$URL_LIST_MD5"|awk -F'[" ]'  '{a[$9]=$9}END{for(i in a)print a[i]}'`
              $CURL -s "$URL_LIST"|grep 'a href="index.cfm?fuseaction=store.PartInfo&PartNumbe'|cut -d'"' -f2 >> $OPT5
              NUM2=$(($NUM2-1))
            done
            echo $URL_LIST_MD5 >> "$URL_MD5"
          fi
          while read X6;do
            URL_DETAIL="${WEB_URL}${X6}"
            URL_DETAIL_MD=`echo $URL_DETAIL|md5sum|awk '{print $1}'`
            if ! grep -q $URL_DETAIL_MD "$URL_MD5" >&/dev/null;then # 該判斷以商品列表詳細頁URL的md5值為基準,負責URL的重復項判定
              $CURL -s "$URL_DETAIL" > "$HTML_DIR/$URL_DETAIL_MD"
              LABEL=`grep 'diagram-label' "$HTML_DIR/$URL_DETAIL_MD"|awk -F'[<>]' '{print $5}'`  # 商品標簽
              GIF_URL=`grep -B 10 partInfo "$HTML_DIR/$URL_DETAIL_MD"|grep -o "https.*gif"|awk '{a=$0}END{print a}'` # 產品對應的圖片URL
              PRODUCT_ID=`grep 'productID' "$HTML_DIR/$URL_DETAIL_MD"|awk -F'[<>]' '{print $3}'` # 產品零件號碼
              GIFILE=${GIF_URL#*/}   # 去除了https:/后的圖片URL信息,as:/a/b.gif
              GIF_IMG="${ROOT_DIR}${GIFILE}" # 圖片存到本地后的絕對路徑,as:/dataimg/a/b.gif
              U4=`grep -B 10 '<!-- start opentop -->' "$HTML_DIR/$URL_DETAIL_MD"|grep javascript|awk -F'[<>]' '{print $3}'`
              ! ls $GIF_IMG >& /dev/null && wget -q -m -k -P "$ROOT_DIR" "$GIF_URL"
              echo $URL_DETAIL_MD >> "$URL_MD5"
              echo "$(date +%m%d%T)+++$X1+++$X2+++$X3+++$U4+++$X5+++$URL_DETAIL+++$URL_DETAIL_MD+++$LABEL+++$PRODUCT_ID+++$GIF_IMG+++$URL_LIST" >> "$REPORT"
            fi
          done < $OPT5  # 傳入商品詳細列表url信息,進行循環
        done < $OPT4    # 傳入產品部位分類信息,進行循環
      done < $OPT3      # 傳入裝飾信息,進行循環
    done < $OPT2        # 傳入車型信息,進行循環
  done < $OPT1          # 傳入品牌信息,進行循環
  echo >&9
}&
done < $OPT0            # 傳入年份信息,進行循環
 
wait
 
exec 9<&-

       OK!

       以上就是腳本的全部內容,整體腳本主要包含了組合目標URL和抓取目標URL兩個大方向,圍繞這兩個大方向,主要是使用 curl 來做數據抓取,是用sed、awk、grep、cut來做興趣數據的抽取。

       由于所要抓取的目標URL必須經過幾個選項匹配,最終才能得到想要結果,因此我們在抓取目標URL之前添加了組合目標URL這一操作。整體這2個方向,我通過多層的while循環嵌套,來實現對參數的復用和一層一層的輸入挖掘。

       為了優化速度以及控制速度,采用了 shell 的多進程和數據智能判重的方式。

       采用 shell 的多進程目的是為了增大操作數來縮短整體完成時間,提高抓取效率。

       shell 多進程主要依托 循環 + { } + & 來實現。如果多進程的進程數量有指定數值,那么我們可以使用for和while都而已,如果多進程的進程數量沒有指定數值,那么我們最好使用while循環語句。通過將 { }& 嵌套在循環中實現將 {}內的命令群組放到后臺去自動執行,然后完成本次 { }& 操作,使得循環可以進入下一次。

       以上并未實現該shell 在后臺開啟進程數的控制,假設你的需要執行一萬次,如果你未控制速度,就可能會導致直接觸發著一萬次操作,同時放到后臺執行,這樣對系統是致命的傷害。另一方面,作為爬蟲,你對目標網站的并發量也不能太大。出于這兩方面的考慮,我們需要控制 shell 多進程每次放入后臺執行的數量。針對這一行為,我們主要通過文件描述符來實現。通過新建一臨時管道文件,然后為該文件打開一個文件描述符,并為其傳遞指定數量的空行(本文傳遞了10個空行),這樣做的目的是為了實現對進程并發量的控制。接著,在下面循環中, { }&操作的前面使用read -u9(這里9為本文使用的文件描述符)來從9這個文件描述符中獲取一行,如果獲取到就能繼續往下執行,如果獲取不到就在這里等待。

       通過以上的2者結合,就能實現對 shell 多進程的智能管控。

       采用數據智能判重的目的在于,在腳本調試中發現速度的執行瓶頸在于curl的速度,即網絡速度,因此一旦腳本異常中斷后,恢復之后,又要重復進行curl操作,這樣就極大增加了腳本執行時間。因此通過智能判重,完美實現了curl時間消耗過久的以及數據重復采集的問題。以下是數據只能判重的邏輯圖:

1.jpg

      針對腳本中變量的取值意義,我已經在上面的腳本中進行了詳細的注釋,這里不在復述。

      其它細枝末節的一些使用方法和技巧,這里不再一一解釋。對 shell 感興趣的朋友可以和我一起交流,一起進步。

轉自:http://nolinux.blog.51cto.com/4824967/1552472

原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/1945

(0)
s19930811s19930811
上一篇 2016-08-15
下一篇 2016-08-15

相關推薦

  • HAproxy實戰

    HAproxy實驗一 1、實驗要求: (1) 動靜分離discuzx,動靜都要基于負載均衡實現; (2) 進一步測試在haproxy和后端主機之間添加varnish緩存(見實驗二步驟); (3) 給出拓撲設計; (4) haproxy的設定要求: (a) 啟動stats; (b) 自定義403、502和503的錯誤頁; (c) 各組后端主機選擇合適的調度方法…

    Linux干貨 2016-11-15
  • N22-妙手-第十周博客作業

    1、請詳細描述CentOS系統的啟動流程(詳細到每個過程系統做了哪些事情)     (1) POST階段         加電自檢,確保每個設備能正常工作     (2) BIOS  &nb…

    Linux干貨 2016-12-05
  • httpd服務——CentOS7

    httpd-2.4 新特性:      (1)MPM支持運行為DSO機制;以模塊形式按需加載      (2)event MPM生產環境可用      (3)異步讀寫機制      (4)支持每個模塊…

    Linux干貨 2016-10-12
  • linux基礎練習

    馬哥教育23期網絡班+第6周課堂練習 Linux 基礎練習 一、linux基礎練習題 1、復制/etc/rc.d/rc.sysinit 文件至/tmp 目錄,將/tmp/rc.sysinit 文件中的以至少一個空白字符開頭的行的行首加#; 2、復制/boot/grub/grub.conf 至/tmp目錄中 刪除/tmp/grub.conf 文件中的行首的空白…

    Linux干貨 2016-11-01
  • grep、egrep正則表達式之初窺門徑

    何謂正則表達式 正則表達式,又稱正規表示法、常規表示法(Regular Expression,在代碼中常簡寫為regex、regexp或RE),是一類字符所書寫的模式,其中許多字符不表示其字面意義,而是表達控制或通配等功能。正則表達式使用單個字符串來描述、匹配一系列符合某個句法規則的字符串。在很多文本編輯器里,正則表達式通常被用來檢索、替換那些符合某個模式的…

    2015-03-19
  • shell 編程(一)

    shell腳本編程:  編程語言的分類:根據運行方式    編程運行:源代碼->編譯器(編譯)--程序文件    解釋運行:源代碼-->運行啟動時解釋,由解釋器邊解釋運行; 根據其編程過程中功能的實現是調用庫還是調用外部的程序文件:   shell腳本編程:      利用系統上的命令編程組件進行編程:   完整的編程:     利用庫或編程組件進行編程; …

    Linux干貨 2016-12-23
欧美性久久久久