前天简单分享了用 shell 写网络爬虫的一些见解,今天特地把代码发出来与51博友分享,还是那句话,爱技术、爱开源、爱linux。
针对脚本的注解和整体构思,我会放到脚本之后为大家详解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
#!/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时间消耗过久的以及数据重复采集的问题。以下是数据只能判重的逻辑图:
针对脚本中变量的取值意义,我已经在上面的脚本中进行了详细的注释,这里不在复述。
其它细枝末节的一些使用方法和技巧,这里不再一一解释。对 shell 感兴趣的朋友可以和我一起交流,一起进步。