學習腳步---- 網絡爬蟲(Spider)Java實現原理(轉載)

 

學習腳步---- 網絡爬蟲(Spider)Java實現原理(轉載)

                                                     網絡爬蟲(Spider)Java實現原理

     “網絡蜘蛛”或者說“網絡爬蟲”,是一種能訪問網站並跟蹤鏈接程序,通過它,可快速地畫出一個網站所包含的網頁地圖信息。本文主要講述如何使用Java編程來構建一個“蜘蛛”,我們會先以一個可複用蜘蛛類包裝一個基本的“蜘蛛”,並在示例程序中演示如何創建一個特定的“蜘蛛”來掃描相關網站找出死鏈接
    *  Java語言在此非常適合構建一個“蜘蛛”程序,其內建了對HTTP協議的支持,通過它可以傳輸大部分的網頁信息;其還內建了一個HTML解析器,正是這兩個原因使Java語言成爲本文構建“蜘蛛”程序的首選。
      文章後面

      例1的示例程序,將會掃描一個網站,並尋找死鏈接。

      *如何使用?

      使用這個程序時需先輸入一個URL並單擊“Begin”按鈕,程序開始之後,“Begin”按鈕會變成“Cancel”按鈕。在程序掃描網站期間,會在“Cancel”按鈕之下顯示進度,且在檢查當前網頁時,也會顯示相關正常鏈接與死鏈接的數目,死鏈接將顯示在程序底部的滾動文本框中。單擊“Cancel”按鈕會停止掃描過程,之後可以輸入一個新的URL;如果期間沒有單擊“Cancel”,程序將會一直運行直到查找完所有網頁,此後,“Cancel”按鈕會再次變回“Begin”,表示程序已停止。
      下面將演示示例程序是如何與可複用“Spider”類交互的,示例程序包含在例1的CheckLinks類中,這個類實現了ISpiderReportable接口,如例2所示,正是通過這個接口,蜘蛛類才能與示例程序相交互。在這個接口中,定義了三個方法

      第一個方法是“spiderFoundURL”,它在每次程序定位一個URL時被調用,如果方法返回true,表示程序應繼續執行下去並找出其中的鏈接;

      第二個方法是“spiderURLError”,它在每次程序檢測URL導致錯誤時被調用(如“404 頁面未找到”);

       第三個方法是“spiderFoundEMail”,它在每次發現電子郵件地址時被調用。有了這三個方法,Spider類就能把相關信息反饋給創建它的程序了。
     在begin方法被調用後,“蜘蛛”就開始工作了;爲允許程序重繪其用戶界面,“蜘蛛”是作爲一個單獨的線程啓動的。點擊“Begin”按鈕會開始這個後臺線程,當後臺線程運行之後,又會調用“CheckLinks”類的run方法,而run方法是由Spider對象實例化時啓動的,如下所示:
spider = new Spider(this);
spider.clear();
base = new URL(url.getText());
spider.addURL(base);
spider.begin();
     首先,一個新的Spider對象被實例化,在此,需要傳遞一個“ISpiderReportable”對象給Spider對象的構造函數,因爲“CheckLinks”類實現了“ISpiderReportable”接口,只需簡單地把它作爲當前對象(可由關鍵字this表示)傳遞給構造函數即可;其次,在程序中維護了一個其訪問過的URL列表,而“clear”方法的調用則是爲了確保程序開始時URL列表爲空,程序開始運行之前必須添加一個URL到它的待處理列表中,此時用戶輸入的URL則是添加到列表中的第一個,程序就由掃描這個網頁開始,並找到與這個起始URL相鏈接的其他頁面;最後,調用“begin”方法開始運行“蜘蛛”,這個方法直到“蜘蛛”工作完畢或用戶取消纔會返回。
     當“蜘蛛”運行時,可以調用由“ISpiderReportable”接口實現的三個方法來報告程序當前狀態,程序的大部分工作都是由“spiderFoundURL”方法來完成的,當“蜘蛛”發現一個新的URL時,它首先檢查其是否有效,如果這個URL導致一個錯誤,就會把它當作一個死鏈接;如果鏈接有效,就會繼續檢查它是否在一個不同的服務器上,如果鏈接在同一服務器上,“spiderFoundURL”返回true,表示“蜘蛛”應繼續跟蹤這個URL並找出其他鏈接,如果鏈接在另外的服務器上,就不會掃描是否還有其他鏈接,因爲這會導致“蜘蛛”不斷地瀏覽Internet,尋找更多、更多的網站,所以,示例程序只會查找用戶指定網站上的鏈接。
構造Spider類
前面已經講了如何使用Spider類,請看例3中的代碼。使用Spider類及“ISpiderReportable”接口能方便地爲某一程序添加“蜘蛛”功能,下面繼續講解Spider類是怎樣工作的。
Spider類必須保持對其訪問過的URL的跟蹤,這樣做的目的是爲了確保“蜘蛛”不會訪問同一URL一次以上;進一步來說,“蜘蛛”必須把URL分成三組:

        第一組存儲在“workloadWaiting”屬性中,包含了一個未處理的URL列表,“蜘蛛”要訪問的第一個URL也存在其中;

        第二組存儲在“workloadProcessed”中,它是“蜘蛛”已經處理過且無需再次訪問的URL;

        第三組存儲在“workloadError”中,包含了發生錯誤的URL。
Begin方法包含了Spider類的主循環,其一直重複遍歷“workloadWaiting”,並處理其中的每一個頁面,當然我們也想到了,在這些頁面被處理時,很可能有其他的URL添加到“workloadWaiting”中,所以,begin方法一直繼續此過程,直到調用Spider類的cancel方法,或“workloadWaiting”中已不再剩有URL。這個過程如下:
cancel = false;
while ( !getWorkloadWaiting().isEmpty() && !cancel ) {
Object list[] = getWorkloadWaiting().toArray();
for ( int i=0; (i
processURL((URL)list[i]);
}
當上述代碼遍歷“workloadWaiting”時,它把每個需處理的URL都傳遞“processURL”方法,而這個方法纔是真正讀取並解析URL中HTML信息的。
讀取並解析HTML
      Java同時支持訪問URL內容及解析HTML,而這正是“processURL”方法要做的。在Java中讀取URL內容相對還比較簡單,下面就是“processURL”方法實現此功能的代碼:
URLConnection connection = url.openConnection();
if ( (connection.getContentType()!=null) &&!connection.getContentType().toLowerCase().startsWith("text/") ) {
getWorkloadWaiting().remove(url);
getWorkloadProcessed().add(url);
log("Not processing because content type is: " +
connection.getContentType() );
return;
}
首先,爲每個傳遞進來的變量url中存儲的URL構造一個“URLConnection”對象,因爲網站上會有多種類型的文檔,而“蜘蛛”只對那些包含HTML,尤其是基於文本的文檔感興趣。前述代碼是爲了確保文檔內容以“text/”打頭,如果文檔類型爲非文本,會從等待區移除此URL,並把它添加到已處理區,這也是爲了保證不會再次訪問此URL。在對特定URL建立連接之後,接下來就要解析其內容了。下面的代碼打開了URL連接,並讀取內容:

InputStream is = connection.getInputStream();

Reader r = new InputStreamReader(is);

現在,我們有了一個Reader對象,可以用它來讀取此URL的內容,對本文中的“蜘蛛”來說,只需簡單地把其內容傳遞給HTML解析器就可以了。本例中使用的HTML解析器爲Swing HTML解析器,其由Java內置,但由於Java對HTML解析的支持力度不夠,所以必須重載一個類來實現對HTML解析器的訪問,這就是爲什麼我們要調用“HTMLEditorKit”類中的“getParser”方法。但不幸的是,Sun公司把這個方法置爲protected,唯一的解決辦法就是創建自己的類並重載“getParser”方法,並把它置爲public,這由“HTMLParse”類來實現,請看例4:

import javax.swing.text.html.*;

public class HTMLParse extends HTMLEditorKit {

public HTMLEditorKit.Parser getParser()

{

return super.getParser();

}

}

這個類用在Spider類的“processURL”方法中,我們也會看到,Reader對象會用於讀取傳遞到“HTMLEditorKit.Parser”中網頁的內容:

HTMLEditorKit.Parser parse = new HTMLParse().getParser();

parse.parse(r,new Parser(url),true);

請留意,這裏又構造了一個新的Parser類,這個Parser類是一個Spider類中的內嵌類,而且還是一個回調類,它包含了對應於每種HTML tag將要調用的特定方法。在本文中,我們只需關心兩類回調函數,它們分別對應一個簡單tag(即不帶結束tag的tag,如

)和一個開始tag,這兩類回調函數名爲“handleSimpleTag”和“handleStartTag”。因爲每種的處理過程都是一樣的,所以“handleStartTag”方法僅是簡單地調用“handleSimpleTag”,而“handleSimpleTag”則會負責從文檔中取出超鏈接,這些超鏈接將會用於定位“蜘蛛”要訪問的其他頁面。在當前tag被解析時,“handleSimpleTag”會檢查是否存在一個“href”或超文本引用:

String href = (String)a.getAttribute(HTML.Attribute.HREF);

if( (href==null) && (t==HTML.Tag.FRAME) )

href = (String)a.getAttribute(HTML.Attribute.SRC);

if ( href==null )

return;

如果不存在“href”屬性,會繼續檢查當前tag是否爲一個Frame,Frame會使用一個“src”屬性指向其他頁面,一個典型的超鏈接通常爲以下形式:

上面鏈接中的“href”屬性指向其鏈接到的頁面,但是“linkedpage.html”不是一個地址,它只是指定了這個Web服務器上一個頁面上的某處,這稱爲相對URL,相對URL必須被解析爲絕對URL,而這由以下代碼完成:

URL url = new URL(base,str);

這又會構造一個URL,str爲相對URL,base爲這個URL上的頁面,這種形式的URL類構造函數可構造一個絕對URL。在URL變爲正確的絕對形式之後,通過檢查它是否在等待區,來確認此URL是否已經被處理過。如果此URL沒有被處理過,它會添加到等待區,之後,它會像其他URL一樣被處理。

相關的代碼如下所示:

1.CheckLinks.java

import java.awt.*;
       import javax.swing.*;
       import java.net.*;
       import java.io.*;
public class CheckLinks extends javax.swing.JFrame implements
             Runnable,ISpiderReportable {

 
  public CheckLinks()
  {
    //{{INIT_CONTROLS
    setTitle("Find Broken Links");
    getContentPane().setLayout(null);
    setSize(405,288);
    setVisible(true);
    label1.setText("Enter a URL:");
    getContentPane().add(label1);
    label1.setBounds(12,12,84,12);
    begin.setText("Begin");
    begin.setActionCommand("Begin");
    getContentPane().add(begin);
    begin.setBounds(12,36,84,24);
    getContentPane().add(url);
    url.setBounds(108,36,288,24);
    errorScroll.setAutoscrolls(true);
    errorScroll.setHorizontalScrollBarPolicy(javax.swing.
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
    errorScroll.setVerticalScrollBarPolicy(javax.swing.
                ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
    errorScroll.setOpaque(true);
    getContentPane().add(errorScroll);
    errorScroll.setBounds(12,120,384,156);
    errors.setEditable(false);
    errorScroll.getViewport().add(errors);
    errors.setBounds(0,0,366,138);
    current.setText("Currently Processing: ");
    getContentPane().add(current);
    current.setBounds(12,72,384,12);
    goodLinksLabel.setText("Good Links: 0");
    getContentPane().add(goodLinksLabel);
    goodLinksLabel.setBounds(12,96,192,12);
    badLinksLabel.setText("Bad Links: 0");
    getContentPane().add(badLinksLabel);
    badLinksLabel.setBounds(216,96,96,12);
    //}}
  //{{INIT_MENUS
    //}}

    //{{REGISTER_LISTENERS
    SymAction lSymAction = new SymAction();
    begin.addActionListener(lSymAction);
    //}}
  }

 
  static public void main(String args[])
  {
    (new CheckLinks()).setVisible(true);
  }

 
  public void addNotify()
  {
    // Record the size of the window prior to calling parent's
    // addNotify.
    Dimension size = getSize();

super.addNotify();

    if ( frameSizeAdjusted )
      return;
    frameSizeAdjusted = true;

// Adjust size of frame according to the insets and menu bar
    Insets insets = getInsets();
    javax.swing.JMenuBar menuBar = getRootPane().getJMenuBar();
    int menuBarHeight = 0;
    if ( menuBar != null )
      menuBarHeight = menuBar.getPreferredSize().height;
    setSize(insets.left + insets.right + size.width, insets.top +
                          insets.bottom + size.height +
                          menuBarHeight);
  }

  // Used by addNotify
  boolean frameSizeAdjusted = false;

  //{{DECLARE_CONTROLS
  javax.swing.JLabel label1 = new javax.swing.JLabel();

 
  javax.swing.JButton begin = new javax.swing.JButton();

 
  javax.swing.JTextField url = new javax.swing.JTextField();

 
  javax.swing.JScrollPane errorScroll =
        new javax.swing.JScrollPane();

 
  javax.swing.JTextArea errors = new javax.swing.JTextArea();
  javax.swing.JLabel current = new javax.swing.JLabel();
  javax.swing.JLabel goodLinksLabel = new javax.swing.JLabel();
  javax.swing.JLabel badLinksLabel = new javax.swing.JLabel();
  //}}

  //{{DECLARE_MENUS
  //}}

 
  protected Thread backgroundThread;

 
  protected Spider spider;

 
  protected URL base;

 
  protected int badLinksCount = 0;

 
  protected int goodLinksCount = 0;


 
  class SymAction implements java.awt.event.ActionListener {
    public void actionPerformed(java.awt.event.ActionEvent event)
    {
      Object object = event.getSource();
      if ( object == begin )
        begin_actionPerformed(event);
    }
  }

 
  void begin_actionPerformed(java.awt.event.ActionEvent event)
  {
    if ( backgroundThread==null ) {
      begin.setLabel("Cancel");
      backgroundThread = new Thread(this);
      backgroundThread.start();
      goodLinksCount=0;
      badLinksCount=0;
    } else {
      spider.cancel();
    }

  }

 
  public void run()
  {
    try {
      errors.setText("");
      spider = new Spider(this);
      spider.clear();
      base = new URL(url.getText());
      spider.addURL(base);
      spider.begin();
      Runnable doLater = new Runnable()
      {
        public void run()
        {
          begin.setText("Begin");
        }
      };
      SwingUtilities.invokeLater(doLater);
      backgroundThread=null;

    } catch ( MalformedURLException e ) {
      UpdateErrors err = new UpdateErrors();
      err.msg = "Bad address.";
      SwingUtilities.invokeLater(err);

    }
  }

 
  public boolean spiderFoundURL(URL base,URL url)
  {
    UpdateCurrentStats cs = new UpdateCurrentStats();
    cs.msg = url.toString();
    SwingUtilities.invokeLater(cs);

    if ( !checkLink(url) ) {
      UpdateErrors err = new UpdateErrors();
      err.msg = url+"(on page " + base + ")\n";
      SwingUtilities.invokeLater(err);
      badLinksCount++;
      return false;
    }

    goodLinksCount++;
    if ( !url.getHost().equalsIgnoreCase(base.getHost()) )
      return false;
    else
      return true;
  }

 
  public void spiderURLError(URL url)
  {
  }

 
  protected boolean checkLink(URL url)
  {
    try {
      URLConnection connection = url.openConnection();
      connection.connect();
      return true;
    } catch ( IOException e ) {
      return false;
    }
  }

 
  public void spiderFoundEMail(String email)
  {
  }
 

class UpdateErrors implements Runnable {
    public String msg;
    public void run()
    {
      errors.append(msg);
    }
  }
 

  class UpdateCurrentStats implements Runnable {
    public String msg;
    public void run()
    {
      current.setText("Currently Processing: " + msg );
      goodLinksLabel.setText("Good Links: " + goodLinksCount);
      badLinksLabel.setText("Bad Links: " + badLinksCount);
    }
  }
}

2.ISpiderReportable .java

import java.net.*;

interface ISpiderReportable {
  public boolean spiderFoundURL(URL base,URL url);
  public void spiderURLError(URL url);
  public void spiderFoundEMail(String email);
}

3.Spider .java

import java.util.*;
       import java.net.*;
       import java.io.*;
       import javax.swing.text.*;
       import javax.swing.text.html.*;


public class Spider {

 
  protected Collection workloadError = new ArrayList(3);

 
  protected Collection workloadWaiting = new ArrayList(3);

 
  protected Collection workloadProcessed = new ArrayList(3);

 
  protected ISpiderReportable report;

 
  protected boolean cancel = false;

 
  public Spider(ISpiderReportable report)
  {
    this.report = report;
  }

 
  public Collection getWorkloadError()
  {
    return workloadError;
  }

 
  public Collection getWorkloadWaiting()
  {
    return workloadWaiting;
  }

 
  public Collection getWorkloadProcessed()
  {
    return workloadProcessed;
  }   

 
  public void clear()
  {
    getWorkloadError().clear();
    getWorkloadWaiting().clear();
    getWorkloadProcessed().clear();
  }

 
  public void cancel()
  {
    cancel = true;
  }

 
  public void addURL(URL url)
  {
    if ( getWorkloadWaiting().contains(url) )
      return;
    if ( getWorkloadError().contains(url) )
      return;
    if ( getWorkloadProcessed().contains(url) )
      return;
    log("Adding to workload: " + url );
    getWorkloadWaiting().add(url);
  }

 
  public void processURL(URL url)
  {
    try {
      log("Processing: " + url );
      // get the URL's contents
      URLConnection connection = url.openConnection();
      if ( (connection.getContentType()!=null) &&
           !connection.getContentType().toLowerCase().startsWith("text/") ) {
        getWorkloadWaiting().remove(url);
        getWorkloadProcessed().add(url);
        log("Not processing because content type is: " +
             connection.getContentType() );
        return;
      }
     
      // read the URL
      InputStream is = connection.getInputStream();
      Reader r = new InputStreamReader(is);
      // parse the URL
      HTMLEditorKit.Parser parse = new HTMLParse().getParser();
      parse.parse(r,new Parser(url),true);
    } catch ( IOException e ) {
      getWorkloadWaiting().remove(url);
      getWorkloadError().add(url);
      log("Error: " + url );
      report.spiderURLError(url);
      return;
    }
    // mark URL as complete
    getWorkloadWaiting().remove(url);
    getWorkloadProcessed().add(url);
    log("Complete: " + url );

  }

 
  public void begin()
  {
    cancel = false;
    while ( !getWorkloadWaiting().isEmpty() && !cancel ) {
      Object list[] = getWorkloadWaiting().toArray();
      for ( int i=0;(i<list.length)&&!cancel;i++ )
        processURL((URL)list[i]);
    }
  }


  protected class Parser
  extends HTMLEditorKit.ParserCallback {
    protected URL base;

    public Parser(URL base)
    {
      this.base = base;
    }

    public void handleSimpleTag(HTML.Tag t,
                                MutableAttributeSet a,int pos)
    {
      String href = (String)a.getAttribute(HTML.Attribute.HREF);
     
      if( (href==null) && (t==HTML.Tag.FRAME) )
        href = (String)a.getAttribute(HTML.Attribute.SRC);
       
      if ( href==null )
        return;

      int i = href.indexOf('#');
      if ( i!=-1 )
        href = href.substring(0,i);

      if ( href.toLowerCase().startsWith("mailto:") ) {
        report.spiderFoundEMail(href);
        return;
      }

      handleLink(base,href);
    }

    public void handleStartTag(HTML.Tag t,
                               MutableAttributeSet a,int pos)
    {
      handleSimpleTag(t,a,pos);    // handle the same way

    }

    protected void handleLink(URL base,String str)
    {
      try {
        URL url = new URL(base,str);
        if ( report.spiderFoundURL(base,url) )
          addURL(url);
      } catch ( MalformedURLException e ) {
        log("Found malformed URL: " + str );
      }
    }
  }

 
  public void log(String entry)
  {
    System.out.println( (new Date()) + ":" + entry );
  }
}

4.HTMLParse .java

import javax.swing.text.html.*;

public class HTMLParse extends HTMLEditorKit {

  public HTMLEditorKit.Parser getParser()
  {
    return super.getParser();
  }
}

 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/wuhailin2005/archive/2009/01/08/3736026.aspx

發佈了0 篇原創文章 · 獲贊 5 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章