首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 爬蟲實戰:廣度優先抓取全站連結完整實作

     編輯:Java 相關知識

演算法設計:廣度優先搜尋 (BFS)

採用兩個 Map<String, Boolean> 追蹤狀態:

  • oldMap:已發現的所有連結,Boolean 表示是否已遍歷。
  • newMap:本輪新發現的連結。

流程:

  1. 從種子 URL 開始,放入 oldMap(值為 false)。
  2. 遍歷 oldMap 中 false 的連結,發送 HTTP GET。
  3. 解析 HTML,用正則提取 <a href="..."> 連結。
  4. 正規化連結(相對路徑轉絕對、去除尾端 /)。
  5. 若連結屬於目標網域且未在 oldMap/newMap 出現,加入 newMap。
  6. 標記該連結為 true(已遍歷)。
  7. 若 newMap 不為空,合併進 oldMap 並遞迴下一輪;否則結束。

核心程式碼實作

package action;

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

public class WebCrawlerDemo {

    public static void main(String[] args) {
        new WebCrawlerDemo().myPrint("https://www.example.com");
    }

    public void myPrint(String baseUrl) {
        Map<String, Boolean> oldMap = new LinkedHashMap<>();
        String host = extractHost(baseUrl);
        oldMap.put(baseUrl, false);
        oldMap = crawlLinks(host, oldMap);
        oldMap.forEach((url, visited) -> System.out.println("連結:" + url));
    }

    private String extractHost(String url) {
        Pattern p = Pattern.compile("(https?://)?[^/\\s]*");
        Matcher m = p.matcher(url);
        return m.find() ? m.group() : "";
    }

    private Map<String, Boolean> crawlLinks(String host, Map<String, Boolean> oldMap) {
        Map<String, Boolean> newMap = new LinkedHashMap<>();
        Pattern linkPattern = Pattern.compile("<a.*?href=[\"']?((https?://)?/?[^\"']+)[\"']?.*?>(.+)</a>");

        for (Map.Entry<String, Boolean> entry : oldMap.entrySet()) {
            if (entry.getValue()) continue; // 已遍歷

            String currentUrl = entry.getKey();
            try {
                URL url = new URL(currentUrl);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(2000);
                conn.setReadTimeout(2000);

                if (conn.getResponseCode() == 200) {
                    try (BufferedReader reader = new BufferedReader(
                            new InputStreamReader(conn.getInputStream(), "UTF-8"))) {
                        String line;
                        while ((line = reader.readLine()) != null) {
                            Matcher matcher = linkPattern.matcher(line);
                            while (matcher.find()) {
                                String newLink = matcher.group(1).trim();
                                // 正規化
                                if (!newLink.startsWith("http")) {
                                    newLink = newLink.startsWith("/") ? host + newLink : host + "/" + newLink;
                                }
                                if (newLink.endsWith("/")) newLink = newLink.substring(0, newLink.length() - 1);

                                // 去重且同網域
                                if (!oldMap.containsKey(newLink) && !newMap.containsKey(newLink)
                                        && newLink.startsWith(host)) {
                                    newMap.put(newLink, false);
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
            oldMap.replace(currentUrl, false, true); // 標記已遍歷
        }

        if (!newMap.isEmpty()) {
            oldMap.putAll(newMap);
            return crawlLinks(host, oldMap); // 遞迴下一輪
        }
        return oldMap;
    }
}

執行結果範例

連結:https://www.example.com
連結:https://www.example.com/about
連結:https://www.example.com/contact
連結:https://www.example.com/products
...(依網站規模輸出所有可達連結)

效能與改進建議

問題改進方向
遞迴深度過大導致 StackOverflow改用迴圈 + Queue 實作 BFS,移除遞迴。
單執行緒速度慢引入執行緒池(ExecutorService)並發抓取,需加鎖保護 Map。
記憶體隨連結數增長若連結超過 10 萬,建議改用資料庫或 Bloom Filter 去重。
正則解析 HTML 不穩定改用 Jsoup 解析 DOM,更健壯。
無 robots.txt / 禮貌性控制加入 robots.txt 解析與 Crawl-Delay 遵守。

總結

本文示範以原生 HttpURLConnection + 正則 + BFS 實作簡易爬蟲,適合理解原理與小規模抓取。生產環境建議採用 Jsoup、HttpClient、多執行緒、分散式佇列(如 Redis + Kafka)等成熟元件,並嚴格遵守目標網站爬蟲協議。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved