这篇教程将涵盖从最基础的 GET 请求到处理更复杂场景,如 POST 请求、处理 Cookies、解析 HTML、处理动态加载内容以及反爬虫策略。

为什么选择 HttpClient?
- 现代化 API:基于
java.net.http包,API 设计清晰、易于使用。 - 异步支持:内置强大的异步非阻塞 I/O,能极大地提高爬取效率。
- HTTP/2 支持:原生支持 HTTP/2 协议,可以复用连接,提升性能。
- 标准库:无需添加第三方依赖,开箱即用。
环境准备
你需要一个支持 Java 11 或更高版本的项目。
如果你使用 Maven,在 pom.xml 中不需要添加任何依赖,因为 HttpClient 是标准库的一部分。
如果你使用 Gradle,在 build.gradle 中同样不需要添加依赖。
第一个爬虫:发送一个简单的 GET 请求
我们的目标是爬取一个网页的 HTML 内容,http://example.com。

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class SimpleCrawler {
public static void main(String[] args) throws Exception {
// 1. 创建 HttpClient 实例
// 它是线程安全的,通常建议在整个应用中只创建一个实例并复用
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // 指定使用 HTTP/2
.connectTimeout(Duration.ofSeconds(10)) // 设置连接超时
.build();
// 2. 创建 HttpRequest 请求
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://example.com"))
.GET() // GET 请求是默认的,可以省略
.timeout(Duration.ofSeconds(10)) // 设置请求超时
.build();
// 3. 发送请求并获取响应
// .send() 是同步方法,会阻塞直到收到响应
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// 4. 处理响应
System.out.println("状态码: " + response.statusCode());
System.out.println("响应头: " + response.headers());
System.out.println("响应体 (HTML):");
System.out.println(response.body());
}
}
代码解析:
HttpClient: 客户端的“引擎”,负责发送和接收请求,我们配置了它的版本和超时时间。HttpRequest: 代表一个 HTTP 请求,我们指定了请求的 URI 和方法(GET)。HttpResponse.BodyHandlers.ofString(): 这是一个响应体处理器,它告诉HttpClient将响应体(服务器返回的数据)作为一个String来处理。HttpClient提供了多种处理器,ofByteArray(),ofInputStream()等。client.send(...): 同步发送请求,返回一个HttpResponse对象,包含了状态码、响应头和响应体。
进阶爬虫:处理 POST 请求、Headers 和 Cookies
很多网站需要登录后才能访问数据,这时就需要用到 POST 请求,并且需要处理 Cookies 来维持登录状态。
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse.BodyHandlers;
import java.time.Duration;
import java.util.List;
import java.util.Map;
public class AdvancedCrawler {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.followRedirects(HttpClient.Redirect.NORMAL) // 自动跟随重定向
.cookieHandler(new java.net.CookieManager()) // 启用 Cookie 管理
.connectTimeout(Duration.ofSeconds(10))
.build();
// --- 模拟登录 ---
String loginUrl = "https://httpbin.org/post"; // 一个用于测试 POST 请求的网站
String loginData = "username=myuser&password=mypassword";
HttpRequest loginRequest = HttpRequest.newBuilder()
.uri(URI.create(loginUrl))
.header("Content-Type", "application/x-www-form-urlencoded") // 设置请求头
.POST(BodyPublishers.ofString(loginData)) // 设置为 POST 请求,并设置请求体
.build();
HttpResponse<String> loginResponse = client.send(loginRequest, BodyHandlers.ofString());
System.out.println("--- 登录响应 ---");
System.out.println("状态码: " + loginResponse.statusCode());
// httpbin.org 会返回你发送的请求信息,包括 headers, form data 等
System.out.println("登录响应体: " + loginResponse.body());
// --- 登录后访问需要权限的页面 ---
// 因为我们在 HttpClient 中设置了 CookieManager,所以登录时的 Cookie 会被自动保存
String protectedUrl = "https://httpbin.org/cookies";
HttpRequest protectedRequest = HttpRequest.newBuilder()
.uri(URI.create(protectedUrl))
.GET()
.build();
HttpResponse<String> protectedResponse = client.send(protectedRequest, BodyHandlers.ofString());
System.out.println("\n--- 访问受保护页面 ---");
System.out.println("状态码: " + protectedResponse.statusCode());
System.out.println("响应体: " + protectedResponse.body());
}
}
关键点:
- POST 请求: 使用
POST(BodyPublishers.ofString(...))来发送 POST 请求,并附带请求体。 - 请求头: 使用
.header("Key", "Value")方法添加或修改请求头,User-Agent,Content-Type等。 - Cookies:
HttpClient.Builder的.cookieHandler()方法是关键,它会自动处理Set-Cookie响应头,并在后续请求中自动附上相应的Cookie头,完美实现了会话保持。 - 跟随重定向:
.followRedirects(HttpClient.Redirect.NORMAL)让客户端自动处理 301/302 等重定向,非常方便。
解析 HTML:使用 Jsoup
拿到 HTML 字符串后,我们需要从中提取有用的信息,Java 中最流行的 HTML 解析库是 Jsoup。

你需要添加 Jsoup 依赖:
Maven (pom.xml):
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version> <!-- 请使用最新版本 -->
</dependency>
Gradle (build.gradle):
implementation 'org.jsoup:jsoup:1.17.2' // 请使用最新版本
示例:爬取百度百科词条的标题和简介
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class JsoupCrawler {
public static void main(String[] args) throws Exception {
String url = "https://baike.baidu.com/item/Java/85213";
HttpClient client = HttpClient.newBuilder()
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") // 模拟浏览器
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
// 使用 Jsoup 解析 HTML
Document doc = Jsoup.parse(response.body());
// 选择器语法,类似于 jQuery
// 爬取标题
Element titleElement = doc.selectFirst("h1.title");
if (titleElement != null) {
System.out.println("标题: " + titleElement.text());
}
// 爬取简介段落
Element summaryElement = doc.selectFirst(".lemma-summary");
if (summaryElement != null) {
System.out.println("\n简介:");
System.out.println(summaryElement.text());
}
// 爬取所有段落
System.out.println("\n所有段落:");
Elements paragraphs = doc.select("p");
for (Element p : paragraphs) {
if (!p.text().trim().isEmpty()) {
System.out.println("- " + p.text());
}
}
} else {
System.err.println("请求失败,状态码: " + response.statusCode());
}
}
}
Jsoup 核心概念:
Document: 代表整个 HTML 文档。Element: 代表 HTML 中的一个标签。Elements:Element的集合,提供类似 List 的操作。select(String cssQuery): 最强大的方法,使用 CSS 选择器来查找元素。#id,.class,tag,[attribute]等。text(): 获取元素的纯文本内容。attr("attrName"): 获取元素的属性值,如href,src。
处理动态加载内容(JavaScript 渲染)
现代网站很多内容是通过 JavaScript 动态加载的,HttpClient 只能获取初始的 HTML,无法执行 JS,这种情况下,你需要使用 Selenium 或 Playwright 等浏览器自动化工具。
Selenium 工作原理: Selenium 会启动一个真实的浏览器(如 Chrome、Firefox),并模拟用户操作,等待 JS 执行完毕后再获取最终的 HTML 内容。
简单 Selenium 示例 (需要配置 ChromeDriver):
- 添加 Selenium 依赖:
<dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>4.15.0</version> <!-- 请使用最新版本 --> </dependency> - 下载与你 Chrome 浏览器版本匹配的 ChromeDriver,并配置到系统
PATH中。
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public class SeleniumCrawler {
public static void main(String[] args) throws InterruptedException {
// 确保你的 chromedriver.exe 在系统 PATH 中,或者指定其路径
// System.setProperty("webdriver.chrome.driver", "path/to/your/chromedriver.exe");
WebDriver driver = new ChromeDriver();
try {
driver.get("https://example.com/some-dynamic-content");
// 显式等待,直到某个元素加载出来
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement dynamicElement = wait.until(
ExpectedConditions.presenceOfElementLocated(By.id("dynamic-content-id"))
);
System.out.println("动态加载的内容: " + dynamicElement.getText());
} finally {
// 操作完成后,关闭浏览器
driver.quit();
}
}
}
反爬虫策略与应对
网站为了防止被爬取,会设置各种反爬虫机制,你需要有策略地应对:
| 反爬虫策略 | 应对方法 |
|---|---|
| User-Agent 检测 | 设置一个常见的浏览器 User-Agent,可以准备一个列表,每次请求随机选择一个。 |
| IP 封禁 | 使用代理 IP 池,每次请求或每隔一段时间切换一个 IP。 |
| 验证码 | 使用第三方打码平台(如 2Captcha, Anti-Captcha)的 API 来识别验证码。 |
| 请求频率限制 | 在代码中设置 Thread.sleep() 或使用 ScheduledExecutorService 来控制请求间隔,模拟人类行为。 |
| 登录验证 | 使用 HttpClient 的 CookieHandler 维护登录会话。 |
| 动态渲染 | 使用 Selenium 或 Playwright 等工具。 |
示例:使用代理和随机 User-Agent
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Random;
import java.util.List;
import java.util.Arrays;
public class AntiCrawlerExample {
// 模拟一个代理IP列表
private static final List<String> PROXY_LIST = Arrays.asList(
"http://proxy1.example.com:8080",
"http://proxy2.example.com:8080"
);
// 模拟一个User-Agent列表
private static final List<String> USER_AGENTS = Arrays.asList(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
);
public static void main(String[] args) throws Exception {
Random random = new Random();
String proxy = PROXY_LIST.get(random.nextInt(PROXY_LIST.size()));
String userAgent = USER_AGENTS.get(random.nextInt(USER_AGENTS.size()));
System.out.println("本次使用代理: " + proxy);
System.out.println("本次使用 User-Agent: " + userAgent);
HttpClient client = HttpClient.newBuilder()
.proxy(HttpClient.ProxySelector.of(new java.net.URI(proxy).toSocketAddress())) // 设置代理
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://httpbin.org/ip")) // 一个可以返回你 IP 的网站
.header("User-Agent", userAgent)
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("\n请求结果:");
System.out.println(response.body());
// 模拟人类操作,间隔 1-3 秒
Thread.sleep(1000 + random.nextInt(2000));
}
}
| 特性 | HttpClient (Java 11+) |
Apache HttpClient | Selenium |
|---|---|---|---|
| 类型 | Java 标准库 | 第三方库 | 浏览器自动化工具 |
| 主要用途 | 发送 HTTP 请求 | 发送 HTTP 请求 | 模拟浏览器操作 |
| JS 支持 | 不支持 | 不支持 | 支持 |
| 性能 | 高(尤其异步) | 高 | 较低(启动真实浏览器) |
| 易用性 | API 现代 | API 成熟但稍显复杂 | API 直观 |
| 依赖 | 无需添加 | 需要添加 Maven/Gradle 依赖 | 需要添加依赖和 WebDriver |
给你的建议:
- 对于静态网站:优先使用
HttpClient+Jsoup的组合,这是最高效、最轻量的方案。 - 对于动态网站:当
HttpClient无法获取数据时,再使用 Selenium 或 Playwright。 - 构建生产级爬虫:需要考虑 IP 代理池、User-Agent 池、请求限速、分布式任务队列(如 Redis + RabbitMQ) 等复杂架构。
希望这份详细的指南能帮助你掌握使用 Java HttpClient 进行网络爬虫开发!
