杰瑞科技汇

Java爬虫用HttpClient如何高效处理请求?

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

Java爬虫用HttpClient如何高效处理请求?-图1
(图片来源网络,侵删)

为什么选择 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

Java爬虫用HttpClient如何高效处理请求?-图2
(图片来源网络,侵删)
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());
    }
}

代码解析:

  1. HttpClient: 客户端的“引擎”,负责发送和接收请求,我们配置了它的版本和超时时间。
  2. HttpRequest: 代表一个 HTTP 请求,我们指定了请求的 URI 和方法(GET)。
  3. HttpResponse.BodyHandlers.ofString(): 这是一个响应体处理器,它告诉 HttpClient 将响应体(服务器返回的数据)作为一个 String 来处理。HttpClient 提供了多种处理器,ofByteArray(), ofInputStream() 等。
  4. 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

Java爬虫用HttpClient如何高效处理请求?-图3
(图片来源网络,侵删)

你需要添加 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,这种情况下,你需要使用 SeleniumPlaywright 等浏览器自动化工具。

Selenium 工作原理: Selenium 会启动一个真实的浏览器(如 Chrome、Firefox),并模拟用户操作,等待 JS 执行完毕后再获取最终的 HTML 内容。

简单 Selenium 示例 (需要配置 ChromeDriver):

  1. 添加 Selenium 依赖:
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>4.15.0</version> <!-- 请使用最新版本 -->
    </dependency>
  2. 下载与你 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

给你的建议:

  1. 对于静态网站:优先使用 HttpClient + Jsoup 的组合,这是最高效、最轻量的方案。
  2. 对于动态网站:当 HttpClient 无法获取数据时,再使用 SeleniumPlaywright
  3. 构建生产级爬虫:需要考虑 IP 代理池、User-Agent 池、请求限速、分布式任务队列(如 Redis + RabbitMQ) 等复杂架构。

希望这份详细的指南能帮助你掌握使用 Java HttpClient 进行网络爬虫开发!

分享:
扫描分享到社交APP
上一篇
下一篇