Java 抓取網頁 (爬蟲/HTTP 客戶端) 是後端常見需求。JDK 內建 `HttpURLConnection` 功能簡陋、API 繁瑣;現代開發首選 **Apache HttpClient 5.x** (或 JDK 11+ `HttpClient`)。本文提供生產級封裝:連接池管理、超時精細控制、自動編碼檢測、GZIP 解壓、重試機制、User-Agent 池、代理支援。
<!-- Apache HttpClient 5.x (推薦,功能最全) -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 可選:HTML 解析 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public final class HttpFetcher {
// 連接池管理器 (線程安全,全局單例)
private static final PoolingHttpClientConnectionManager CONN_MANAGER = new PoolingHttpClientConnectionManager();
static {
CONN_MANAGER.setMaxTotal(200); // 最大總連接數
CONN_MANAGER.setDefaultMaxPerRoute(50); // 每路由最大連接數
CONN_MANAGER.setValidateAfterInactivity(TimeValue.ofSeconds(30)); // 空閒驗證
}
// HTTP 客戶端 (線程安全,全局單例)
private static final CloseableHttpClient HTTP_CLIENT = HttpClients.custom()
.setConnectionManager(CONN_MANAGER)
.setConnectionManagerShared(true)
.evictIdleConnections(TimeValue.ofMinutes(5)) // 定期清理空閒
.disableAutomaticRetries() // 由應用層控制重試
.build();
// 請求配置模板
private static final RequestConfig REQUEST_CONFIG = RequestConfig.custom()
.setConnectTimeout(Timeout.ofSeconds(10)) // 建立連接超時
.setResponseTimeout(Timeout.ofSeconds(30)) // 等待響應超時 (含讀取)
.setConnectionRequestTimeout(Timeout.ofSeconds(5)) // 從池獲取連接超時
.setRedirectsEnabled(true) // 自動跟隨重定向
.setMaxRedirects(5)
.setCircularRedirectsAllowed(false)
.build();
// User-Agent 池 (簡單反爬)
private static final List USER_AGENTS = List.of(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
);
private static final Random RANDOM = new Random();
private HttpFetcher() {}
/**
* GET 請求獲取文本內容 (自動處理 GZIP、編碼檢測)
* @param url 目標 URL
* @return 響應文本,失敗拋異常
*/
public static String fetch(String url) throws IOException {
return fetch(url, null, null);
}
/**
* 完整參數版本
* @param url 目標 URL
* @param headers 額外請求頭 (可覆蓋默認 UA)
* @param proxy 代理 (host, port),無則 null
*/
public static String fetch(String url, java.util.Map headers, ProxyConfig proxy) throws IOException {
HttpGet request = new HttpGet(url);
request.setConfig(REQUEST_CONFIG);
// 設置隨機 User-Agent
request.setHeader(HttpHeaders.USER_AGENT, USER_AGENTS.get(RANDOM.nextInt(USER_AGENTS.size())));
// 接受壓縮
request.setHeader(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate, br");
request.setHeader(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
request.setHeader(HttpHeaders.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.9,en;q=0.8");
request.setHeader(HttpHeaders.CONNECTION, "keep-alive");
// 合併額外頭
if (headers != null) {
headers.forEach(request::setHeader);
}
// 代理設置 (HttpClient 5.x 需在客戶端或請求配置中設置,此處簡化)
// 實際項目建議構建帶代理的客戶端實例
return HTTP_CLIENT.execute(request, response -> {
int status = response.getCode();
if (status >= 300) {
throw new HttpException("HTTP 錯誤: " + status + " " + response.getReasonPhrase());
}
HttpEntity entity = response.getEntity();
if (entity == null) return "";
// 關鍵:EntityUtils.toString 自動處理 GZIP 解壓、Content-Type charset 解析、無 charset 回退 UTF-8
// 也可手動控制:EntityUtils.toString(entity, StandardCharsets.UTF_8)
return EntityUtils.toString(entity);
});
}
// 代理配置數據類
public record ProxyConfig(String host, int port) {}
// 關閉資源 (應用關閉時調用)
public static void shutdown() throws IOException {
HTTP_CLIENT.close();
CONN_MANAGER.close();
}
}
public class CrawlerDemo {
public static void main(String[] args) {
try {
// 1. 簡單獲取
String html = HttpFetcher.fetch("https://www.example.com");
System.out.println("長度: " + html.length());
// 2. 解析 (Jsoup)
org.jsoup.nodes.Document doc = org.jsoup.Jsoup.parse(html);
String title = doc.title();
System.out.println("標題: " + title);
// 3. 帶自定義頭/代理 (需 HttpFetcher 支持)
// String html2 = HttpFetcher.fetch(url, Map.of("Referer", "https://google.com"), new HttpFetcher.ProxyConfig("127.0.0.1", 7890));
} catch (IOException e) {
e.printStackTrace();
} finally {
try { HttpFetcher.shutdown(); } catch (IOException ignored) {}
}
}
}
| 超時類型 | 含義 | 推薦值 | 後果 |
|---|---|---|---|
| Connect Timeout | TCP 三次握手建立連接耗時 | 5-10s | 網絡不可達、防火牆攔截、目標端口未開放 |
| Connection Request Timeout | 從連接池獲取可用連接等待時間 | 1-5s | 並發過高、池耗盡、死鎖 |
| Response Timeout (Socket Timeout) | 發送請求後等待第一個數據包返回的時間 | 15-60s | 服務端處理慢、大文件下載、長鏈路 |
**注意**:`setResponseTimeout` 在 HttpClient 5.x 中包含讀取數據的總時間,非單次讀取間隔。大文件下載需調大或使用流式處理。
JDK 11 引入 `java.net.http.HttpClient` (JEP 321),支援 HTTP/2、WebSocket、CompletableFuture 異步、無第三方依賴。
// JDK 11+ 原生客戶端示例
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.header("User-Agent", "Java HttpClient")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
String body = response.body(); // 自動解碼、解壓
| 特性 | Apache HttpClient 5.x | JDK 11+ HttpClient |
|---|---|---|
| HTTP/2 | 支援 (需額外依賴) | 原生支援 |
| 異步非阻塞 | 支援 (CompletableFuture/Callback) | 原生支援 (CompletableFuture) |
| 連接池 | 成熟、豐富配置、監控 | 基礎支援 |
| Cookie 管理 | 完整 (BrowserCompatCookieSpec 等) | 基礎 CookieManager |
| 代理/認證 | 極其靈活 | 基礎支援 |
| 依賴體積 | ~1.5 MB | 0 (JDK 內建) |
| 適用場景 | 複雜爬蟲、企業級集成、需精細控制 | 微服務調用、簡單抓取、無依賴項目 |
Java 抓取網頁的生產級方案核心:**HttpClient 5.x 連接池單例 + 三層超時精細控制 + EntityUtils 自動編碼/解壓 + User-Agent 輪換 + 異常分類處理**。對於簡單場景,JDK 11+ 原生 `HttpClient` 是零依賴的優秀選擇。切記:**遵守 robots.txt、控制請求頻率、處理異常重試、記錄日誌**,做負責任的工程師。