SHELL网络爬虫实例剖析

前天简单分享了用 shell 写网络爬虫的一些见解,今天特地把代码发出来与51博友分享,还是那句话,爱技术、爱开源、爱linux。

针对脚本的注解和整体构思,我会放到脚本之后为大家详解。

#!/bin/bash
#
# This script is used to grab the data on the specified industry websites
# Written by sunsky
# Mail : [email protected]
# 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            # 传入年份信息,进行循环

if [ $? -eq 0 ];then
  echo "--------完成了--------" >> $REPORT
else
  echo "--------未完成--------" >> $REPORT
fi

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时间消耗过久的以及数据重复采集的问题。以下是数据只能判重的逻辑图:

针对脚本中变量的取值意义,我已经在上面的脚本中进行了详细的注释,这里不在复述。

其它细枝末节的一些使用方法和技巧,这里不再一一解释。对 shell 感兴趣的朋友可以和我一起交流,一起进步。

时间: 2024-10-30 02:00:54

SHELL网络爬虫实例剖析的相关文章

crawler4j:轻量级多线程网络爬虫实例

crawler4j是Java实现的开源网络爬虫.提供了简单易用的接口,可以在几分钟内创建一个多线程网络爬虫. 下面实例结合jsoup(中文版API),javacvs 爬取自如租房网(http://sh.ziroom.com/z/nl/)租房信息. 1.maven导入相关包 1 <dependency> 2 <groupId>edu.uci.ics</groupId> 3 <artifactId>crawler4j</artifactId> 4 &

Python ===if while for语句 以及一个小小网络爬虫实例

if分支语句 >>> count=89 >>> if count==89: print count 89                          #单分支 >>> #coding:utf-8 count=int(raw_input('请输入一个数字')) print count if count>80: print '比80大' else: if count<80: print ‘比80小’   #多分支 =======自定义函数

网络爬虫实例代码优化 面试重点

我在“python3开发桌面软件之程序主体”一文中已经分享了919网监助手1.0程序的主体源代码(即events.py中的代码),后用迭代器.生成器表达式等对代码进行优化,使程序运行更快.更稳定.优化后的代码如下: #coding:utf-8import wximport configparserimport requestsimport reimport osimport stringfrom bs4 import BeautifulSoupfrom openpyxl import load_

[定向爬虫] 网络爬虫实例2-淘宝定向爬虫

import requests import re import time #获取html页面 def getHTMLText(url): try: r = requests.get(url,timeout = 30) r.raise_for_status() r.encoding = "utf-8" #可以节约时间 return r.text except: return "" #对获取的每一个页面进行解析,ilt是结果的列表类型 def parsePage(il

基于java的网络爬虫框架(实现京东数据的爬取,并将插入数据库)

原文地址http://blog.csdn.net/qy20115549/article/details/52203722 本文为原创博客,仅供技术学习使用.未经允许,禁止将其复制下来上传到百度文库等平台. 目录 网络爬虫框架 网络爬虫的逻辑顺序 网络爬虫实例教学 model main util parse db 再看main方法 爬虫效果展示 网络爬虫框架 写网络爬虫,一个要有一个逻辑顺序.本文主要讲解我自己经常使用的一个顺序,并且本人经常使用这个框架来写一些简单的爬虫,复杂的爬虫,也是在这个基

[Python]网络爬虫(四):Opener与Handler的介绍和实例应用(转)

在开始后面的内容之前,先来解释一下urllib2中的两个个方法:info and geturl urlopen返回的应答对象response(或者HTTPError实例)有两个很有用的方法info()和geturl() 1.geturl(): 这个返回获取的真实的URL,这个很有用,因为urlopen(或者opener对象使用的)或许会有重定向.获取的URL或许跟请求URL不同. 以人人中的一个超级链接为例, 我们建一个urllib2_test10.py来比较一下原始URL和重定向的链接: [p

如何用SHELL写好网络爬虫

上周,老大压下来任务,让写一个网络爬虫,负责爬某一个行业网站的数据信息.由于本人只会 shell 编程语言,其它编程语言从未涉猎,因此就只能硬着头皮用 shell 去写了. 转眼之前已经过去一周了,一切从无到有,经历的坎坷无数,这里我就不一一吐槽. 这里呢,我就简单和大家分享下,我个人对,如何用 shell 去写好网络爬虫的几点拙见,希望有想法的朋友联系我一起交流交流想法,没有想法的就看看有精华就吸收走,有糟粕的果断弃之. 1.你肯定要简单了解下网络爬虫是什么!这个定义在谷歌一搜一大堆,这里我就

Python3网络爬虫(十一):爬虫黑科技之让你的爬虫程序更像人类用户的行为(代理IP池等)

原文链接: Jack-Cui,http://blog.csdn.net/c406495762 运行平台: Windows Python版本: Python3.x IDE: Sublime text3 1 前言 近期,有些朋友问我一些关于如何应对反爬虫的问题.由于好多朋友都在问,因此决定写一篇此类的博客.把我知道的一些方法,分享给大家.博主属于小菜级别,玩爬虫也完全是处于兴趣爱好,如有不足之处,还望指正. 在互联网上进行自动数据采集(抓取)这件事和互联网存在的时间差不多一样长.今天大众好像更倾向于

使用网络爬虫的一丝心得

因为参与了创新计划,所以懵懵懂懂的接触到了网络爬虫. 爬数据使用工具,因此了解到Python.asp.net等可以用来抓数据. 想想在学习.net的时候根本没有想到会使用在这个上面--书本上的知识都是死的,那学习的基础知识只能通过不断的拓展使用领域才能在更好的得到深化.应用! 进入一个陌生的领域,从入门到精通的路真的是需要用汗水积累起来的--没有真正的天才,只有自感聪明的蠢材.(自我审视) 有句话说的特别的好:"世界上两种聪明的人:一种是从来不认为自己聪明的聪明人:而另一种是自以为自己很聪明的'