轉載,network spider java

          Posted on 2009-05-14 19:42 lauber 閱讀(427) 評論(0)  編輯  收藏

          轉載:http://blog.csdn.net/mailbomb/archive/2004/11/07/171356.aspx
          創建智能網絡蜘蛛

                 ——如何使用Java網絡對象和HTML對象(翻譯)

                 作者:Mark O. Pendergast

          原文:http://www.javaworld.com/javaworld/jw-11-2004/jw-1101-spider.html

           

          摘要

                 你是否想過創建自己的符合特定標準的網站數據庫呢?網絡蜘蛛,有時也稱為網絡爬蟲,是一些根據網絡鏈接從一個網站到另外一個網站,檢查內容和記錄位置的程序。商業搜索站點使用網絡蜘蛛豐富它們的數據庫,研究人員可以使用蜘蛛獲得相關的信息。創建自己的蜘蛛搜索的內容、主機和網頁特征,比如文字密度和內置的多媒體內容。這篇文章將告訴你如何使用JavaHTML和網絡類來創建你自己的功能強大的網絡蜘蛛。

           

                 這篇文章將介紹如何在標準Java網絡對象的基礎上創建一個智能的網絡蜘蛛。蜘蛛的核心是一個基于關鍵字/短語標準和網頁特征進行深入網絡搜索的遞歸程序。搜索過程在圖形上類似于JTree結構。我主要介紹的問題,例如處理相關的URL,防止循環引用和監視內存/堆棧使用。另外,我將介紹再訪問和分解遠程網頁中如何正確是用Java網絡對象。

           

          l         蜘蛛示例程序

          示例程序包括用戶界面類SpiderControl、網絡搜索類Spider,兩個用作創建JTree顯示結果的類UrlTreeNodeUrlNodeRenderer,和兩個幫助驗證用戶界面中數字輸入的類IntegerVerifierVerifierListener。文章末尾的資源中有完整代碼和文檔的璉接。

          SpiderControl界面由三個屬性頁組成,一個用來設置搜索參數,另一個顯示結果搜索樹(JTree),第三個顯示錯誤和狀態信息,如圖1

          1 搜索參數屬性頁

          搜索參數包括訪問網站的最大數量,搜索的最大深度(鏈接到鏈接到鏈接),關鍵字/短語列表,搜索的頂級主機,起始網站或者門戶。一旦用戶輸入了搜索參數,并按下開始按鈕,網絡搜索將開始,第二個屬性頁將顯示搜索的進度。
          2 搜索樹

                 一個Spider類的實例以獨立進程的方式執行網絡搜索。獨立進程的使用是為了SpiderControl模塊可以不斷更新搜索樹顯示和處理停止搜索按鈕。當Spider運行時,它不斷在第二個屬性頁中為JTree增加節點(UrlTreeNode)。包含關鍵字和短語的搜索樹節點以藍色顯示(UrlNodeRenderer)

          當搜索完成以后,用戶可以查看站點的統計,還可以用外部瀏覽器(默認是位于Program Files目錄的Internet Explorer)查看站點。統計包括關鍵字出現次數,總字符數,總圖片數和總鏈接數。

          l         Spider

          Spider類負責搜索給出起點(入口)的網絡,一系列的關鍵字和主機,和搜索深度和大小的限制。Spider繼承了Thread,所以可以以獨立線程運行。這允許SpiderControl模塊不斷更新搜索樹顯示和處理停止搜索按鈕。

          構造方法接受包含對一個空的JTree和一個空的JtextArea引用的搜索參數。JTree被用作創建一個搜索過程中的分類站點記錄。這樣為用戶提供了可見的反饋,幫助跟蹤Spdier循環搜索的位置。JtextArea顯示錯誤和過程信息。

          構造器將參數存放在類變量中,使用UrlNodeRenderer類初始化顯示節點的JTree。直到SpiderControl調用run()方法搜索才開始。

          run()方法以獨立的線程開始執行。它首先判斷入口站點是否是一個Web引用(httpftp或者www開始)或是一個本地文件引用。它接著確認入口站點是否具有正確的符號,重置運行統計,接著調用searchWeb()開始搜索:

               public void run()

               {

                 DefaultTreeModel treeModel = (DefaultTreeModel)searchTree.getModel(); // get our model

                 DefaultMutableTreeNode root = (DefaultMutableTreeNode)treeModel.getRoot();

                 String urllc = startSite.toLowerCase();

                 if(!urllc.startsWith("http://") && !urllc.startsWith("ftp://") &&

                      !urllc.startsWith("www."))

                   {

                    startSite = "file:///"+startSite;   // Note you must have 3 slashes !

                   }

                   else // Http missing ?

                    if(urllc.startsWith("www."))

                    {

                      startSite = "http://"+startSite; // Tack on http:// 

                    }

                  

                  startSite = startSite.replace('""', '/'); // Fix bad slashes

            

                 sitesFound = 0;

                 sitesSearched = 0;

                 updateStats();

                 searchWeb(root,startSite); // Search the Web

                 messageArea.append("Done!"n"n");

               }

          searchWeb()是一個接受搜索樹父節點和搜索Web地址參數的遞歸方法。searchWeb()首先檢查給出的站點是否已被訪問和未被執行的搜索深度和站點。SearchWeb()接著允許SpiderControl運行(更新界面和檢查停止搜索按鈕是否按下)。如果所有正常,searchWeb()繼續,否則返回。

          searchWeb()開始讀和解析站點以前,它首先檢驗基于站點創建的URL對象是否具有正確的類型和主機。URL協議被檢查來確認它是一個HTML地址或者一個文件地址(不必搜索mailto:和其他協議)。接著檢查文件擴展名(如果當前有)來確認它是一個HTML文件(不必解析pdf或者gif文件)。一旦這些工作完成,通過isDomainOk()方法檢查根據用戶指定的列表檢查主機:

          ...URL url = new URL(urlstr); // Create the URL object from a string.

           

             String protocol = url.getProtocol(); // Ask the URL for its protocol

             if(!protocol.equalsIgnoreCase("http") && !protocol.equalsIgnoreCase("file"))

             {

                messageArea.append("    Skipping : "+urlstr+" not a http site"n"n");

                return;

             }

           

             String path = url.getPath(); // Ask the URL for its path

             int lastdot = path.lastIndexOf("."); // Check for file extension

             if(lastdot > 0)

             {

                String extension = path.substring(lastdot); // Just the file extension

                if(!extension.equalsIgnoreCase(".html") && !extension.equalsIgnoreCase(".htm"))

                return; // Skip everything but html files

            }

           

             if(!isDomainOk(url))

             {

                messageArea.append("    Skipping : "+urlstr+" not in domain list"n"n");

                return;

             }

           

          這里,searchWeb()公平的確定它是否有值得搜索的URL,接著它為搜索樹創建一個新節點,添加到樹中,打開一個輸入流解析文件。下面的章節涉及很多關于解析HTML文件,處理相關URL和控制遞歸的細節。

          l         解析HTML文件

          這里有兩個為了查找A HREF來解析HTML文件方法——一個麻煩的方法和一個簡單的方法。

          如果你選擇麻煩的方法,你將使用JavaStreamTokenizer類創建你自己的解析規則。使用這些技術,你必須為StreamTokenizer對象指定單詞和空格,接著去掉<>符號來查找標簽,屬性,在標簽之間分割文字。太多的工作要做。

          簡單的方法是使用內置的ParserDelegator類,一個HTMLEditorKit.Parser抽象類的子類。這些類在Java文檔中沒有完善的文檔。使用ParserDelegator有三個步驟:首先為你的URL創建一個InputStreamReader對象,接著創建一個ParserCallback對象的實例,最后創建一個ParserDelegator對象的實例并調用它的public方法parse()

             UrlTreeNode newnode = new UrlTreeNode(url); // Create the data node

             InputStream in = url.openStream(); // Ask the URL object to create an input stream

             InputStreamReader isr = new InputStreamReader(in); // Convert the stream to a reader

             DefaultMutableTreeNode treenode = addNode(parentnode, newnode); 

             SpiderParserCallback cb = new SpiderParserCallback(treenode); // Create a callback object

             ParserDelegator pd = new ParserDelegator(); // Create the delegator

             pd.parse(isr,cb,true); // Parse the stream

             isr.close(); // Close the stream

          parse()接受一個InputStreamReader,一個ParseCallback對象實例和一個指定CharSet標簽是否忽略的標志。parse()方法接著讀和解碼HTML文件,每次完成解碼一個標簽或者HTML元素后調用ParserCallback對象的方法。

          在示例代碼中,我實現了ParserCallback作為Spider的一個內部類,這樣就允許ParseCallback訪問Spider的方法和屬性?;?/span>ParserCallback的類可以覆蓋下面的方法:

          n         handleStartTag():當遇到起始HTML標簽時調用,比如>A <

          n         handleEndTag():當遇到結束HTML標簽時調用,比如>/A<

          n         handleSimpleTag():當遇到沒有匹配結束標簽時調用

          n         handleText():當遇到標簽之間的文字時調用

          在示例代碼中,我覆蓋了handleSimpleTag()以便我的代碼可以處理HTMLBASEIMG標簽。BASE標簽告訴當處理相關的URL引用時使用什么URL。如果沒有BASE標簽出現,那么當前URL就用來處理相關的引用。HandleSimpleTag()接受三個參數,一個HTML.Tag對象,一個包含所有標簽屬性的MutableAttributeSet,和在文件中的相應位置。我的代碼檢查標簽來判斷它是否是一個BASE對象實例,如果是則HREF屬性被提取出來并保存在頁面的數據節點中。這個屬性以后在處理鏈接站點的URL地址中被用到。每次遇到IMG標簽,頁面圖片數就被更新。

          我覆蓋了handleStartTag以便程序可以處理HTMLATITLE標簽。方法檢查t參數是否是一個事實上的A標簽,如果是則HREF屬性將被提取出來。

          fixHref()被用作清理大量的引用(改變反斜線為斜線,添加缺少的結束斜線),鏈接的URL通過使用基礎URL和引用創建URL對象來處理。接著遞歸調用searchWeb()來處理鏈接。如果方法遇到TITLE標簽,它就清除存儲最后遇到文字的變量以便標題的結束標記具有正確的值(有時網頁的title標簽之間沒有標題)。

          我覆蓋了handleEndTag()以便HTMLTITLE結束標記可以被處理。這個結束標記指出前面的文字(存在lastText)是頁面的標題文字。這個文字接著存在頁面的數據節點中。因為添加標題信息到數據節點中將改變樹中數據節點的顯示,nodeChanged()方法必須被調用以便樹可以更新。

          我覆蓋了handleText()方法以便HTML頁面的文字可以根據被搜索的任意關鍵字或者短語來檢查。HandleText()接受一個包含一個子符數組和該字符在文件中位置作為參數。HandleText()首先將字符數組轉換成一個String對象,在這個過程中全部轉換為大寫。接著在搜索列表中的每個關鍵字/短語根據String對象的indexof()方法來檢查。如果indexof()返回一個非負結果,則關鍵字/短語在頁面的文字中顯示。如果關鍵字/短語被顯示,匹配被記錄在匹配列表的節點中,統計數據被更新:

          public class SpiderParserCallback extends HTMLEditorKit.ParserCallback {

          /**

           * Inner class used to html handle parser callbacks

           */

           public class SpiderParserCallback extends HTMLEditorKit.ParserCallback {

                /** URL node being parsed */

                private UrlTreeNode node;

                /** Tree node */

                private DefaultMutableTreeNode treenode;

                /** Contents of last text element */

                private String lastText = "";

                /**

                 * Creates a new instance of SpiderParserCallback

                 * @param atreenode search tree node that is being parsed

                 */

               public SpiderParserCallback(DefaultMutableTreeNode atreenode) {

                      treenode = atreenode;

                      node = (UrlTreeNode)treenode.getUserObject();

               }

               /**

                * Handle HTML tags that don't have a start and end tag

                * @param t HTML tag

                * @param a HTML attributes

                * @param pos Position within file

                */

               public void handleSimpleTag(HTML.Tag t,

                                       MutableAttributeSet a,

                                       int pos)

               {

                 if(t.equals(HTML.Tag.IMG))

                 {

                   node.addImages(1);

                   return;

                 }

                  if(t.equals(HTML.Tag.BASE))

                 {

                   Object value = a.getAttribute(HTML.Attribute.HREF);

                   if(value != null)

                    node.setBase(fixHref(value.toString()));

           

                 }   

                  

               }

               /**

                * Take care of start tags

                * @param t HTML tag

                * @param a HTML attributes

                * @param pos Position within file

                */

                public void handleStartTag(HTML.Tag t,

                                       MutableAttributeSet a,

                                       int pos)

               {

                  if(t.equals(HTML.Tag.TITLE))

                 {

                   lastText="";

                   return;

                 }

                 if(t.equals(HTML.Tag.A))

                 {

                   Object value = a.getAttribute(HTML.Attribute.HREF);

                   if(value != null)

                   {

                    node.addLinks(1);

                    String href = value.toString();

                    href = fixHref(href);

                    try{

                      URL referencedURL = new URL(node.getBase(),href);

                      searchWeb(treenode, referencedURL.getProtocol()+"://"+referencedURL.getHost()+referencedURL.getPath());

                    }

                    catch (MalformedURLException e)

                    {

                      messageArea.append("   Bad URL encountered : "+href+""n"n");  

                      return; 

                    }

                   }

                 }

                   

               }

               

                /**

                * Take care of start tags

                * @param t HTML tag

                * @param pos Position within file

                */

                public void handleEndTag(HTML.Tag t,

                                          int pos)

               {

                 if(t.equals(HTML.Tag.TITLE) && lastText != null)

                 {

                   node.setTitle(lastText.trim());

                   DefaultTreeModel tm = (DefaultTreeModel)searchTree.getModel();

                   tm.nodeChanged(treenode);

                  }

                   

               }

                /**

                 * Take care of text between tags, check against keyword list for matches, if

                 * match found, set the node match status to true

                 * @param data Text between tags

                 * @param pos position of text within Webpage

                 */

                public void handleText(char[] data, int pos)

                {

                  lastText = new String(data);

                  node.addChars(lastText.length());

                  String text = lastText.toUpperCase();

                  for(int i = 0; i < keywordList.length; i++)

                  {

                    if(text.indexOf(keywordList[i]) >= 0)

                    {

                      if(!node.isMatch())

                      {

                       sitesFound++;

                       updateStats();

                      }

                      node.setMatch(keywordList[i]);

                      return;

                    }

                  }

                }

               

            

           }

           

          l         處理和補全URL

          當遇到相關頁面的鏈接,你必須在它們基礎URL上創建完整的鏈接。基礎URL可能通過BASE標簽在頁面中明確的定義,或者暗含在當前頁面的鏈接中。JavaURL對象為你解決這個問題提供了構造器,提供了根據它的鏈接結構創建相似的。

                 URL(URL context, String spec)接受spec參數的鏈接和context參數的基礎鏈接。如果spec是一個相關鏈接,構建器將使用context來創建一個完整引用的URL對象。URL它推薦URL遵循嚴格的(Unix)格式。使用反斜線,在Microsoft Windows中,而不是斜線,將是錯誤的引用。如果spec或者context指向一個目錄(包含index.htmldefault.html),而不是一個HTML文件,它必須有一個結束斜線。fixHref()方法檢查這些引用并且修正它們:

                    public static String fixHref(String href)

             {

                String newhref = href.replace('""', '/'); // Fix sloppy Web references

               int lastdot = newhref.lastIndexOf('.');

                int lastslash = newhref.lastIndexOf('/');

                if(lastslash > lastdot)

                {

                if(newhref.charAt(newhref.length()-1) != '/')

                   newhref = newhref+"/"; // Add missing /

                }

             

                return newhref;    

                

             }

          l         控制遞歸

          searchWeb()開始是為了搜索用戶指定的起始Web地址而被調用的。它接著在遇到HTML鏈接時調用自身。這形成了深度優先搜索的基礎,也帶來了兩種問題。首先非常危險的內存/堆棧溢出問題將因為太多的遞歸調用而產生。如果出現環形的引用,這個問題就將發生,也就是說,一個頁面鏈接另外一個鏈接回來的連接,這是WWW中常見的事情。為了預防這種現象,searchWeb()檢查搜索樹(通過urlHasBeenVisited()方法)來確定是否引用的頁面已經存在。如果已經存在,這個鏈接將被忽略。如果你選擇實現一個沒有搜索樹的蜘蛛,你仍然必須維護一個以訪問站點的列表(Vector或數組中)以便你可以判斷是否你正在重復訪問站點。

          遞歸的第二個問題來自深度優先的搜索和WWW的結構。根據選擇的入口,深度優先的搜索在初始頁面的初始鏈接在完成處理以前造成大量的遞歸調用。這就造成了兩種不需要的結果:首先內存/堆棧溢出可能發生,第二被搜索過的頁面可能很久才被從初始入口眾多的結果中刪除。為了控制這些,我為蜘蛛添加了最大搜索深度設置。用戶可以選擇可以達到的深度等級(鏈接到鏈接到鏈接),當遇到每個鏈接時,當前深度通過調用depthLimitExceeded()方法進行檢查。如果達到限制,鏈接就被忽略。測試僅僅檢查JTree中節點的級別。

          示例程序也增加了站點限制,用戶來指定,可以在特定數目的URL被檢查以后停止搜索,這樣確保程序可以最后停止!站點限制通過一個簡單的數字計數器sitesSearched來控制,這個數字每次調用searchWeb()后都被更新和檢查。

          l         UrlTreeNodeUrlNodeRenderer

          UrlTreeNodeUrlNodeRenderer是用來在SpiderControl用戶界面中創建JTree中個性化的樹節點的類。UrlTreeNode包含每個搜索過的站點鐘的URL信息和統計數據。UrlTreeNode以作為用戶對象屬性的標準DefaultMutableTreeNode對象形式存儲在JTree中。數據包括節點中跟蹤關鍵字出現的能力,節點的URL,節點的基礎URL,鏈接的數量,圖片的數量和字符的個數,以及節點是否符合搜索規則。

          UrlTreeNodeRendererDefaultTreeCellRenderer界面的實現。UrlTreeNodeRenderer使節點包含匹配關鍵字顯示為藍色。UrlTreeNodeRenderer也為JtreeNodes加入了個性化的圖標。個性化的顯示通過覆蓋getTreeCellRendererComponent()方法(如下)實現。這個方法在樹中創建了一個Component對象。大部分的Component屬性通過子類來進行設置,UrlTreeNodeRenderer改變了文字的顏色(前景色)和圖標:

             public Component getTreeCellRendererComponent(

                                    JTree tree,

                                    Object value,

                                    boolean sel,

                                    boolean expanded,

                                    boolean leaf,

                                    int row,

                                    boolean hasFocus) {

           

                    super.getTreeCellRendererComponent(

                                    tree, value, sel,

                                    expanded, leaf, row,

                                    hasFocus);

                   

                    UrlTreeNode node = (UrlTreeNode)(((DefaultMutableTreeNode)value).getUserObject());

                    if (node.isMatch()) // Set color

                        setForeground(Color.blue);

                     else

                        setForeground(Color.black);

                  

                    if(icon != null)    // Set a custom icon

                    {

                        setOpenIcon(icon);

                        setClosedIcon(icon);

                        setLeafIcon(icon);

                    }

                   

           

                    return this;

              }

          l         總結

          這篇文章向你展示了如何創建網絡蜘蛛和控制它的用戶界面。用戶界面使用JTree來跟蹤蜘蛛的進展和記錄訪問過的站點。當然,你也可以使用Vector來記錄訪問過的站點和使用一個簡單的計數器來顯示進展。其他增強可以包含通過數據庫記錄關鍵字和站點的接口,增加通過多個入口搜索的能力,用大量或者很少的文字內容來顯現站點,以及為搜索引擎提供同義搜索的能力。

          這篇文章中展示的Spider類使用遞歸調用搜索程序,當然,一個新蜘蛛的獨立線程可以在遇到每個鏈接時開始。這樣的好處是允許鏈接遠程URL并發執行,提高速度。然而記住那些叫做DefaultMutableTreeNodeJTree對象,不是線程安全的,所以程序員必須自己實現同步。

           

          資源:

          該文章的源代碼和Java文檔:

          http://www.javaworld.com/javaworld/jw-11-2004/spider/jw-1101-spider.zip


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           

          posts - 9, comments - 5, trackbacks - 0, articles - 0

          Copyright © lauber

          主站蜘蛛池模板: 三原县| 清流县| 汝州市| 白银市| 关岭| 灵宝市| 抚松县| 通渭县| 津南区| 萨迦县| 新晃| 平安县| 定襄县| 南投市| 金寨县| 湘潭县| 北安市| 化德县| 牟定县| 古蔺县| 汶川县| 会同县| 和龙市| 江达县| 张家界市| 镇平县| 彩票| 屯昌县| 九龙城区| 双牌县| 曲沃县| 南部县| 葵青区| 耒阳市| 成都市| 巫山县| 扎囊县| 庆阳市| 甘肃省| 会宁县| 绥江县|