爬虫程序是如何遍历互联网,把网页全部抓取下来的呢?互联网可以看成一个超级大的”图“,每个页面可以看作是一个"节点"。页面中的连接可以看成是图的"有向边”。因此,能够通过图的遍历的方式对互联网这个超级大“图”进行访问。图的遍历通常可分为宽度优先遍历和深度优先遍历两种方式。但是深度优先遍历可能会在深度上过“深”地遍历或者陷入“黑洞”,大多数爬虫都不采用这种方式。另一方面,在爬取的时候,有时候也不能完全按照宽度优先遍历的方式,而是给待遍历的网页赋予一定的优先级, 根据这个优先级进行遍历,这种方法称为带偏好的遍历。
1、宽度优先遍历互联网
整个的宽度优先爬虫过程就是从一系列的种子节点开始, 把这些网页中的 “ 子节点 ” ( 也就是超链接)提取出来, 放入队列中依次进行抓取。 被处理过的链接需要放入一张表( 通常称为Visited表)中。每次新处理一个链接之前,需要查看这个链接是否已经存在于Visited表中。如果存在,证明链接已经处理过,跳过,不做处理,否则进行下一步处理。实际的过程如图1.5所示。
如图1.5所示, 初始的URL地址是爬虫系统中提供的种子URL(一般在系统的配置文件中指定)。 当解析这些种子URL所表示的网页时, 会产生新的URL(比如从页面中的<ahref=“ http://www.admin.com”中提取出http://www.admin.com这个链接)。然后,进行以下工作:
(1)把解析出的链接和Visited表中的链接进行比较,若Visited表中不存在此链接, 表示其未被访问过。
(2)把链接放入TODO表中。
(3)处理完毕后,再次从TODO表中取得一条链接,直接放入Visited表中。
(4)针对这个链接所表示的网页,继续上述过程。如此循环往复。
2、Java宽度优先爬虫示例
Java实现一个简易的爬虫。其中用到了HttpClient和HtmlParser两个开源工具包
首先, 需要定义图1.6中所描述的 “URL队列 ” , 这里使用一个LinkedList来实现这个队列。
Queue类:
package com.bfsTest;
import java.util.LinkedList;
/**
*
*队列,保存将要访问的URL
*
*/
public class Queue {
//使用链表实现队列
private LinkedList queue = new LinkedList();
//入队列
public void enQueue(Object t){
queue.addLast(t);
}
//出队列
public Object deQueue()
{
return queue.removeFirst();
}
//判断队列是否为空
public boolean isQueueEmpty()
{
return queue.isEmpty();
}
public boolean contains(Object t)
{
return queue.contains(t);
}
public boolean empty()
{
return queue.isEmpty();
}
}
除了URL队列之外,在爬虫过程中,还需要一个数据结构来记录已经访问过的URL。
每当要访问一个URL的时候,首先在这个数据结构中进行查找,如果当前的URL已经存
在,则丢弃它。这个数据结构要有两个特点:
1、结构中保存的URL不能重复。
2、能够快速地查找 (实际系统中URL的数目非常多,因此要考虑查找性能) 。
针对以上两点,我们选择HashSet作为存储结构。
LinkQueue类:
package com.bfsTest;
import java.util.*;
public class LinkQueue {
//已访问的url集合
private static Set visitedUrl = new HashSet();
//待访问的url 集合
private static Queue unVisitedUrl = new Queue();
//获得URL队列
public static Queue getUnVisitedUrl()
{
return unVisitedUrl;
}
//添加到访问的URL队列中
public static void addVisitedUrl(String url)
{
visitedUrl.add(url);
}
//移除访问过的URL
public static void removeVisitedUrl(String url)
{
visitedUrl.remove(url);
}
//未访问的URL出队列
public static Object unVisitedUrlDeQueue()
{
return unVisitedUrl.deQueue();
}
//保证每个URL只被访问一次
public static void addUnvisitedUrl(String url)
{
if(url != null && !url.trim().equals("")
&& !visitedUrl.contains(url)
&& !unVisitedUrl.contains(url))
unVisitedUrl.enQueue(url);
}
//获得已经访问的URl数目
public static int getVisitedUrlNum()
{
return visitedUrl.size();
}
//判断未访问的URL队列是否为空
public static boolean unVisitedUrlsEmpty()
{
return unVisitedUrl.empty();
}
}