思路:首先使用wget下载足够的网页,然后awk配合正则表达式提取网页中的单词,最后使用awk进行词频统计并输出。
首先使用wget递归下载wikipedia网站内容,为了提高采样质量,主要采集维基百科的特色(freatured)条目:
wget -r -e robots=off --wait=0.3 --level=1 -np--output-document=webpage.txt --quota=20Mhttps://en.wikipedia.org/wiki/Wikipedia:Featured_articles
参数解释:
-r 递归下载网站,
-e robots=off 使用了-r参数后,wget会以爬虫的形式下载网站,并且接受网站robots.txt的限制,不巧的是,维基百科限制了wget的爬虫访问,为此,需要让wget忽略这一限制,使用这一参数
--wait=0.3 为了防止访问频率过高被封禁IP,设置wget两次访问网站之间间隔0.3s
--level=1 递归深度为1。所输入的网页为维基百科所有特色条目的索引,所以只需要递归一层
-np 不递归父目录(noparent)
--output-document=filename 为了方便之后的处理,设置wget将网页源代码连接起来并存储到一个文件中
--quota=20M 设置最多下载20M网页内容(实际上一般会超过这一值,详细请见wget帮助),减少wget运行时间
https://en.wikipedia.org/wiki/Wikipedia:Featured_articles这是网页链接,为维基百科特色条目的索引。
在分析词频的时候,必须排除网页中非文字内容的干扰,具体来说,主要是html标签和JavaScript脚本。原本这是很方便的,只要在删除JavaScript脚本后删去所有最邻近的<>之间的内容即可,但遗憾的是,shell(此处指awk, grep, egrep)中只能使用贪婪匹配,这会导致删去的内容过多(如<span>hahaha </span><span> bbbb</span>,我们希望删去所有的<span>和</span>,留下hahaha和bbbb,但是linux会直接将整行文字匹配并删除),为此,我采用的技巧如下:
首先将>替换为换行,这样就可以确保以 `<` 开头的行只有原来 `<>` 之间的内容。然后,再将所有以`<`开头的行删掉即可,代码如下:
echo example | awk'{gsub(/>/,"\n");print;}'| awk'{gsub(/<.*/,"");print}'
但是,这样不能有效删除script标签之间的内容,因为两个script标签中的代码往往存在换行,换行干扰了上述操作。所以,还需要首先去除所有的换行,将网页连成一行,最终的代码如下:
echo example |awk 'BEGIN{p="";space=" "}{p=p""space""$0} END{print(p);}' | awk'{gsub(/<\/script>/,"\n");print}' | awk'{gsub(/<script>.*/,"");print}'| awk'{gsub(/>/,"\n");print;}'| awk'{gsub(/<.*/,"");print}'
这样即可将文件中大部分的html代码去除。
最后,文件中还有不少的换行,空格,和制表符,对这些符号,同样使用gsub进行替换,如下:
awk 'BEGIN{p="";space=" "}{p=p""space""$0} END{print(p);}'|awk '{gsub(/[\t]{1,}/," ");print}'
为了方便处理,再将输出改为一个单词一行:
egrep -o "\b[[:alpha:]]+\b"
我的方法是使用awk将所有的行存储到一个变量中:
awk 'BEGIN{p="";space=" "}{p=p""space""$0} END{print(p);}'
其中关键的是字符串连接的方法,awk中,假设有两个字符串:
Str1="aaaaa";
Str2="bbbb";
Str3=Str1""Str2;
使用""即可连接两个字符串
统计词频对于awk的关联数组是非常简单的工作,当文字整理成一个单词一行后,只需要:
awk '{ count[$0]++ } '
即可将各个单词的个数统计于count数组中,使用:
即可将数组按照下标排列好后输出:
awk '{ count[$0]++ } END{slen=asorti(count,newcount);printf("%-14s%s\n","Word","Count") ; for(i=1;i<=slen;i++) {printf("%-14s%d\n",newcount[i],count[newcount[i]]); } }'
awk中有两个排序函数:asort和asorti,使用方法为
arrayLength = assort/asorti(sourceArray, destinationArray)
使用asort可以对数值进行排序,数组的索引(下标)会被丢弃。
使用asorti可以对下标进行排序,但是数组的索引会被丢弃。
排序完成后会返回数组长度arrayLength
最后奉上完整代码如下:
#Author <span style="white-space:pre"> </span>Archimekai #Date <span style="white-space:pre"> </span>20160314 # !bin/bash filename="webpage.txt"; wget -r -erobots=off --wait=0.3 --level=1 -np --output-document=$filename --quota=20M https://en.wikipedia.org/wiki/Wikipedia:Featured_articles awk'BEGIN{p="";space=" "} {p=p""space""$0}END{print(p);}' $filename | awk '{gsub(/<\/script>/,"\n");print}'| awk '{gsub(/<script>.*/,"");print}'| awk'{gsub(/>/,"\n");print;}'| awk'{gsub(/<.*/,"");print}'|awk 'BEGIN{p="";space=""} {p=p""space""$0} END{print(p);}'|awk '{gsub(/[\t]{1,}/," ");print}'|egrep -o "\b[[:alpha:]]+\b"|awk '{count[$0]++ } END{slen=asorti(count,newcount);printf("%-14s%s\n","Word","Count") ; for(i=1;i<=slen;i++) {printf("%-14s%d\n",newcount[i],count[newcount[i]]); } }'>wordfreq.txt cat wordfreq.txt部分输出:
Word Count
A 1288
AA 2
AAA 1
AB 2
ABC 6
ABCD 1
ABN 2
AC 3
ACC 10
ACM 1
ACR 3
ACS 11
ACT 1
ACTION 3
AD 45
ADMIN 1
ADORAVI 2
ADW 4
AE 1
AEC 3
AETERNAE 1
AFAIK 1
AFD 2