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

Java 網頁爬蟲與代理 IP 技術實戰:基於 Jsoup 的高併發資料採集

     編輯:Java 綜合教學

Java 網頁爬蟲與代理 IP 技術實戰:基於 Jsoup 的高併發資料採集

本文介紹使用 Java 實現高併發網頁資料採集的核心技術棧:Jsoup 解析、代理 IP 池管理、反爬蟲應對策略、分散式爬蟲架構設計,幫助開發者構建穩健、高效、合規的資料採集系統。

一、技術選型對比

框架/庫優點缺點適用場景
JsoupAPI 簡潔、支援 CSS 選擇器、容錯性強、無瀏覽器開銷無法執行 JavaScript、無法處理動態渲染頁面靜態頁面、API 介面、結構化資料提取
HttpClient / OkHttp底層 HTTP 客戶端、連線池、支援 HTTP/2、協程需自行處理 HTML 解析高併發請求、大檔案下載、微服務通訊
Selenium / Playwright真實瀏覽器環境、支援 JS 渲染、模擬用戶行為資源消耗大、啟動慢、難以大規模併發動態渲染、複雜驗證碼、E2E 測試
WebMagic國產爬蟲框架、內建調度/去重/持久化、註解式配置社區活躍度一般、擴充性受限快速構建中小型爬蟲專案

推薦組合:OkHttp (連線池/HTTP/2) + Jsoup (解析) + Redis (去重/隊列) + 代理池服務

二、核心組件實現

1. 代理 IP 實體與驗證

@Data
@Builder
public class ProxyIp {
    private String host;
    private int port;
    private String username;  // 私密代理認證
    private String password;
    private ProxyType type;   // HTTP/HTTPS/SOCKS5
    private long responseTime; // 驗證延遲 ms
    private int successCount;
    private int failCount;
    private LocalDateTime lastVerified;
    private boolean available;
    
    public String toHttpProxyString() {
        return (username != null ? username + ":" + password + "@" : "") + host + ":" + port;
    }
}

public enum ProxyType { HTTP, HTTPS, SOCKS5 }

2. 代理池管理服務 (定時抓取、驗證、評分、淘汰)

@Service
@Slf4j
public class ProxyPoolService {
    
    private final RedisTemplate redisTemplate;
    private final OkHttpClient httpClient;
    private final Set sourceApis = Set.of(
        "https://api.xicidaili.com/free2016.txt",
        "https://api.proxyfly.io/proxies?format=txt",
        "自建代理服務 API"
    );
    
    // 定時任務:每 5 分鐘刷新
    @Scheduled(fixedDelay = 300_000)
    public void refreshProxyPool() {
        List candidates = new ArrayList<>();
        for (String api : sourceApis) {
            candidates.addAll(fetchFromApi(api));
        }
        // 並行驗證
        List verified = candidates.parallelStream()
            .map(this::verifyProxy)
            .filter(p -> p.isAvailable())
            .collect(Collectors.toList());
        
        // 存入 Redis 有序集合 (分數 = 成功率 * 1000 - responseTime)
        for (ProxyIp proxy : verified) {
            double score = proxy.getSuccessCount() * 1000.0 / (proxy.getSuccessCount() + proxy.getFailCount() + 1) - proxy.getResponseTime();
            redisTemplate.opsForZSet().add("proxy:pool", proxy.toHttpProxyString(), score);
        }
        // 移除分數過低/過期代理
        redisTemplate.opsForZSet().removeRangeByScore("proxy:pool", 0, 100);
        log.info("代理池刷新完成,可用: {}", verified.size());
    }
    
    private ProxyIp verifyProxy(ProxyIp proxy) {
        String testUrl = "https://httpbin.org/ip"; // 返回呼叫端 IP
        Request request = new Request.Builder()
            .url(testUrl)
            .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxy.getHost(), proxy.getPort())))
            .timeout(5, TimeUnit.SECONDS)
            .build();
        
        long start = System.currentTimeMillis();
        try (Response response = httpClient.newCall(request).execute()) {
            if (response.isSuccessful()) {
                proxy.setResponseTime(System.currentTimeMillis() - start);
                proxy.setSuccessCount(proxy.getSuccessCount() + 1);
                proxy.setAvailable(true);
                proxy.setLastVerified(LocalDateTime.now());
                // 可選:解析響應驗證 IP 是否為代理 IP
            } else {
                proxy.setFailCount(proxy.getFailCount() + 1);
                proxy.setAvailable(false);
            }
        } catch (Exception e) {
            proxy.setFailCount(proxy.getFailCount() + 1);
            proxy.setAvailable(false);
        }
        return proxy;
    }
    
    // 獲取高分代理 (支援加權隨機)
    public ProxyIp acquireBestProxy() {
        Set> top = redisTemplate.opsForZSet()
            .reverseRangeWithScores("proxy:pool", 0, 49); // 取前 50
        if (top == null || top.isEmpty()) return null;
        // 加權隨機選取
        List proxies = top.stream().map(ZSetOperations.TypedTuple::getValue).collect(Collectors.toList());
        return ProxyIp.parse(proxies.get(ThreadLocalRandom.current().nextInt(proxies.size())));
    }
}

3. 高併發爬蟲執行器

@Service
@Slf4j
public class CrawlerExecutor {
    
    private final OkHttpClient client = new OkHttpClient.Builder()
        .connectionPool(new ConnectionPool(200, 5, TimeUnit.MINUTES))
        .retryOnConnectionFailure(true)
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build();
    
    private final ProxyPoolService proxyPool;
    private final RedisTemplate redisTemplate;
    
    // 併發控制:信號量限制同時進行的請求數
    private final Semaphore semaphore = new Semaphore(100);
    
    public CompletableFuture fetch(String url, CrawlContext ctx) {
        return CompletableFuture.supplyAsync(() -> {
            semaphore.acquireUninterruptibly();
            try {
                return doFetchWithRetry(url, ctx, 3);
            } finally {
                semaphore.release();
            }
        }, crawlerExecutor); // 自定義執行緒池
    }
    
    private Document doFetchWithRetry(String url, CrawlContext ctx, int retry) {
        ProxyIp proxy = proxyPool.acquireBestProxy();
        Request.Builder builder = new Request.Builder()
            .url(url)
            .header("User-Agent", rotateUserAgent())
            .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
            .header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
        
        if (proxy != null) {
            builder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxy.getHost(), proxy.getPort())));
            if (proxy.getUsername() != null) {
                String credential = Credentials.basic(proxy.getUsername(), proxy.getPassword());
                builder.header("Proxy-Authorization", credential);
            }
        }
        
        Request request = builder.build();
        long start = System.currentTimeMillis();
        try (Response response = client.newCall(request).execute()) {
            long cost = System.currentTimeMillis() - start;
            if (response.isSuccessful()) {
                String html = response.body().string();
                // 記錄成功指標
                recordMetric(url, cost, true, proxy);
                return Jsoup.parse(html, url);
            } else {
                // 非 2xx:記錄失敗、標記代理降權
                recordMetric(url, cost, false, proxy);
                if (proxy != null) proxyPool.reportFailure(proxy);
                if (retry > 0 && isRetryable(response.code())) {
                    return doFetchWithRetry(url, ctx, retry - 1);
                }
                throw new CrawlException("HTTP " + response.code() + ": " + url);
            }
        } catch (IOException e) {
            recordMetric(url, System.currentTimeMillis() - start, false, proxy);
            if (proxy != null) proxyPool.reportFailure(proxy);
            if (retry > 0) {
                sleepWithJitter(1000); // 指數退避 + 抖動
                return doFetchWithRetry(url, ctx, retry - 1);
            }
            throw new CrawlException("Fetch failed: " + url, e);
        }
    }
    
    private void recordMetric(String url, long cost, boolean success, ProxyIp proxy) {
        // 寫入 Redis/InfluxDB 監控
    }
}

三、反爬蟲應對策略

  1. 請求指紋偽裝:輪換 User-Agent、Accept-Language、模擬瀏覽器 Header 順序、TLS 指紋 (JA3)
  2. IP 輪換與冷卻:單 IP 請求頻率限制 (如 10 req/min)、錯誤碼觸發換 IP (403/429/5xx)
  3. Cookie 管理:登入態維護、Session 持久化、分散式 Cookie 池
  4. JavaScript 執行:必要時引入 Headless Chrome (Puppeteer/Playwright) 處理動態渲染、驗證碼
  5. 行為模擬:隨機延遲、滾動頁面、點擊軌跡、模擬人類閱讀停留
  6. 驗證碼識別:OCR (Tesseract)、第三方打碼平台、深度學習模型

四、分散式爬蟲架構

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  Scheduler  │────▶│  Redis Queue│────▶│  Workers × N│
│  (種子/去重) │     │  (優先級隊列)│     │  (無狀態)   │
└─────────────┘     └─────────────┘     └─────────────┘
                           │                    │
                    ┌──────┴──────┐      ┌──────┴──────┐
                    │  Proxy Pool │      │  Storage    │
                    │  (服務化)   │      │  (ES/MySQL) │
                    └─────────────┘      └─────────────┘
  • 調度器:種子 URL 入隊、BloomFilter/Redis Set 去重、優先級調度 (深度/重要性)
  • Worker:無狀態水平擴展、消費隊列、執行抓取、解析、持久化、新發現 URL 回推隊列
  • 代理池服務化:獨立部署、提供 gRPC/HTTP API、健康檢查、自動擴縮容
  • 監控告警:QPS、成功率、隊列積壓、代理可用率、目標站點反爬等級

五、法律合規與工程倫理

⚠️ 重要提醒:
  • 嚴格遵守 robots.txt 協議,檢查 User-agent: * 規則
  • 尊重網站服務條款 (ToS),未經授權不得大規模採集個人隱私、受版權保護內容
  • 控制抓取頻率,避免對目標伺服器造成拒絕服務影響
  • 資料使用需符合 GDPR、個資法等法規,建立資料處理紀錄
  • 企業級採集建議走官方 API 或商業授權管道

六、總結

構建生產級爬蟲系統的核心在於:代理池高可用、反爬對抗動態化、架構無狀態水平擴展、全鏈路可觀測。Jsoup + OkHttp + Redis + 代理服務化是經典且穩健的技術選型。切記:技術手段服務於合規業務,工程師應恪守法律底線與道德準則。

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