解析HTML文件
這里有兩個為了查找A HREF來解析HTML文件方法——一個麻煩的方法和一個簡單的方法。
如果你選擇麻煩的方法,你將使用Java的StreamTokenizer類創(chuàng)建你自己的解析規(guī)則。使用這些技術(shù),你必須為StreamTokenizer對象指定單詞和空格,接著去掉<和>符號來查找標(biāo)簽,屬性,在標(biāo)簽之間分割文字。太多的工作要做。
簡單的方法是使用內(nèi)置的ParserDelegator類,一個HTMLEditorKit.Parser抽象類的子類。這些類在Java文檔中沒有完善的文檔。使用ParserDelegator有三個步驟:首先為你的URL創(chuàng)建一個InputStreamReader對象,接著創(chuàng)建一個ParserCallback對象的實例,最后創(chuàng)建一個ParserDelegator對象的實例并調(diào)用它的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標(biāo)簽是否忽略的標(biāo)志。parse()方法接著讀和解碼HTML文件,每次完成解碼一個標(biāo)簽或者HTML元素后調(diào)用ParserCallback對象的方法。
在示例代碼中,我實現(xiàn)了ParserCallback作為Spider的一個內(nèi)部類,這樣就允許ParseCallback訪問Spider的方法和屬性。基于ParserCallback的類可以覆蓋下面的方法:
■ handleStartTag():當(dāng)遇到起始HTML標(biāo)簽時調(diào)用,比如>A <
■ handleEndTag():當(dāng)遇到結(jié)束HTML標(biāo)簽時調(diào)用,比如>/A<
■ handleSimpleTag():當(dāng)遇到?jīng)]有匹配結(jié)束標(biāo)簽時調(diào)用
■ handleText():當(dāng)遇到標(biāo)簽之間的文字時調(diào)用
在示例代碼中,我覆蓋了handleSimpleTag()以便我的代碼可以處理HTML的BASE和IMG標(biāo)簽。BASE標(biāo)簽告訴當(dāng)處理相關(guān)的URL引用時使用什么URL。如果沒有BASE標(biāo)簽出現(xiàn),那么當(dāng)前URL就用來處理相關(guān)的引用。HandleSimpleTag()接受三個參數(shù),一個HTML.Tag對象,一個包含所有標(biāo)簽屬性的MutableAttributeSet,和在文件中的相應(yīng)位置。我的代碼檢查標(biāo)簽來判斷它是否是一個BASE對象實例,如果是則HREF屬性被提取出來并保存在頁面的數(shù)據(jù)節(jié)點中。這個屬性以后在處理鏈接站點的URL地址中被用到。每次遇到IMG標(biāo)簽,頁面圖片數(shù)就被更新。
我覆蓋了handleStartTag以便程序可以處理HTML的A和TITLE標(biāo)簽。方法檢查t參數(shù)是否是一個事實上的A標(biāo)簽,如果是則HREF屬性將被提取出來。
fixHref()被用作清理大量的引用(改變反斜線為斜線,添加缺少的結(jié)束斜線),鏈接的URL通過使用基礎(chǔ)URL和引用創(chuàng)建URL對象來處理。接著遞歸調(diào)用searchWeb()來處理鏈接。如果方法遇到TITLE標(biāo)簽,它就清除存儲最后遇到文字的變量以便標(biāo)題的結(jié)束標(biāo)記具有正確的值(有時網(wǎng)頁的title標(biāo)簽之間沒有標(biāo)題)。
我覆蓋了handleEndTag()以便HTML的TITLE結(jié)束標(biāo)記可以被處理。這個結(jié)束標(biāo)記指出前面的文字(存在lastText中)是頁面的標(biāo)題文字。這個文字接著存在頁面的數(shù)據(jù)節(jié)點中。因為添加標(biāo)題信息到數(shù)據(jù)節(jié)點中將改變樹中數(shù)據(jù)節(jié)點的顯示,nodeChanged()方法必須被調(diào)用以便樹可以更新。
我覆蓋了handleText()方法以便HTML頁面的文字可以根據(jù)被搜索的任意關(guān)鍵字或者短語來檢查。HandleText()接受一個包含一個子符數(shù)組和該字符在文件中位置作為參數(shù)。HandleText()首先將字符數(shù)組轉(zhuǎn)換成一個String對象,在這個過程中全部轉(zhuǎn)換為大寫。接著在搜索列表中的每個關(guān)鍵字/短語根據(jù)String對象的indexof()方法來檢查。如果indexof()返回一個非負(fù)結(jié)果,則關(guān)鍵字/短語在頁面的文字中顯示。如果關(guān)鍵字/短語被顯示,匹配被記錄在匹配列表的節(jié)點中,統(tǒng)計數(shù)據(jù)被更新:
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) >= 0) { if(!node.isMatch()) { sitesFound++; updateStats(); } node.setMatch(keywordList); return; } } }
}
|
引用 報告 回復(fù) |
admin
管理員    UID 1 精華 0 積分 0 帖子 418 閱讀權(quán)限 200 注冊 2007-5-8 狀態(tài) 離線
|
|
|
2、處理和補全URL
當(dāng)遇到相關(guān)頁面的鏈接,你必須在它們基礎(chǔ)URL上創(chuàng)建完整的鏈接?;A(chǔ)URL可能通過BASE標(biāo)簽在頁面中明確的定義,或者暗含在當(dāng)前頁面的鏈接中。Java的URL對象為你解決這個問題提供了構(gòu)造器,提供了根據(jù)它的鏈接結(jié)構(gòu)創(chuàng)建相似的。 URL(URL context, String spec)接受spec參數(shù)的鏈接和context參數(shù)的基礎(chǔ)鏈接。如果spec是一個相關(guān)鏈接,構(gòu)建器將使用context來創(chuàng)建一個完整引用的URL對象。URL它推薦URL遵循嚴(yán)格的(Unix)格式。使用反斜線,在Microsoft Windows中,而不是斜線,將是錯誤的引用。如果spec或者context指向一個目錄(包含index.html或default.html),而不是一個HTML文件,它必須有一個結(jié)束斜線。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;
}
3、 控制遞歸
searchWeb()開始是為了搜索用戶指定的起始Web地址而被調(diào)用的。它接著在遇到HTML鏈接時調(diào)用自身。這形成了深度優(yōu)先搜索的基礎(chǔ),也帶來了兩種問題。首先非常危險的內(nèi)存/堆棧溢出問題將因為太多的遞歸調(diào)用而產(chǎn)生。如果出現(xiàn)環(huán)形的引用,這個問題就將發(fā)生,也就是說,一個頁面鏈接另外一個鏈接回來的連接,這是WWW中常見的事情。為了預(yù)防這種現(xiàn)象,searchWeb()檢查搜索樹(通過urlHasBeenVisited()方法)來確定是否引用的頁面已經(jīng)存在。如果已經(jīng)存在,這個鏈接將被忽略。如果你選擇實現(xiàn)一個沒有搜索樹的蜘蛛,你仍然必須維護(hù)一個以訪問站點的列表(在Vector或數(shù)組中)以便你可以判斷是否你正在重復(fù)訪問站點。
遞歸的第二個問題來自深度優(yōu)先的搜索和WWW的結(jié)構(gòu)。根據(jù)選擇的入口,深度優(yōu)先的搜索在初始頁面的初始鏈接在完成處理以前造成大量的遞歸調(diào)用。這就造成了兩種不需要的結(jié)果:首先內(nèi)存/堆棧溢出可能發(fā)生,第二被搜索過的頁面可能很久才被從初始入口眾多的結(jié)果中刪除。為了控制這些,我為蜘蛛添加了最大搜索深度設(shè)置。用戶可以選擇可以達(dá)到的深度等級(鏈接到鏈接到鏈接),當(dāng)遇到每個鏈接時,當(dāng)前深度通過調(diào)用depthLimitExceeded()方法進(jìn)行檢查。如果達(dá)到限制,鏈接就被忽略。測試僅僅檢查JTree中節(jié)點的級別。
示例程序也增加了站點限制,用戶來指定,可以在特定數(shù)目的URL被檢查以后停止搜索,這樣確保程序可以最后停止!站點限制通過一個簡單的數(shù)字計數(shù)器sitesSearched來控制,這個數(shù)字每次調(diào)用searchWeb()后都被更新和檢查。
4、UrlTreeNode和UrlNodeRenderer
UrlTreeNode和UrlNodeRenderer是用來在SpiderControl用戶界面中創(chuàng)建JTree中個性化的樹節(jié)點的類。UrlTreeNode包含每個搜索過的站點鐘的URL信息和統(tǒng)計數(shù)據(jù)。UrlTreeNode以作為用戶對象屬性的標(biāo)準(zhǔn)DefaultMutableTreeNode對象形式存儲在JTree中。數(shù)據(jù)包括節(jié)點中跟蹤關(guān)鍵字出現(xiàn)的能力,節(jié)點的URL,節(jié)點的基礎(chǔ)URL,鏈接的數(shù)量,圖片的數(shù)量和字符的個數(shù),以及節(jié)點是否符合搜索規(guī)則。
UrlTreeNodeRenderer是DefaultTreeCellRenderer界面的實現(xiàn)。UrlTreeNodeRenderer使節(jié)點包含匹配關(guān)鍵字顯示為藍(lán)色。UrlTreeNodeRenderer也為JtreeNodes加入了個性化的圖標(biāo)。個性化的顯示通過覆蓋getTreeCellRendererComponent()方法(如下)實現(xiàn)。這個方法在樹中創(chuàng)建了一個Component對象。大部分的Component屬性通過子類來進(jìn)行設(shè)置,UrlTreeNodeRenderer改變了文字的顏色(前景色)和圖標(biāo):
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; }
5、 總結(jié)
這篇文章向你展示了如何創(chuàng)建網(wǎng)絡(luò)蜘蛛和控制它的用戶界面。用戶界面使用JTree來跟蹤蜘蛛的進(jìn)展和記錄訪問過的站點。當(dāng)然,你也可以使用Vector來記錄訪問過的站點和使用一個簡單的計數(shù)器來顯示進(jìn)展。其他增強可以包含通過數(shù)據(jù)庫記錄關(guān)鍵字和站點的接口,增加通過多個入口搜索的能力,用大量或者很少的文字內(nèi)容來顯現(xiàn)站點,以及為搜索引擎提供同義搜索的能力。
這篇文章中展示的Spider類使用遞歸調(diào)用搜索程序,當(dāng)然,一個新蜘蛛的獨立線程可以在遇到每個鏈接時開始。這樣的好處是允許鏈接遠(yuǎn)程URL并發(fā)執(zhí)行,提高速度。然而記住那些叫做DefaultMutableTreeNode的JTree對象,不是線程安全的,所以程序員必須自己實現(xiàn)同步。
|
|
|