引言
吴军先生在他的《智能时代》中曾经提到:“如果我们把资本和机械动能作为大航海时代以来全球近代化的推动力的话,那么数据将成为下一次技术革命和社会变革的核心动力”。
的确,现在这个时代是数据的时代。当人工智能需要的计算能力、算法都放到云上,成为一件人人可以得到的武器的时候,谁拥有了数据谁才取得了优势。Google的成功的原因之一就是它拥有以太级别的数据。
而在这个人人上网的时代,网络可以说是数据的最大聚集地。如果说网络是一个蕴含大量数据的金库,那么网络爬虫就是获得这里面黄金的重要钥匙之一。
因为网络爬虫爬取网页的策略所对应的算法很有意思,所以我对其中三种用Processing进行了简单的可视化,它们分别是 :BFS
、DFS
、BFS + DFS
。
下面大家可以先直观的感受以一下。
心急的同学可以看一下在线效果。
代码
主要思想
可视化的思想也简单。我们把整个网络抽象成一个棋盘,每一个格子代表一个网页。爬虫可以在这个棋盘上移动,如果它移动到一个格子就会对其染色,表示它已经爬取过该网页的信息了。
这样我们就把爬虫爬取网络这个过程变成了对该棋盘染色的过程,其爬取网页的顺序就对应对该棋盘上格子染色的顺序。我们要做的就是给爬虫指定一个移动的顺序或者规则,让整个棋盘被染上颜色。
这里有四点需要说明。
-
假设任意相邻的格子之间都有超链接。也就说爬虫如果移动了下面图中这个蓝色的格子,它就可以移动该蓝色格子上下左右的相邻的四个黄色的个格子。
-
假设行或列距离比较近的格子间在某些情况下也可以认为有超链接。也就说虽然第3列的格子和第5列的格子没有相邻,但某些情况下下也可以看作被超链接相连接。至于原因在后面我们会讲到。
-
每个格子有以下三种状态:
- 没有被发现:初始化的时候格子全部都是这个状态。
- 发现了但是没有被访问:上面的黄色的格子就是这个状态,此时爬虫在蓝色这个格子,爬取了蓝色格子所对应的网页的信息,然后找到了指向它旁边四个格子的超链接。那么这四个格子在这种情况下就是发现了但是没有被访问。
- 被访问了:上面的蓝色的格子就是被访问了,因为爬虫爬取了它对应网页的信息,也染了色。
-
第四,最开始的格子都是白色的,爬虫对其染的颜色取决于该爬虫到达该格子所经过超链接的数量。
开始爬取之前,我们将所有格子标记为没有被爬取(或者访问过)。然后通过点击棋盘上的任意格子在该格子上放上一个爬虫并且激活该它,之后该爬虫会根据当前的爬取策略,在棋盘上移动。这样我们的棋盘就开始按照模式被染色了。
在了解了该可视化的主要思想后,我们就开始用Processing写代码思想该效果。
主要结构
第一步我们来看看代码的大体结构。这部分代码主要初始化了棋盘和颜色比例尺,以及确定该作品的交互方式。我们可以通过点击鼠标在该棋盘上的任意位置放置爬虫,通过按下键盘上的任意按键来改变爬虫的移动策略。
Grid g;
void setup() {
size(500, 500);
//初始化棋盘,将棋盘的宽度和高度设置为和作品一样,一个像素代表一个格子。
g = new Grid(width, height, 1);
colorMode(HSB, 360);
background(360);
}
void draw() {
//爬虫不停爬,直到格子完全被染色。
g.crawl();
}
void mousePressed() {
g.putCrawler(mouseX, mouseY);
}
void keyPressed() {
g.changeCrawlerType();
}
class Grid{
}
//颜色比例尺,用于确定每个格子的颜色
int colorScale = {
#6d3fa9, /*.....*/,#6d40aa};
Grid类
下面我们先来看棋盘这个类。每一个格子有一个对应的标号,该标号由它所在的行数和列数决定。棋盘类有两个很重要的成员变量:visited和frontier。
visited这个int类型的数组用于记录每个格子是否被访问过。如果该格子没有被访问过,那么它在这个数组里面对应的值为0,否者为1。
frontier是个动态数组,用来存储被已经被发现的格子。每一次我们对一个格子染色后,会将它相邻且没有被访问过的的格子放入该动态数组。然后爬虫会按照某种策略选择这个动态数组中的一个格子,移动到该格子对其染色。爬虫从该数组里面选择格子的策略不同,导致了它对整个棋盘的染色模式不同,这个我们很快会看见。
在每一次染色开始的时候,我们都会把每个格子的状态设为没有被访问过,并且清空frontier动态数组中的格子,然后将爬虫爬的起始格子的标号放入froniter中,表示这个格子已经被发现了,可以被染色了。
这里需要注意的是我们把从起始格子到到达该格子的超链接数用一个depth数组存储,并且称其为这个格子的深度。
了解了这些,大家看懂下面的代码就很容易了。
class Grid {
//分别代表两种移动的策略
final int BFS = 0, DFS = 1;
float cellSize;
//记录已经发现的还没有来的即被访问的点
ArrayList<Integer> frontier;
int [] visited, depth;
int col, row;
//当前移动的策略
int type;
boolean isCrawling;
Grid(float _width