Java爬蟲之抓取一個網站上的全部鏈接


前言:寫這篇文章之前,主要是我看了幾篇類似的爬蟲寫法,有的是用的隊列來寫,感覺不是很直觀,還有的只有一個請求然后進行頁面解析,根本就沒有自動爬起來這也叫爬蟲?因此我結合自己的思路寫了一下簡單的爬蟲,測試用例就是自動抓取我的博客網站(http://www.zifangsky.cn)的所有鏈接。

一 算法簡介

       程序在思路上采用了廣度優先算法,對未遍歷過的鏈接逐次發起GET請求,然后對返回來的頁面用正則表達式進行解析,取出其中未被發現的新鏈接,加入集合中,待下一次循環時遍歷。

       具體實現上使用了Map<String, Boolean>,鍵值對分別是鏈接和是否被遍歷標志。程序中使用了兩個Map集合,分別是:oldMap和newMap,初始的鏈接在oldMap中,然后對oldMap里面的標志為false的鏈接發起請求,解析頁面,用正則取出<a>標簽下的鏈接,如果這個鏈接未在oldMap和newMap中,則說明這是一條新的鏈接,同時要是這條鏈接是我們需要獲取的目標網站的鏈接的話,我們就將這條鏈接放入newMap中,一直解析下去,等這個頁面解析完成,把oldMap中當前頁面的那條鏈接的值設為true,表示已經遍歷過了。最后是當整個oldMap未遍歷過的鏈接都遍歷結束后,如果發現newMap不為空,則說明這一次循環有新的鏈接產生,因此將這些新的鏈接加入oldMap中,繼續遞歸遍歷,反之則說明這次循環沒有產生新的鏈接,繼續循環下去已經不能產生新鏈接了,因為任務結束,返回鏈接集合oldMap

二 程序實現

上面相關思路已經說得很清楚了,並且代碼中關鍵地方有注釋,因此這里就不多說了,代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package  action;
 
import  java.io.BufferedReader;
import  java.io.IOException;
import  java.io.InputStream;
import  java.io.InputStreamReader;
import  java.net.HttpURLConnection;
import  java.net.MalformedURLException;
import  java.net.URL;
import  java.util.LinkedHashMap;
import  java.util.Map;
import  java.util.regex.Matcher;
import  java.util.regex.Pattern;
 
public  class  WebCrawlerDemo {
 
     public  static  void  main(String[] args) {
         WebCrawlerDemo webCrawlerDemo =  new  WebCrawlerDemo();
         webCrawlerDemo.myPrint( "http://www.zifangsky.cn" );
     }
 
     public  void  myPrint(String baseUrl) {
         Map<String, Boolean> oldMap =  new  LinkedHashMap<String, Boolean>();  // 存儲鏈接-是否被遍歷
                                                                             // 鍵值對
         String oldLinkHost =  "" ;   //host
 
         Pattern p = Pattern.compile( "(https?://)?[^/\\s]*" ); //比如:http://www.zifangsky.cn
         Matcher m = p.matcher(baseUrl);
         if  (m.find()) {
             oldLinkHost = m.group();
         }
 
         oldMap.put(baseUrl,  false );
         oldMap = crawlLinks(oldLinkHost, oldMap);
         for  (Map.Entry<String, Boolean> mapping : oldMap.entrySet()) {
             System.out.println( "鏈接:"  + mapping.getKey());
 
         }
 
     }
 
     /**
      * 抓取一個網站所有可以抓取的網頁鏈接,在思路上使用了廣度優先算法
      * 對未遍歷過的新鏈接不斷發起GET請求,一直到遍歷完整個集合都沒能發現新的鏈接
      * 則表示不能發現新的鏈接了,任務結束
     
      * @param oldLinkHost  域名,如:http://www.zifangsky.cn
      * @param oldMap  待遍歷的鏈接集合
     
      * @return 返回所有抓取到的鏈接集合
      * */
     private  Map<String, Boolean> crawlLinks(String oldLinkHost,
             Map<String, Boolean> oldMap) {
         Map<String, Boolean> newMap =  new  LinkedHashMap<String, Boolean>();
         String oldLink =  "" ;
 
         for  (Map.Entry<String, Boolean> mapping : oldMap.entrySet()) {
             System.out.println( "link:"  + mapping.getKey() +  "--------check:"
                     + mapping.getValue());
             // 如果沒有被遍歷過
             if  (!mapping.getValue()) {
                 oldLink = mapping.getKey();
                 // 發起GET請求
                 try  {
                     URL url =  new  URL(oldLink);
                     HttpURLConnection connection = (HttpURLConnection) url
                             .openConnection();
                     connection.setRequestMethod( "GET" );
                     connection.setConnectTimeout( 2000 );
                     connection.setReadTimeout( 2000 );
 
                     if  (connection.getResponseCode() ==  200 ) {
                         InputStream inputStream = connection.getInputStream();
                         BufferedReader reader =  new  BufferedReader(
                                 new  InputStreamReader(inputStream,  "UTF-8" ));
                         String line =  "" ;
                         Pattern pattern = Pattern
                                 .compile( "<a.*?href=[\"']?((https?://)?/?[^\"']+)[\"']?.*?>(.+)</a>" );
                         Matcher matcher =  null ;
                         while  ((line = reader.readLine()) !=  null ) {
                             matcher = pattern.matcher(line);
                             if  (matcher.find()) {
                                 String newLink = matcher.group( 1 ).trim();  // 鏈接
                                 // String title = matcher.group(3).trim(); //標題
                                 // 判斷獲取到的鏈接是否以http開頭
                                 if  (!newLink.startsWith( "http" )) {
                                     if  (newLink.startsWith( "/" ))
                                         newLink = oldLinkHost + newLink;
                                     else
                                         newLink = oldLinkHost +  "/"  + newLink;
                                 }
                                 //去除鏈接末尾的 /
                                 if (newLink.endsWith( "/" ))
                                     newLink = newLink.substring( 0 , newLink.length() -  1 );
                                 //去重,並且丟棄其他網站的鏈接
                                 if  (!oldMap.containsKey(newLink)
                                         && !newMap.containsKey(newLink)
                                         && newLink.startsWith(oldLinkHost)) {
                                     // System.out.println("temp2: " + newLink);
                                     newMap.put(newLink,  false );
                                 }
                             }
                         }
                     }
                 catch  (MalformedURLException e) {
                     e.printStackTrace();
                 catch  (IOException e) {
                     e.printStackTrace();
                 }
 
                 try  {
                     Thread.sleep( 1000 );
                 catch  (InterruptedException e) {
                     e.printStackTrace();
                 }
                 oldMap.replace(oldLink,  false true );
             }
         }
         //有新鏈接,繼續遍歷
         if  (!newMap.isEmpty()) {
             oldMap.putAll(newMap);
             oldMap.putAll(crawlLinks(oldLinkHost, oldMap));   //由於Map的特性,不會導致出現重復的鍵值對
         }
         return  oldMap;
     }
 
}

三 最后的測試效果

 

wKiom1aCJ6LjWcotAAFyTOkuY9M691.png

PS:其實用遞歸這種方式不是太好,因為要是網站頁面比較多的話,程序運行時間長了對內存的消耗會非常大,不過因為我的博客網站頁面不是很多,因此效果還可以。

 

==============僅供參考============


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM