Java網絡爬蟲講解

網絡蜘蛛即Web Spider,是一個很形象的名字。把互聯網比喻成一個蜘蛛網,那麼Spider就是在網上爬來爬去的蜘蛛。網絡蜘蛛是通過網頁的鏈接地址來尋找網頁,從 網站某一個頁面(通常是首頁)開始,讀取網頁的內容,找到在網頁中的其它鏈接地址,然後通過這些鏈接地址尋找下一個網頁,這樣一直循環下去,直到把這個網站所有的網頁都抓取完爲止。如果把整個互聯網當成一個網站,那麼網絡蜘蛛就可以用這個原理把互聯網上所有的網頁都抓取下來。

  對於搜索引擎來說,要抓取互聯網上所有的網頁幾乎是不可能的,從目前公佈的數據來看,容量最大的搜索引擎也不過是抓取了整個網頁數量的百分之四十左右。這其中的原因一方面是抓取技術的瓶頸,無法遍歷所有的網頁,有許多網頁無法從其它網頁的鏈接中找到;另一個原因是存儲技術和處理技術的問題,如果按照每個頁面的平均大小爲20K計算(包含圖片),100億網頁的容量是100×2000G字節,即使能夠存儲,下載也存在問題(按照一臺機器每秒下載 20K計算,需要340臺機器不停的下載一年時間,才能把所有網頁下載完畢)。同時,由於數據量太大,在提供搜索時也會有效率方面的影響。因此,許多搜索引擎的網絡蜘蛛只是抓取那些重要的網頁,而在抓取的時候評價重要性主要的依據是某個網頁的鏈接深度。

  在抓取網頁的時候,網絡蜘蛛一般有兩種策略:廣度優先和深度優先。

   廣度優先是指網絡蜘蛛會先抓取起始網頁中鏈接的所有網頁,然後再選擇其中的一個鏈接網頁,繼續抓取在此網頁中鏈接的所有網頁。這是最常用的方式,因爲這個方法可以讓網絡蜘蛛並行處理,提高其抓取速度。深度優先是指網絡蜘蛛會從起始頁開始,一個鏈接一個鏈接跟蹤下去,處理完這條線路之後再轉入下一個起始頁, 繼續跟蹤鏈接。這個方法有個優點是網絡蜘蛛在設計的時候比較容易。兩種策略的區別,下圖的說明會更加明確。

  由於不可能抓取所有的網頁,有些網絡蜘蛛對一些不太重要的網站,設置了訪問的層數。例如,在上圖中,A爲起始網頁,屬於0層,B、C、D、 E、F屬於第1層,G、H屬於第2層, I屬於第3層。如果網絡蜘蛛設置的訪問層數爲2的話,網頁I是不會被訪問到的。這也讓有些網站上一部分網頁能夠在搜索引擎上搜索到,另外一部分不能被搜索到。對於網站設計者來說,扁平化的網站結構設計有助於搜索引擎抓取其更多的網頁。

  網絡蜘蛛在訪問網站網頁的時候,經常會遇到加密數據和網頁權限的問題,有些網頁是需要會員權限才能訪問。當然,網站的所有者可以通過協議讓網 絡蜘蛛不去抓取(下小節會介紹),但對於一些出售報告的網站,他們希望搜索引擎能搜索到他們的報告,但又不能完全**的讓搜索者查看,這樣就需要給網絡蜘 蛛提供相應的用戶名和密碼。網絡蜘蛛可以通過所給的權限對這些網頁進行網頁抓取,從而提供搜索。而當搜索者點擊查看該網頁的時候,同樣需要搜索者提供相應的權限驗證。

 網站與網絡蜘蛛

  網絡蜘蛛需要抓取網頁,不同於一般的訪問,如果控制不好,則會引起網站服務器負擔過重。去年4月,淘寶http://www.taobao.com)就因爲雅虎搜索引擎的網絡蜘蛛抓取其數據引起淘寶網服務器的不穩定。網站是否就無法和網絡蜘蛛交流呢?其實不然,有多種方法可以讓網站和網絡蜘蛛進行交流。一方面讓網站管理員瞭解網絡蜘蛛都來自哪兒,做了些什麼,另一方面也告訴網絡蜘蛛哪些網頁不應該抓取,哪 些網頁應該更新。

  每個網絡蜘蛛都有自己的名字,在抓取網頁的時候,都會向網站標明自己的身份。網絡蜘蛛在抓取網頁的時候會發送一個請求,這個請求中就有一個字段爲User-agent,用於標識此網絡蜘蛛的身份。例如Google網絡蜘蛛的標識爲GoogleBot,Baidu網絡蜘蛛的標識爲BaiDuSpider, Yahoo網絡蜘蛛的標識爲Inktomi Slurp。如果在網站上有訪問日誌記錄,網站管理員就能知道,哪些搜索引擎的網絡蜘蛛過來過,什麼時候過來的,以及讀了多少數據等等。如果網站管理員發現某個蜘蛛有問題,就通過其標識來和其所有者聯繫。下面是博客中http://www.blogchina.com)2004年5月15日的搜索引擎訪問日誌:

  網絡蜘蛛進入一個網站,一般會訪問一個特殊的文本文件Robots.txt,這個文件一般放在網站服務器的根目錄下,http://www.blogchina.com/robots.txt。網站管理員可以通過robots.txt來定義哪些目錄網絡蜘蛛不能訪問,或者哪些目錄對於某些特定的網絡蜘蛛不能訪問。例如有些網站的可執行文件目錄和臨時文件目錄不希望被搜索引擎搜索到,那麼網站管理員就可以把這些目錄定義爲拒絕訪問目錄。Robots.txt語法很簡單,例如如果對目錄沒有任何限制,可以用以下兩行來描述:
  User-agent: * 
  Disallow:

  當然,Robots.txt只是一個協議,如果網絡蜘蛛的設計者不遵循這個協議,網站管理員也無法阻止網絡蜘蛛對於某些頁面的訪問,但一般的網絡蜘蛛都會遵循這些協議,而且網站管理員還可以通過其它方式來拒絕網絡蜘蛛對某些網頁的抓取。

  網絡蜘蛛在下載網頁的時候,會去識別網頁的HTML代碼,在其代碼的部分,會有META標識。通過這些標識,可以告訴網絡蜘蛛本網頁是否需要被抓取,還可以告訴網絡蜘蛛本網頁中的鏈接是否需要被繼續跟蹤。例如:表示本網頁不需要被抓取,但是網頁內的鏈接需要被跟蹤。

  現在一般的網站都希望搜索引擎能更全面的抓取自己網站的網頁,因爲這樣可以讓更多的訪問者能通過搜索引擎找到此網站。爲了讓本網站的網頁更全面被抓取到,網站管理員可以建立一個網站地圖,即SiteMap。許多網絡蜘蛛會把sitemap.htm文件作爲一個網站網頁爬取的入口,網站管理員可以把網站內部所有網頁的鏈接放在這個文件裏面,那麼網絡蜘蛛可以很方便的把整個網站抓取下來,避免遺漏某些網頁,也會減小對網站服務器的負擔。

  內容提取

  搜索引擎建立網頁索引,處理的對象是文本文件。對於網絡蜘蛛來說,抓取下來網頁包括各種格式,包括html、圖片、doc、pdf、多媒體、 動態網頁及其它格式等。這些文件抓取下來後,需要把這些文件中的文本信息提取出來。準確提取這些文檔的信息,一方面對搜索引擎的搜索準確性有重要作用,另一方面對於網絡蜘蛛正確跟蹤其它鏈接有一定影響。

  對於doc、pdf等文檔,這種由專業廠商提供的軟件生成的文檔,廠商都會提供相應的文本提取接口。網絡蜘蛛只需要調用這些插件的接口,就可以輕鬆的提取文檔中的文本信息和文件其它相關的信息。

  HTML等文檔不一樣,HTML有一套自己的語法,通過不同的命令標識符來表示不同的字體、顏色、位置等版式,如:、、等,提取文本信息時需要把這些標識符都過濾掉。過濾標識符並非難事,因爲這些標識符都有一定的規則,只要按照不同的標識符取得相應的信息即可。但在識別這些信息的時候,需要同步記錄許多版式信息,例如文字的字體大小、是否是標題、是否是加粗顯示、是否是頁面的關鍵詞等,這些信息有助於計算單詞在網頁中的重要程度。同時,對於 HTML網頁來說,除了標題和正文以外,會有許多廣告鏈接以及公共的頻道鏈接,這些鏈接和文本正文一點關係也沒有,在提取網頁內容的時候,也需要過濾這些 無用的鏈接。例如某個網站有“產品介紹”頻道,因爲導航條在網站內每個網頁都有,若不過濾導航條鏈接,在搜索“產品介紹”的時候,則網站內每個網頁都會搜索到,無疑會帶來大量垃圾信息。過濾這些無效鏈接需要統計大量的網頁結構規律,抽取一些共性,統一過濾;對於一些重要而結果特殊的網站,還需要個別處理。這就需要網絡蜘蛛的設計有一定的擴展性。

  對於多媒體、圖片等文件,一般是通過鏈接的錨文本(即,鏈接文本)和相關的文件註釋來判斷這些文件的內容。例如有一個鏈接文字爲“張曼玉照片 ”,其鏈接指向一張bmp格式的圖片,那麼網絡蜘蛛就知道這張圖片的內容是“張曼玉的照片”。這樣,在搜索“張曼玉”和“照片”的時候都能讓搜索引擎找到這張圖片。另外,許多多媒體文件中有文件屬性,考慮這些屬性也可以更好的瞭解文件的內容。

  動態網頁一直是網絡蜘蛛面臨的難題。所謂動態網頁,是相對於靜態網頁而言,是由程序自動生成的頁面,這樣的好處是可以快速統一更改網頁風格,也可以減少網頁所佔服務器的空間,但同樣給網絡蜘蛛的抓取帶來一些麻煩。由於開發語言不斷的增多,動態網頁的類型也越來越多,如:asp、jsp、php 等。這些類型的網頁對於網絡蜘蛛來說,可能還稍微容易一些。網絡蜘蛛比較難於處理的是一些腳本語言(如VBScript和javascript)生成的網頁,如果要完善的處理好這些網頁,網絡蜘蛛需要有自己的腳本解釋程序。對於許多數據是放在數據庫的網站,需要通過本網站的數據庫搜索才能獲得信息,這些給網絡蜘蛛的抓取帶來很大的困難。對於這類網站,如果網站設計者希望這些數據能被搜索引擎搜索,則需要提供一種可以遍歷整個數據庫內容的方法。

  對於網頁內容的提取,一直是網絡蜘蛛中重要的技術。整個系統一般採用插件的形式,通過一個插件管理服務程序,遇到不同格式的網頁採用不同的插件處理。這種方式的好處在於擴充性好,以後每發現一種新的類型,就可以把其處理方式做成一個插件補充到插件管理服務程序之中。

  更新週期

  由於網站的內容經常在變化,因此網絡蜘蛛也需不斷的更新其抓取網頁的內容,這就需要網絡蜘蛛按照一定的週期去掃描網站,查看哪些頁面是需要更新的頁面,哪些頁面是新增頁面,哪些頁面是已經過期的死鏈接。

  搜索引擎的更新週期對搜索引擎搜索的查全率有很大影響。如果更新週期太長,則總會有一部分新生成的網頁搜索不到;週期過短,技術實現會有一定難度,而且會對帶寬、服務器的資源都有浪費。搜索引擎的網絡蜘蛛並不是所有的網站都採用同一個週期進行更新,對於一些重要的更新量大的網站,更新的週期短,如有些新聞網站,幾個小時就更新一次;相反對於一些不重要的網站,更新的週期就長,可能一兩個月才更新一次。

  一般來說,網絡蜘蛛在更新網站內容的時候,不用把網站網頁重新抓取一遍,對於大部分的網頁,只需要判斷網頁的屬性(主要是日期),把得到的屬性和上次抓取的屬性相比較,如果一樣則不用更新。


            Spider的實現細節

a.  URL 的組織和管理考慮到系統自身的資源和時間有限,Spider程序應儘可能的對鏈接進行篩選,以保證獲取信息的質量和效率。Spider程序對新URL 的選擇往往與搜索引擎的類型、目標集合、能夠處理信息的類型、資源的限制和是否支持Robots限制協議有關。

概括爲以下幾點:

訪問過的和重複的URL排除

文件類型必須被系統處理,不能處理的URL排除

不在目標集合中的排除,被Rohots.txt限制的排除

URL排序也是減輕系統負擔的重要手段之一。這就要求計算URL的重要性,如果評估新URI的重要性較高,則會沖掉舊的URL。無論任何情況下,對 Spider而言,首先訪問目標集合中的重要站點都是意義和重要的。但是一個頁面的重要性的準確評估只能在分析其內容之後進行。可以根據一個頁面鏈接數量的多少來評估此頁面是否重要;或者對URL 地址進行解析其中的內容例如以".com", ".edu",".cn"就較爲重要一些,或者可以根據頁而標題與當前的熱點問題是否相近或相關來評定其頁面的重要性。決定網站或頁面的重要性的因素很多,也根據各個搜索引擎的側重點不同而各異,最終的評估方法都依賴於該搜索引擎對於資源獲取的要求來決定。影響Spider速度的一種重要因素是DNS查詢,爲此每個 Spider都要維護一個自己的DNS緩衝。這樣每個鏈接都處於不同的狀態,包括:DNS 查詢、連接到主機、發送請求、得到響應。這些因素綜合起來使得Spider變成一個非常複雜的系統。

b. Spider的遍歷規則:頁面的遍歷主要有兩種方式:深度遍歷和廣度遍歷。深度遍歷算法可以獲得的信息較爲集中,信息比較完整,但覆蓋面就比較有限,廣度遍歷算法則剛好相反。

c. Spider實現中的主要問題:雖然Spider的功能很強,但也存在不少的問題:

(1)如果一組URL地址沒有被組外URL所鏈接到,那麼Spider就找不到它們。由於spider不能更新過快(因爲網絡帶寬是有限的,更新過快就會影響其他用戶的正常使用),難免有不能及時加入的新網站或新頁面。

(2)spider程序在遍歷Web時也存在危險,很可能遇到一個環鏈接而陷入死循環中。簡單的避免方法就是忽略已訪問過的URL,或限制網站的遍歷深度。

(3) Spider程序時大型搜索引擎中很脆弱的部分,因爲它與很多的Web報務器、不同的域名服務器打交道,而這些服務完全在系統的控制之外。由於網絡上包含了大量的垃圾信息,Spider很可能會收取這些垃圾信息。一個頁面出現問題也很可能引起Spider程序中止、崩潰或其他不可預料的行爲。因此訪問 Internet的Spider程序應該設計得非常強壯,充分考慮各種可能遇到的情況,讓Spider在遇到各種情況時可以採取相應的處理行爲,而不至於獲得一些垃圾信息或者直接就對程序本身造成危害。

Spider構架

發現、蒐集網頁信息需要有高性能的“網絡蜘蛛”程序〔Spider〕去自動地在互聯網中搜索信息。一個典型的網絡蜘蛛工作的方式:查看一個頁面,並從中找到相關信息,然後它再從該頁面的所有鏈接中出發,繼續尋找相關的信息,以此類推。網絡蜘蛛在搜索引擎整體結構中的位置如下圖所示: 初始化時,網絡蜘蛛一般指向一個URL ( Uniform ResourceLocator)池。在遍歷Internet的過程中,按照深度優先或廣度優先或其他啓發式算法從URL池中取出若干URL進行處理,同時將未訪問的 URL放入URL池中,這樣處理直到URL池空爲止。對Web文檔的索引則根據文檔的標題、首段落甚至整個頁面內容進行,這取決於搜索服務的數據收集策略。

網絡蜘蛛在漫遊的過程中,根據頁面的標題、頭、鏈接等生成摘要放在索引數據庫中。如果是全文搜索,還需要將整個頁面的內容保存到本地數據庫。網絡蜘蛛爲實現其快速地瀏覽整個互聯網,通常在技術上採用搶先式多線程技術實現在網上搜索信息。通過搶先式多線程的使用,你能索引一個基於URL鏈接的 Web頁面,啓動一個新的線程跟隨每個新的URL鏈接,索引一個新的URL起點。當然在服務器上所開的線程也不能無限膨脹,需要在服務器的正常運轉和快速 收集網頁之間找一個平衡點。

在整個搜索引擎工作過程中,整個蜘蛛的數據入口是URL地址,數據出口是Web頁倉庫。Spider程序發現URL鏈接以後,經過Stor處理模塊,將我們所需要的網頁數據存儲在Web頁倉庫中,爲以後的形成網頁快照、網頁分析提供基礎數據。在Spider程序工作的過程中,發現新的鏈接,對該鏈接進行分析,形成新的搜索地址,作爲下一次Spider程序的數據輸入。這個過程的實現就是Spider程序的隊列管理。

Spider程序的工作過程,簡單來講,就是不斷髮現新的鏈接,並對該鏈接對應的頁面分析存儲的工程。如下圖所示,

一、索引器: 索引器的功能是理解搜索器所蒐集的信息,從中抽取出索引項,用於表示文檔以及生成文檔庫的索引表。索引項有客觀索引項內容索引項兩種: 客觀項:與文檔的語意內容無關,如作者名、URL、更新時間、編碼、長度、鏈接流行度(Link Popularity)等等; 內容索引項:是用來反映文檔內容的,如關鍵詞及其權重、短語、詞、字等等。內容索引項可以分爲單索引項和多索引項(或稱短語索引項)兩種。單索引項對於英文來講是英語單詞,比較容易提取,因爲單詞之間有天然的分隔符(空格);對於中文等連續書寫的語言,必須採用多索引項,進行詞語的切分。索引器可以使用集中式索引算法或分佈式索引算法。當數據量很大時,必須實現實時索引(Real-time Indexing),否則不能夠跟上信息量急劇增加的速度。索引算法對索引器的性能(如大規模峯值查詢時的響應速度)有很大的影響。一個搜索引擎的有效性 在很大程度取決於索引的質量。 由於漢文字符多,處理複雜,中文詞的處理不容易。索引器中的中文分詞技術: 一個分詞系統=分詞程序+分詞詞典(1)最大匹配法MM (2)反向最大匹配法RMM (1)最佳匹配法OM (1)雙向掃描法[百度的分詞就採用了雙向掃描法] 系統關鍵是:分詞精度和分詞速度

二、建立索引的方法: 爲了加快檢索速度,搜索引擎要對Snider程序蒐集到的信,建立倒排索引。 (1)全文索引和部分索引有些搜索引擎對於信息庫中的頁面建立全文索引,有些只建立摘要部分索引或者每個段落前面部分的索引。還有些搜索引擎在建立索引時,要同時考慮超文本的不同標記所表示的含義,如粗體、大字體顯示的東西往往比較重要。有些搜索引擎還在建立索引的過程中收集頁面中的超鏈接。這些超鏈接反映了收集到的信息之間的空間結構。利用這些結果信息可以提高頁面相關度判別時的準確度。(2)是否過濾無用詞由於網頁中存在這許多無用(無實際意義)單詞,例如“啊”、“的”等。這此詞往往不能明確表達該網頁信息,所以有些搜索引擎保存一個無用詞彙表,在建立索引時將不對這些詞彙建立索引。 (3)是否使用Meta標記中的信息網頁中的Meta標記用來標註一些非常顯示性的信息。有些網頁將頁面的關鍵詞等信息放在其中。便於在建立索引的過程中提高這些詞彙的相關度。(4)是否對圖像標記中的替換文本(ALT text)或頁面中的註解建立索引由於現有的搜索引擎對圖像的檢索技術還不太成熟,大多數搜索引擎不支持圖像的檢索。在超文木的結構頁面中,圖像標記中往往存放着圖像的替換信息。這些信息說明了該圖像對應的圖像的基本信息。(5)是否支持詞幹提取技術

三、建立索引的過程: 分析過程對文檔進行索引並存儲到存儲桶中排序過程

Spider處理流程

當一個URL被加入到等待隊列中時Spider程序就會開始運行。只要等待隊列中有一個網頁或Spider程序正在處理一個網頁,Spider程序就會繼續它的工作。當等待隊列爲空並且當前沒有處理任何網頁,Spider程序就會停止它的工作。

Spider程序實現初探

Spider 程序是從網上下載Web頁面再對其進行處理,爲了提高效率,很顯然要採用多線程的方法,幾個Spider線程同時並行工作,訪問不同的鏈接。構造 Spider程序有兩種方式。第一種是將它設計爲遞歸程序,第二種是將它編寫成非遞歸的程序。遞歸是在一個方法中調用它本身的程序設計技術。當需要重複做同樣的基本仟務或在處理先前任務時可展現將來的任務信息時,遞歸是相當實用的。例如下面的代碼:

void RecursiveSpider(String url) {

download URL……

parse URL……

while found each URL

call RecursiveSpider(found URL) ……

process the page just downloaded……

} 這段代碼查看單獨的一個Web頁的任務放在一個RecursiveSpider方法中。在此,調用RecursiveSipder方法來訪問URL。當它發現鏈接時,該方法調用它自己。遞歸方法在訪問很少的網頁時,可以使用。因爲當一個遞歸程序運行時要把每次遞歸壓入堆棧(堆棧是個程序結構,每次調用一個方法時,將返回地址存入其中)。如果遞歸程序要運行很多次,堆棧會變得非常大,它可能會耗盡整個堆棧內存而導致程序中止。遞歸還有個問題是多線程和遞歸是不兼容的,因爲在這一過程中每一個線程都是自己的堆棧。當一個方法調用它自身時,它們需要使用同一個堆棧。這就是說遞歸的Spider程序不能使用多線程。 非遞歸程序不調用自身,而是採用隊列的方法。隊列就是排隊,要得到程序的處理就必須在隊列中排隊等待。我們在構造造Spider時就採用該方式。使用非遞歸的方法時,給定Spider程序一個要訪問的頁面,它會將其加入到要訪問的站點的隊列中去。當Spider發現新的鏈接時,也會將它們加入到該隊列中。 Spider程序會順序處理隊列中的每一個網頁。實際在Spider程序中使用了四個隊列;在Spider程序的構造過程中,有兩種方法用於訪問隊列的管理。一種方法就是基於內存的隊列管理。

第二種方法就是基於SQL的隊列管理。基於SQL的隊列和基於內存的隊列都是有效的,在校園網上做實驗的結果表明,在系統運行過程中間,如果 Spider 的訪問任務隨着網頁數量增加,基於內存的Spider程序效率會下降。因而,選擇基於SQL的隊列管理方案來構造本Spider程序。

等待隊列: 在這個隊列中,URL等待被Spider程序處理。新發現的URL被加入到該處理隊列:當Spider開始處理URL時,它們被傳送到這一隊列。當一個 URL被處理後它被移送到錯誤隊列或完成隊列: 錯誤隊列: 如果下載某一頁面時出現錯誤,它的URL將被加入該隊列。該隊列的URL不會再移動到其他隊列。被列入該隊列的URL將不再會被Spider程序處理。

完成隊列: 如果頁面的下載沒有出現任何錯誤,則該頁面將會被加入完成隊列。加入該隊列的URL不會再移動到其他隊列。同一時刻一個URL只能在一個隊列中。其實通俗的講就是該URL處於什麼狀態,URL 狀態的變化過程就是程序處理URL的過程。下圖說明的一個URL狀態的變化過程。 Spider程序會遇到三種連接:內部連接外部連接其他連接,一個示例Spider類:

 

Java代碼 
import java.awt.*; 

import java.net.*; 
import java.io.*; 
import java.lang.*; 
import java.util.*; 


class node{ 
private Object data; 
private node next; 
private node prev; 
public node(Object o){ 
data = o; 
prev = next = null; 

public String toString(){ 
if(next!=null)return data.toString() + "\n"+ next.toString(); 
return data.toString(); 

public node getNext(){return next;} 
public void setNext(node n){next = n;} 
public node getPrev(){return prev;} 
public void setPrev(node n){prev = n;} 
public Object getData(){return data;} 


class linkedlist{ 
node head; 
node tail; 
public linkedlist(){ 
tail = head = null; 

public String toString(){ 
if(head==null)return "Empty list"; 
return head.toString(); 

public void insert(Object o){ 
if(tail==null){ 
head = tail = new node(o); 
}else{ 
node nn = new node(o); 
tail.setNext(nn); 
tail=nn; 


public boolean contains(Object o){ 
for(node n = head;n!=null;n=n.getNext()){ 
if(o.equals(n.getData()))return true; 

return false; 

public Object pop(){ 
if(head==null)return null; 
Object ret = head.getData(); 
head = head.getNext(); 
if(head==null)tail = null; 
return ret; 

public boolean isEmpty(){ 
return head==null; 




class list{ 
protected node tail; 
protected node ptr; 
private boolean stop; 
public list(){ 
ptr=tail=null; 
stop=false; 

public boolean isEmpty(){return tail==null;} 
public void reset(){ 
stop=false; 
ptr=tail; 

public String toString(){ 
if(tail==null)return "Empty list"; 
String ret=""; 
for(node n =tail.getNext();n!=tail;n=n.getNext())ret+=n.getData().toString()+"\n";
ret+=tail.getData().toString(); 
return ret; 

public Object get(){ 
if(ptr==null)return null; 
ptr = ptr.getNext(); 
if(ptr==tail.getNext()){ 
if(stop)return null; 
stop=true; 
return tail.getNext().getData(); 

return ptr.getData(); 

public void insert(Object o, boolean attail){ 
node nn = new node(o); 
if(tail==null){ 
nn.setNext(nn); 
   nn.setPrev(nn); 
   ptr=tail=nn; 
   return; 

if(attail){ 
tail.getNext().setPrev(nn); 
   nn.setNext(tail.getNext()); 
   tail.setNext(nn); 
   nn.setPrev(tail); 
   tail=nn; 
}else{ 
   nn.setNext(tail.getNext()); 
   nn.setPrev(tail); 
   tail.setNext(nn); 
   nn.getNext().setPrev(nn); 


public void insert(Object o){} 

   
class stack extends list{ 
public stack(){super();} 
public void insert(Object o){insert(o, false);} 

class queue extends list{ 
public queue(){super();} 
public void insert(Object o){insert(o, true);} 
public String peek(){ 
   if(tail==null)return ""; 
   return tail.getNext().getData().toString(); 

public Object pop(){ 
if(tail==null)return null; 
Object ret = tail.getNext().getData(); 
if(tail.getNext()==tail){ 
   tail=ptr=null; 
}else{ 
   if(tail.getNext()==ptr)ptr=ptr.getNext(); 
   tail.setNext(tail.getNext().getNext()); 

return ret; 


   
   
class hashtable{ 
   private Vector table; 
   private int size; 
   public hashtable(){ 
size = 991; 
table = new Vector(); 
for(int i=0;i<size;i++){ 
   table.add(new linkedlist()); 

   } 
   public void insert(Object o){ 
int index = o.hashCode(); 
index = index % size; 
if(index<0)index+=size; 
linkedlist ol = (linkedlist)table.get(index); 
ol.insert(o); 
   } 
   public boolean contains(Object o){ 
int index = o.hashCode(); 
index = index % size; 
if(index<0)index+=size; 
return ((linkedlist)(table.get(index))).contains(o); 
   } 
   public String toString(){ 
String ret =""; 
for(int i=0;i<size;i++){ 
   if(!((linkedlist)(table.get(i))).isEmpty()){ 
ret+="\n"; 
ret+=table.get(i).toString(); 
   } 

return ret; 
   } 

   
class spider implements Runnable{ 
public queue todo; 
public stack done; 
public stack errors; 
public stack omittions; 
private hashtable allsites; 
private String last=""; 
   int maxsites; 
   int visitedsites; 
   int TIMEOUT; 
   String base; 
   String []badEndings2 = {"ps", "gz"}; 
   String []badEndings3 = {"pdf", "txt","zip", "jpg", "mpg", "gif","mov", "tut", "req", "abs","swf", "tex", "dvi", "bin","exe", "rpm"};
   String []badEndings4 = {"jpeg", "mpeg"}; 
   
   public spider(String starturl, int max, String b){ 
TIMEOUT = 5000; 
base = b; 
allsites = new hashtable(); 
todo = new queue(); 
done = new stack(); 
errors = new stack(); 
omittions = new stack(); 
try{ 
   URL u = new URL(starturl); 
   todo.insert(u); 
}catch(Exception e){ 
   System.out.println(e); 
   errors.insert("bad starting url "+starturl+","+e.toString()); 

maxsites = max; 
visitedsites = 0; 
   } 
   
   /* 
   * how many millisec to wait for each page 
   */ 
   public void setTimer(int amount){ 
TIMEOUT = amount; 
   } 
   
   /* 
   * strips the '#' anchor off a url 
   */ 
   private URL stripRef(URL u){ 
try{ 
   return new URL(u.getProtocol(), u.getHost(), u.getPort(),u.getFile()); 
}catch(Exception e){return u;} 
   } 
   
   /* 
   * adds a url for future processing 
   */ 
   public void addSite(URL toadd){ 
if(null!=toadd.getRef())toadd = stripRef(toadd); 
if(!allsites.contains(toadd)){ 
   allsites.insert(toadd); 
   if(!toadd.toString().startsWith(base)){ 
omittions.insert("foreign URL: "+toadd.toString()); 
return; 
   } 
   if(!toadd.toString().startsWith("http") &&!toadd.toString().startsWith("HTTP")){
omittions.insert("ignoring URL: "+toadd.toString()); 
return; 
   } 
   
   String s = toadd.getFile(); 
   String last=""; 
   String []comp={}; 
   if(s.charAt(s.length()-3)=='.'){ 
last = s.substring(s.length()-2); 
comp = badEndings2; 
   }else if(s.charAt(s.length()-4)=='.'){ 
last = s.substring(s.length()-3); 
comp = badEndings3; 
   }else if(s.charAt(s.length()-5)=='.'){ 
last = s.substring(s.length()-4); 
comp = badEndings4; 
   } 
   for(int i=0;i<comp.length;i++){ 
if(last.equalsIgnoreCase(comp[i])){//loop through all bad extensions 
     omittions.insert("ignoring URL:"+toadd.toString()); 
     return; 

   } 
     
   todo.insert(toadd); 

   } 
   
   /* 
   * true if there are pending urls and the maximum hasn't beenreached 
   */ 
   public boolean hasMore(){ 
return !todo.isEmpty() && visitedsites<maxsites; 
   } 
   
   /* 
   * returns the next site, works like enumeration, will return newvalues each time
   */ 
   private URL getNextSite(){ 
last = todo.peek(); 
visitedsites++; 
return (URL)todo.pop(); 
   } 
   
   /* 
   * Just to see what we are doing now... 
   */ 
   public String getCurrent(){ 
return last; 
   } 
   
   /* 
   * process the next site 
   */ 
   public void doNextSite(){ 
URL current = getNextSite(); 
if(current==null)return; 
try{ 
   //System.err.println("Processing #"+visitedsites+":"+current); 
   parse(current); 
   done.insert(current); 

catch(Exception e){ 
   errors.insert("Bad site: "+current.toString()+","+e.toString()); 

   } 
   
   public void run(){ 
while(hasMore())doNextSite(); 
   } 
   
   /* 
   * to print out the internal data structures 
   */ 
   public String toString(){return getCompleted()+getErrors();} 
   private String getErrors(){ 
if(errors.isEmpty())return "No errors\n"; 
else return "Errors:\n"+errors.toString()+"\nEnd oferrors\n"; 
   } 
   private String getCompleted(){ 
return "Completed Sites:\n"+done.toString()+"\nEnd of completedsites\n"; 
   } 
   
   /* 
   * Parses a web page at (site) and adds all the urls it sees 
   */ 
   private void parse(URL site) throws Exception{ 
String source=getText(site); 
String title=getTitle(source); 
if(title.indexOf("404")!=-1 || 
   title.indexOf("Error")!=-1 || 
   title.indexOf("Not Found")!=-1){ 
   throw new Exception (("404, Not Found: "+site)); 

int loc, beg; 
boolean hasLT=false; 
boolean hasSp=false; 
boolean hasF=false; 
boolean hasR=false; 
boolean hasA=false; 
boolean hasM=false; 
boolean hasE=false; 
for(loc=0;loc<source.length();loc++){ 
   char c = source.charAt(loc); 
   if(!hasLT){ 
hasLT = (c=='<'); 
   } 
   
   //search for "<a " 
   else if(hasLT && !hasA && !hasF){ 
if(c=='a' || c=='A')hasA=true; 
else if(c=='f' || c=='F')hasF=true; 
else hasLT=false; 
   }else if(hasLT && hasA && !hasF &&!hasSp){ 
if(c==' ' || c=='\t' || c=='\n')hasSp=true; 
else hasLT = hasA = false; 
   } 
   
   //search for "<frame " 
   else if(hasLT && hasF && !hasA && !hasR){ 
if(c=='r' || c=='R')hasR=true; 
else hasLT = hasF = false; 
   }else if(hasLT && hasF && hasR && !hasA){ 
if(c=='a' || c=='A')hasA=true; 
else hasLT = hasF = hasR = false; 
   }else if(hasLT && hasF && hasR && hasA&& !hasM){ 
if(c=='m' || c=='M')hasM=true; 
else hasLT = hasF = hasR = hasA = false; 
   }else if(hasLT && hasF && hasR && hasA&& hasM && !hasE){ 
if(c=='e' || c=='E')hasE=true; 
else hasLT = hasF = hasR = hasA = hasM = false; 
   }else if(hasLT && hasF && hasR && hasA&& hasM && hasE && !hasSp){ 
if(c==' ' || c=='\t' || c=='\n')hasSp=true; 
else hasLT = hasF = hasR = hasA = hasM = hasE = false; 
   } 
     
   //found "<frame " 
   else if(hasLT && hasF && hasR && hasA&& hasM && hasE && hasSp){ 
hasLT = hasF = hasR = hasA = hasM = hasE = hasSp = false; 
beg = loc; 
loc = source.indexOf(">", loc); 
if(loc==-1){ 
     errors.insert("malformed frame at"+site.toString()); 
     loc = beg; 

else{ 
     try{ 
   parseFrame(site, source.substring(beg, loc)); 
     } 
     catch(Exception e){ 
   errors.insert("while parsing "+site.toString()+",error parsing frame: "+e.toString());
     } 

   } 
   
   //found "<a " 
   else if(hasLT && hasA && hasSp && !hasF){ 
hasLT = hasA = hasSp = false; 
beg = loc; 
loc = source.indexOf(">", loc); 
if(loc==-1){ 
     errors.insert("malformed linked at"+site.toString()); 
     loc = beg; 

else{ 
     try{ 
   parseLink(site, source.substring(beg, loc)); 
     } 
     catch(Exception e){ 
   errors.insert("while parsing "+site.toString()+",error parsing link: "+e.toString());
     } 

   } 

   } 
     
   /* 
   * parses a frame 
   */ 
   private void parseFrame(URL at_page, String s) throws Exception{ 
int beg=s.indexOf("src"); 
if(beg==-1)beg=s.indexOf("SRC"); 
if(beg==-1)return;//doesn't have a src, ignore 
beg = s.indexOf("=", beg); 
if(beg==-1)throw new Exception("while parsing"+at_page.toString()+", bad frame, missing \'=\' after src:"+s);
int start = beg; 
for(;beg<s.length();beg++){ 
   if(s.charAt(beg)=='\'')break; 
   if(s.charAt(beg)=='\"')break; 

int end=beg+1; 
for(;end<s.length();end++){ 
   if(s.charAt(beg)==s.charAt(end))break; 

beg++; 
if(beg>=end){//missing quotes... just take the first token after"src=" 
   for(beg=start+1;beg<s.length() && (s.charAt(beg)=='');beg++){} 
   for(end=beg+1;end<s.length() && (s.charAt(beg)!=' ')&& (s.charAt(beg)!='>');end++){}

   
if(beg>=end){ 
   errors.insert("while parsing "+at_page.toString()+",bad frame: "+s); 
   return; 

   
String linkto=s.substring(beg,end); 
if(linkto.startsWith("mailto:")||linkto.startsWith("Mailto:"))return;
if(linkto.startsWith("javascript:")||linkto.startsWith("Javascript:"))return;
if(linkto.startsWith("news:")||linkto.startsWith("Javascript:"))return;
try{ 
   addSite(new URL(at_page, linkto)); 
   return; 
}catch(Exception e1){} 
try{ 
   addSite(new URL(linkto)); 
   return; 
}catch(Exception e2){} 
try{ 
   URL cp = new URL(at_page.toString()+"/index.html"); 
   System.out.println("attemping to use "+cp); 
   addSite(new URL(cp, linkto)); 
   return; 
}catch(Exception e3){} 
errors.insert("while parsing "+at_page.toString()+", bad frame:"+linkto+", formed from: "+s);
   } 
   
   /* 
   * given a link at a URL, will parse it and add it to the list ofsites to do 
   */ 
   private void parseLink(URL at_page, String s) throws Exception{ 
//System.out.println("parsing link "+s); 
int beg=s.indexOf("href"); 
if(beg==-1)beg=s.indexOf("HREF"); 
if(beg==-1)return;//doesn't have a href, must be an anchor 
beg = s.indexOf("=", beg); 
if(beg==-1)throw new Exception("while parsing"+at_page.toString()+", bad link, missing \'=\' after href:"+s);
int start = beg; 
for(;beg<s.length();beg++){ 
   if(s.charAt(beg)=='\'')break; 
   if(s.charAt(beg)=='\"')break; 

int end=beg+1; 
for(;end<s.length();end++){ 
   if(s.charAt(beg)==s.charAt(end))break; 

beg++; 
if(beg>=end){//missing quotes... just take the first token after"href=" 
   for(beg=start+1;beg<s.length() && (s.charAt(beg)=='');beg++){} 
   for(end=beg+1;end<s.length() && (s.charAt(beg)!=' ')&& (s.charAt(beg)!='>');end++){}

   
if(beg>=end){ 
   errors.insert("while parsing"+at_page.toString()+", bad href: "+s); 
   return; 

   
String linkto=s.substring(beg,end); 
if(linkto.startsWith("mailto:")||linkto.startsWith("Mailto:"))return;
if(linkto.startsWith("javascript:")||linkto.startsWith("Javascript:"))return;
if(linkto.startsWith("news:")||linkto.startsWith("Javascript:"))return;
   
try{ 
   addSite(new URL(at_page, linkto)); 
   return; 
}catch(Exception e1){} 
try{ 
   addSite(new URL(linkto)); 
   return; 
}catch(Exception e2){} 
try{ 
   addSite(new URL(newURL(at_page.toString()+"/index.html"), linkto)); 
   return; 
}catch(Exception e3){} 
errors.insert("while parsing "+at_page.toString()+", bad link:"+linkto+", formed from: "+s);
   } 
   
   /* 
   * gets the title of a web page with content s 
   */ 
   private String getTitle(String s){ 
try{ 
   int beg=s.indexOf("<title>"); 
   if(beg==-1)beg=s.indexOf("<TITLE>"); 
   int end=s.indexOf("</title>"); 
   if(end==-1)end=s.indexOf("</TITLE>"); 
   return s.substring(beg,end); 

catch(Exception e){return "";} 
   } 
   
   /* 
   * gets the text of a web page, times out after 10s 
   */ 
   private String getText(URL site) throws Exception 
   { 
urlReader u = new urlReader(site); 
Thread t = new Thread(u); 
t.setDaemon(true); 
t.start(); 
t.join(TIMEOUT); 
String ret = u.poll(); 
if(ret==null){ 
throw new Exception("connection timed out"); 
}else if(ret.equals("Not html")){ 
throw new Exception("Not an HTML document"); 

return ret; 
   } 
   
   /* 
   * returns how many sites have been visited so far 
   */ 
   public int Visited(){return visitedsites;} 

   
class urlReader implements Runnable{ 
   URL site; 
   String s; 
   public urlReader(URL u){ 
site = u; 
s=null; 
   } 
   public void run(){ 
try{ 
   String ret=new String(); 
   URLConnection u = site.openConnection(); 
   String type = u.getContentType(); 
   if(type.indexOf("text")==-1 &&   
     type.indexOf("txt")==-1&&   
     type.indexOf("HTM")==-1&&   
     type.indexOf("htm")==-1){ 
//System.err.println("bad content type "+type+" at site"+site); 
System.out.println("bad content type "+type+" at site"+site); 
ret = "Not html"; 
return; 
   } 
   InputStream in = u.getInputStream(); 
   BufferedInputStream bufIn = new BufferedInputStream(in); 
   int data; 
   while(true){ 
data = bufIn.read(); 
// Check for EOF 
if (data == -1) break; 
else ret+= ( (char) data); 
   } 
   s = ret; 
}catch(Exception e){s=null;} 
   } 
   public String poll(){return s;} 

   
public class spidergui extends Frame{ 
   
private spider s; 
private Color txtColor; 
private Color errColor; 
private Color topColor; 
private Color numColor; 
private Color curColor; 
   
public spidergui(spider spi, String title){ 
super(title); 
curColor = new Color(40, 40, 200); 
txtColor = new Color(0, 0, 0); 
errColor = new Color(255, 0, 0); 
topColor = new Color(40, 40, 100); 
numColor = new Color(50, 150, 50); 
s=spi; 
setBounds(0, 0, 800, 600); 
show(); 
toFront(); 
repaint(); 

public void endShow(){ 
System.out.println(s); 
hide(); 
dispose(); 

public void paint(Graphics g){ 
super.paint(g); 
s.todo.reset(); 
s.done.reset(); 
s.errors.reset(); 
s.omittions.reset(); 
String txt; 
Object o; 
g.setColor(curColor); 
g.setFont(new Font("arial", Font.PLAIN, 18)); 
String cur = s.getCurrent(); 
if(cur.length()>80)g.drawString( 
   cur.substring(0, 40)+ 
   " . . . "+ 
   cur.substring(cur.length()-30, cur.length()), 
50, 50); 
else g.drawString(cur, 50, 50); 
   
g.setColor(numColor); 
g.setFont(new Font("arial", Font.BOLD, 24)); 
g.drawString(Integer.toString(s.Visited()), 350, 80); 
   
g.setFont(new Font("arial", Font.PLAIN, 14)); 
g.setColor(topColor); 
g.drawString("To Do:", 100, 80); 
g.drawString("Completed:", 500, 80); 
g.drawString("Ignored:", 500, 250); 
g.drawString("Errors:", 100, 420); 
   
g.setColor(txtColor); 
g.setFont(new Font("arial", Font.PLAIN, 12)); 
for(int i=0;i<23 && (o=s.todo.get())!=null;i++){ 
txt = Integer.toString(i+1) + ": "+o.toString(); 
if(txt.length()>65)g.drawString( 
   txt.substring(0, 38) + 
   " . . . " + 
   txt.substring(txt.length()-18, txt.length()), 
20, 100+13*i); 
else g.drawString(txt, 20, 100+13*i); 

for(int i=0;i<10 && (o=s.done.get())!=null;i++){ 
txt = Integer.toString(i+1) + ": "+o.toString(); 
if(txt.length()>60)g.drawString(txt.substring(0, 57)+"...", 400,100+13*i); 
else g.drawString(txt, 400, 100+13*i); 

for(int i=0;i<10 && (o=s.omittions.get())!=null;i++){ 
txt = Integer.toString(i+1) + ": "+o.toString(); 
if(txt.length()>60)g.drawString(txt.substring(0, 57)+"...", 400,270+13*i); 
else g.drawString(txt, 400, 270+13*i); 

g.setColor(errColor); 
for(int i=0;i<10 && (o=s.errors.get())!=null;i++){ 
txt = Integer.toString(i+1) + ": "+o.toString(); 
g.drawString(txt, 20, 440+13*i); 

   

public void run(){ 
repaint(); 
while(s.hasMore()){ 
repaint(); 
s.doNextSite(); 

repaint(); 

   
public static void main(String []args){ 
int max = 5; 
String site=""; 
String base=""; 
int time=0; 
for(int i=0;i<args.length;i++){ 
   if(args[i].startsWith("-max=")){ 
max=Integer.parseInt(args[i].substring(5,args[i].length())); 
   } 
   else if(args[i].startsWith("-time=")){ 
time=Integer.parseInt(args[i].substring(6,args[i].length())); 
   } 
   else if(args[i].startsWith("-init=")){ 
site=args[i].substring(6,args[i].length()); 
   } 
   else if(args[i].startsWith("-base=")){ 
base=args[i].substring(6,args[i].length()); 
   } 
   elseif(args[i].startsWith("-help")||args[i].startsWith("-?")){ 
System.out.println("additional command line switches:"); 
System.out.println("-max=N     : to limit to N sites,default 5"); 
System.out.println("-init=URL   : to set the initial site,REQUIRED"); 
System.out.println("-base=URL   : only follow url's that startwith this"); 
System.out.println("        default \"\" (matches all URLs)"); 
System.out.println("-time=N   : how many millisec to wait foreach page"); 
System.out.println("        default 5000 (5 seconds)"); 
System.exit(0); 
   } 
   else System.err.println("unrecognized switch:"+args[i]+", continuing"); 

if(site==""){ 
   System.err.println("No initial site parameter!"); 
   System.err.println("Use -init=<site> switch to set, or-help for more info."); 
   System.exit(1); 

   
spider spi=new spider(site, max, base); 
   
if(time>0)spi.setTimer(time); 
   
spidergui s = new spidergui(spi, "Spider: "+site); 
s.run(); 
System.out.println(spi); 

}

 

 

 

另一個實現:

這是一個web搜索的基本程序,從命令行輸入搜索條件(起始的URL、處理url的最大數、要搜索的字符串), 
它就會逐個對Internet上的URL進行實時搜索,查找並輸出匹配搜索條件的頁面。 這個程序的原型來自《java編程藝術》, 
爲了更好的分析,站長去掉了其中的GUI部分,並稍作修改以適用jdk1.5。以這個程序爲基礎,可以寫出在互聯網上搜索 
諸如圖像、郵件、網頁下載之類的“爬蟲”。 
先請看程序運行的過程:


D:\java>javac SearchCrawler.java(編譯)

D:\java>java   SearchCrawler http://127.0.0.1:8080/zz3zcwbwebhome/index.jsp20 java

Start searching... 
result: 
searchString=java 
http://127.0.0.1:8080/zz3zcwbwebhome/index.jsp
http://127.0.0.1:8080/zz3zcwbwebhome/reply.jsp
http://127.0.0.1:8080/zz3zcwbwebhome/learn.jsp
http://127.0.0.1:8080/zz3zcwbwebhome/download.jsp
http://127.0.0.1:8080/zz3zcwbwebhome/article.jsp
http://127.0.0.1:8080/zz3zcwbwebhome/myexample/jlGUIOverview.htm
http://127.0.0.1:8080/zz3zcwbwebhome/myexample/Proxooldoc/index.html
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=301
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=297
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=291
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=286
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=285
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=284
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=276
http://127.0.0.1:8080/zz3zcwbwebhome/view.jsp?id=272  

又如: 
D:\java>java    SearchCrawler http://www.sina.com20 java
Start searching... 
result: 
searchString=java 
http://sina.com 
http://redirect.sina.com/WWW/sinaCN/www.sina.com.cnclass=a2
http://redirect.sina.com/WWW/sinaCN/www.sina.com.cnclass=a8
http://redirect.sina.com/WWW/sinaHK/www.sina.com.hkclass=a2
http://redirect.sina.com/WWW/sinaTW/www.sina.com.twclass=a8
http://redirect.sina.com/WWW/sinaUS/home.sina.comclass=a8
http://redirect.sina.com/WWW/smsCN/sms.sina.com.cn/class=a2
http://redirect.sina.com/WWW/smsCN/sms.sina.com.cn/class=a3
http://redirect.sina.com/WWW/sinaNet/www.sina.net/class=a3


D:\java> 
下面是這個程序的源碼 
Java代碼 
import java.util.*; 
import java.net.*; 
import java.io.*; 
import java.util.regex.*; 

// 搜索Web爬行者 
public class SearchCrawler implements Runnable{ 
   
/* disallowListCache緩存robot不允許搜索的URL。 Robot協議在Web站點的根目錄下設置一個robots.txt文件, 
*規定站點上的哪些頁面是限制搜索的。 搜索程序應該在搜索過程中跳過這些區域,下面是robots.txt的一個例子: 
# robots.txt for http://somehost.com/ 
   User-agent: * 
   Disallow: /cgi-bin/ 
   Disallow: /registration # /Disallow robots on registration page 
   Disallow: /login 
*/ 


private HashMap< String,ArrayList< String>> disallowListCache = newHashMap< String,ArrayList< String>>();  
ArrayList< String> errorList= new ArrayList< String>();//錯誤信息   
ArrayList< String> result=new ArrayList< String>(); //搜索到的結果   
String startUrl;//開始搜索的起點 
int maxUrl;//最大處理的url數 
String searchString;//要搜索的字符串(英文) 
boolean caseSensitive=false;//是否區分大小寫 
boolean limitHost=false;//是否在限制的主機內搜索 
    
public SearchCrawler(String startUrl,int maxUrl,String searchString){ 
   this.startUrl=startUrl; 
   this.maxUrl=maxUrl; 
   this.searchString=searchString; 


   public ArrayList< String> getResult(){ 
       return result; 
   } 

public void run(){//啓動搜索線程 
        
       crawl(startUrl,maxUrl,searchString,limitHost,caseSensitive); 

     

    //檢測URL格式 
private URL verifyUrl(String url) { 
    // 只處理HTTP URLs. 
    if (!url.toLowerCase().startsWith("http://")) 
      return null; 

    URL verifiedUrl = null; 
    try { 
      verifiedUrl = new URL(url); 
    } catch (Exception e) { 
      return null; 
    } 

    return verifiedUrl; 


// 檢測robot是否允許訪問給出的URL. 
private boolean isRobotAllowed(URL urlToCheck) {   
    String host = urlToCheck.getHost().toLowerCase();//獲取給出RUL的主機   
    //System.out.println("主機="+host);

    // 獲取主機不允許搜索的URL緩存   
    ArrayList< String> disallowList=disallowListCache.get(host);   

    // 如果還沒有緩存,下載並緩存。   
    if (disallowList == null) {   
      disallowList = new ArrayList<String>();   
      try {   
        URL robotsFileUrl =newURL("http://" + host + "/robots.txt");   
        BufferedReader reader =newBufferedReader(new InputStreamReader(robotsFileUrl.openStream()));  

        // 讀robot文件,創建不允許訪問的路徑列表。   
        String line;   
        while ((line = reader.readLine()) !=null) {   
          if(line.indexOf("Disallow:") == 0) {//是否包含"Disallow:"  
            StringdisallowPath =line.substring("Disallow:".length());//獲取不允許訪問路徑  

            // 檢查是否有註釋。   
            intcommentIndex = disallowPath.indexOf("#");   
            if(commentIndex != - 1) {   
             disallowPath =disallowPath.substring(0, commentIndex);//去掉註釋   
           }   
              
            disallowPath= disallowPath.trim();   
           disallowList.add(disallowPath);   
           }   
         }   

        // 緩存此主機不允許訪問的路徑。   
        disallowListCache.put(host,disallowList);   
      } catch (Exception e) {   
             return true; //web站點根目錄下沒有robots.txt文件,返回真 
      }   
    }   

       
     String file = urlToCheck.getFile();   
     //System.out.println("文件getFile()="+file);
     for (int i = 0; i < disallowList.size(); i++){   
       String disallow =disallowList.get(i);   
       if (file.startsWith(disallow)){   
         return false;   
       }   
     }   
   
     return true;   
   }   
   
   
   
    
   private String downloadPage(URL pageUrl) { 
      try { 
         // Open connection to URL forreading. 
         BufferedReader reader = 
           newBufferedReader(new InputStreamReader(pageUrl.openStream())); 
   
         // Read page into buffer. 
         String line; 
         StringBuffer pageBuffer = newStringBuffer(); 
         while ((line =reader.readLine()) != null) { 
          pageBuffer.append(line); 
         } 
           
         return pageBuffer.toString(); 
      } catch (Exception e) { 
      } 
   
      return null; 
   } 
   
   // 從URL中去掉"www"
   private String removeWwwFromUrl(String url) { 
     int index = url.indexOf("://www."); 
     if (index != -1) { 
       return url.substring(0, index + 3) + 
         url.substring(index + 7); 
     } 
   
     return (url); 
   } 
   
   // 解析頁面並找出鏈接 
   private ArrayList< String> retrieveLinks(URL pageUrl, StringpageContents, HashSet crawledList,
     boolean limitHost) 
   { 
     // 用正則表達式編譯鏈接的匹配模式。 
Pattern p=    Pattern.compile("<a\\s+href\\s*=\\s*\"?(.*?)[\"|>]",Pattern.CASE_INSENSITIVE);
     Matcher m = p.matcher(pageContents); 
   
       
     ArrayList< String> linkList = new ArrayList<String>(); 
     while (m.find()) { 
       String link = m.group(1).trim(); 
         
       if (link.length() < 1) { 
         continue; 
       } 
   
       // 跳過鏈到本頁面內鏈接。 
       if (link.charAt(0) == '#') { 
         continue; 
       } 
   
         
       if (link.indexOf("mailto:") !=-1) { 
         continue; 
       } 
        
       if(link.toLowerCase().indexOf("javascript") != -1) { 
         continue; 
       } 
   
       if (link.indexOf("://") == -1){ 
         if (link.charAt(0) == '/') {//處理絕對地    
           link ="http://" + pageUrl.getHost()+":"+pageUrl.getPort()+ link; 
         } else{           
           String file =pageUrl.getFile(); 
           if(file.indexOf('/') == -1) {//處理相對地址 
             link ="http://" + pageUrl.getHost()+":"+pageUrl.getPort() +"/" + link;
           } else { 
             Stringpath =file.substring(0, file.lastIndexOf('/') + 1); 
             link ="http://" + pageUrl.getHost() +":"+pageUrl.getPort()+ path+ link;
           } 
         } 
       } 
   
       int index = link.indexOf('#'); 
       if (index != -1) { 
         link = link.substring(0,index); 
       } 
   
       link = removeWwwFromUrl(link); 
   
       URL verifiedLink = verifyUrl(link); 
       if (verifiedLink == null) { 
         continue; 
       } 
   
       /* 如果限定主機,排除那些不合條件的URL*/ 
       if (limitHost && 
          !pageUrl.getHost().toLowerCase().equals( 
            verifiedLink.getHost().toLowerCase())) 
       { 
         continue; 
       } 
   
       // 跳過那些已經處理的鏈接. 
       if (crawledList.contains(link)) { 
         continue; 
       } 
   
        linkList.add(link); 
     } 
   
    return (linkList); 
   } 
   
// 搜索下載Web頁面的內容,判斷在該頁面內有沒有指定的搜索字符串 
   
   private boolean searchStringMatches(String pageContents, StringsearchString, boolean caseSensitive){
        String searchContents =pageContents;   
        if (!caseSensitive) {//如果不區分大小寫 
           searchContents =pageContents.toLowerCase(); 
        } 
   
       
     Pattern p = Pattern.compile("[\\s]+"); 
     String[] terms = p.split(searchString); 
     for (int i = 0; i < terms.length; i++) { 
       if (caseSensitive) { 
         if(searchContents.indexOf(terms[i]) == -1) { 
           return false; 
         } 
       } else { 
         if(searchContents.indexOf(terms[i].toLowerCase()) == -1) { 
           return false; 
         } 
       }     } 
   
     return true; 
   } 
   
     
   //執行實際的搜索操作 
   public ArrayList< String> crawl(String startUrl, intmaxUrls, String searchString,boolean limithost,boolean caseSensitive )
   {   
       
    System.out.println("searchString="+searchString); 
     HashSet< String> crawledList = new HashSet<String>(); 
     LinkedHashSet< String> toCrawlList = newLinkedHashSet< String>(); 
   
      if (maxUrls < 1) { 
         errorList.add("InvalidMax URLs value."); 
        System.out.println("Invalid Max URLs value."); 
       } 
     
       
     if (searchString.length() < 1) { 
       errorList.add("Missing SearchString."); 
       System.out.println("Missing searchString"); 
     } 
   
       
     if (errorList.size() > 0) { 
       System.out.println("err!!!"); 
       return errorList; 
       } 
   
       
     // 從開始URL中移出www 
     startUrl = removeWwwFromUrl(startUrl); 
   
       
     toCrawlList.add(startUrl); 
     while (toCrawlList.size() > 0) { 
         
       if (maxUrls != -1) { 
         if (crawledList.size() ==maxUrls) { 
           break; 
         } 
       } 
   
       // Get URL at bottom of the list. 
       String url =toCrawlList.iterator().next(); 
   
       // Remove URL from the to crawl list. 
       toCrawlList.remove(url); 
   
       // Convert string url to URL object. 
       URL verifiedUrl = verifyUrl(url); 
   
       // Skip URL if robots are not allowed toaccess it. 
       if (!isRobotAllowed(verifiedUrl)) { 
         continue; 
       } 
   
       
       // 增加已處理的URL到crawledList 
       crawledList.add(url); 
       String pageContents =downloadPage(verifiedUrl); 
   
         
       if (pageContents != null &&pageContents.length() > 0){ 
         // 從頁面中獲取有效的鏈接 
         ArrayList< String> links=retrieveLinks(verifiedUrl, pageContents, crawledList,limitHost);
        
         toCrawlList.addAll(links); 
   
         if(searchStringMatches(pageContents, searchString,caseSensitive)) 
         { 
           result.add(url); 
          System.out.println(url); 
         } 
      } 
   
       
     } 
    return result; 
   } 
   
   // 主函數 
   public static void main(String[] args) { 
      if(args.length!=3){ 
         System.out.println("Usage:javaSearchCrawler startUrl maxUrl searchString");
         return; 
      } 
     int max=Integer.parseInt(args[1]); 
     SearchCrawler crawler = newSearchCrawler(args[0],max,args[2]); 
     Thread search=new Thread(crawler); 
     System.out.println("Start searching..."); 
     System.out.println("result:"); 
     search.start(); 
      
   } 
}

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