网页爬虫的设计与实现(Java版)

来源:互联网 发布:2017淘宝图片尺寸 编辑:程序博客网 时间:2024/05/21 17:55
   最近为了练手而且对网页爬虫也挺感兴趣,决定自己写一个网页爬虫程序。
首先看看爬虫都应该有哪些功能。

内容来自(http://www.ibm.com/developerworks/cn/java/j-lo-dyse1/index.html?ca=drs-)

网页收集的过程如同图的遍历,其中网页就作为图中的节点,而网页中的超链接则作为图中的边,通过某网页的超链接得到其他网页的地址,从而可以进一步的进行网页收集;图的遍历分为广度优先和深度优先两种方法,网页的收集过程也是如此。综上,Spider收集网页的过程如下:从初始 URL集合获得目标网页地址,通过网络连接接收网页数据,将获得的网页数据添加到网页库中并且分析该网页中的其他 URL 链接,放入未访问 URL集合用于网页收集。下图表示了这个过程:



网页收集器Gather

网页收集器通过一个 URL 来获取该 URL 对应的网页数据,其实现主要是利用 Java 中的 URLConnection类来打开 URL 对应页面的网络连接,然后通过 I/O 流读取其中的数据,BufferedReader提供读取数据的缓冲区提高数据读取的效率以及其下定义的 readLine() 行读取函数。代码如下 ( 省略了异常处理部分 ):

                         URL url = new URL(“http://www.xxx.com”); URLConnection conn = url.openConnection(); BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = null; while((line = reader.readLine()) != null)               document.append(line + "\n");

网页处理

收集到的单个网页,需要进行两种不同的处理,一种是放入网页库,作为后续处理的原始数据;另一种是被分析之后,抽取其中的 URL连接,放入 URL 池等待对应网页的收集。

网页的保存需要按照一定的格式,以便以后数据的批量处理。这里介绍一种存储数据格式,该格式从北大天网的存储格式简化而来:

  • 网页库由若干记录组成,每个记录包含一条网页数据信息,记录的存放为顺序添加;
  • 一条记录由数据头、数据、空行组成,顺序为:头部 + 空行 + 数据 + 空行;
  • 头部由若干属性组成,有:版本号,日期,IP地址,数据长度,按照属性名和属性值的方式排列,中间加冒号,每个属性占用一行;
  • 数据即为网页数据。

需要说明的是,添加数据收集日期的原因,由于许多网站的内容都是动态变化的,比如一些大型门户网站的首页内容,这就意味着如果不是当天爬取的网页数据,很可能发生数据过期的问题,所以需要添加日期信息加以识别。

URL 的提取分为两步,第一步是 URL 识别,第二步再进行 URL的整理,分两步走主要是因为有些网站的链接是采用相对路径,如果不整理会产生错误。URL的识别主要是通过正则表达式来匹配,过程首先设定一个字符串作为匹配的字符串模式,然后在 Pattern 中编译后即可使用 Matcher类来进行相应字符串的匹配。实现代码如下:

public ArrayList<URL> urlDetector(String htmlDoc){    final String patternString = "<[a|A]\\s+href=([^>]*\\s*>)";               Pattern pattern = Pattern.compile(patternString,Pattern.CASE_INSENSITIVE);       ArrayList<URL> allURLs = new ArrayList<URL>();    Matcher matcher = pattern.matcher(htmlDoc);    String tempURL;    //初次匹配到的url是形如:<a href="http://bbs.life.xxx.com.cn/" target="_blank">    //为此,需要进行下一步的处理,把真正的url抽取出来,        //可以对于前两个"之间的部分进行记录得到url    while(matcher.find()){              try {                      tempURL = matcher.group();           tempURL = tempURL.substring(tempURL.indexOf("\"")+1);                              if(!tempURL.contains("\""))                continue;                      tempURL = tempURL.substring(0, tempURL.indexOf("\""));                          }        catch (MalformedURLException e)           {            e.printStackTrace();        }       }    return allURLs;    }

按照“<[a|A]\\s+href=([^>]*\\s*>)”这个正则表达式可以匹配出URL 所在的整个标签,形如“<ahref="http://bbs.life.xxx.com.cn/"target="_blank">”,所以在循环获得整个标签之后,需要进一步提取出真正的URL,我们可以通过截取标签中前两个引号中间的内容来获得这段内容。如此之后,我们可以得到一个初步的属于该网页的 URL 集合。

接下来我们进行第二步操作,URL 的整理,即对之前获得的整个页面中 URL集合进行筛选和整合。整合主要是针对网页地址是相对链接的部分,由于我们可以很容易的获得当前网页的URL,所以,相对链接只需要在当前网页的 URL 上添加相对链接的字段即可组成完整的URL,从而完成整合。另一方面,在页面中包含的全面 URL中,有一些网页比如广告网页是我们不想爬取的,或者不重要的,这里我们主要针对于页面中的广告进行一个简单处理。一般网站的广告连接都有相应的显示表达,比如连接中含有“ad”等表达时,可以将该链接的优先级降低,这样就可以一定程度的避免广告链接的爬取。

经过这两步操作时候,可以把该网页的收集到的 URL 放入 URL 池中,接下来我们处理爬虫的 URL 的派分问题。

Dispatcher 分配器

分配器管理 URL,负责保存着 URL 池并且在 Gather 取得某一个网页之后派分新的URL,还要避免网页的重复收集。分配器采用设计模式中的单例模式编码,负责提供给 Gather 新的URL,因为涉及到之后的多线程改写,所以单例模式显得尤为重要。

重复收集是指物理上存在的一个网页,在没有更新的前提下,被 Gather重复访问,造成资源的浪费,主要原因是没有清楚的记录已经访问的 URL 而无法辨别。所以,Dispatcher 维护两个列表,“已访问表”,和“未访问表”。每个 URL 对应的页面被抓取之后,该 URL 放入已访问表中,而从该页面提取出来的 URL则放入未访问表中;当 Gather 向 Dispatcher 请求 URL 的时候,先验证该 URL 是否在已访问表中,然后再给Gather 进行作业。

Spider启动多个 Gather 线程

现在 Internet 中的网页数量数以亿计,而单独的一个 Gather来进行网页收集显然效率不足,所以我们需要利用多线程的方法来提高效率。Gather 的功能是收集网页,我们可以通过 Spider类来开启多个 Gather 线程,从而达到多线程的目的。代码如下:

public void start() {     Dispatcher disp = Dispatcher.getInstance();     for(int i = 0; i < gatherNum; i++){              Thread gather = new Thread(new Gather(disp));      gather.start();     }}在开启线程之后,网页收集器开始作业的运作,并在一个作业完成之后,向 Dispatcher 申请下一个作业,因为有了多线程的 Gather,为了避免线程不安全,需要对 Dispatcher 进行互斥访问,在其函数之中添加 synchronized 关键词,从而达到线程的安全访问。

0 0