什么是“实时”抓取?
在开始之前,我们先明确“实时”的几种含义:

(图片来源网络,侵删)
- 准实时 (Near Real-Time / Scheduled Polling):以固定的、较短的时间间隔(如每5分钟、每30秒)去检查网页是否有更新,这是最常见、最容易实现的方式,适用于大多数场景。
- 真正的实时 (True Real-Time / Event-Driven):当网页内容发生变化时,立即得到通知,这通常需要依赖网页提供的技术,如 WebSockets 或 Server-Sent Events (SSE),这种方式技术难度较高,但延迟最低。
- 流式实时 (Streaming):抓取一个持续更新的数据流,例如社交媒体的实时信息流,这通常也依赖于 WebSocket 或类似的 API。
下面,我将针对这三种情况,从易到难,为你介绍 Java 实现方案。
准实时抓取 (定时任务)
这是最经典、最通用的方法,核心思想是:定时执行爬虫脚本,抓取数据并处理。
技术栈选择
- HTTP 客户端:用于发送请求和接收响应。
- Jsoup:强烈推荐,专门用于解析 HTML,非常简单易用,提供了强大的 CSS 和 jQuery 风格的选择器,非常适合抓取静态网页。
- Apache HttpClient:功能更强大的 HTTP 客户端,可以处理更复杂的请求(如处理 cookies、session、文件上传等)。
- Java 11+ HttpClient:Java 自带的 HTTP 客户端,功能日趋完善,无需额外依赖。
- 任务调度框架:用于定时执行抓取任务。
- Spring Boot Scheduler:如果你在使用 Spring Boot,这是最简单、最集成的选择。
- Quartz:功能非常强大的企业级任务调度框架,支持复杂的调度策略(如 Cron 表达式)。
- ScheduledExecutorService:Java 自带的一个线程池工具,适合简单的定时任务。
示例:使用 Jsoup + Spring Boot Scheduler
假设我们要定时抓取某个新闻网站的标题列表。
创建 Spring Boot 项目

(图片来源网络,侵删)
在 pom.xml 中添加 Jsoup 依赖:
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version> <!-- 使用最新版本 -->
</dependency>
编写爬虫服务
创建一个服务类,用于封装抓取逻辑。
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Service
public class NewsCrawlerService {
// 目标网站的 URL (这里以一个示例网站为例)
private static final String TARGET_URL = "https://news.ycombinator.com/";
/**
* 抓取新闻标题
* @return 标题列表
*/
public List<String> fetchNewsTitles() {
List<String> titles = new ArrayList<>();
try {
// 1. 发送 HTTP GET 请求,获取 HTML 文档
// 使用 User-Agent 模拟浏览器,避免被某些网站拦截
Document doc = Jsoup.connect(TARGET_URL)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
.get();
// 2. 使用 CSS 选择器解析 HTML,提取标题
// 在 Hacker News 中,每个新闻标题都在 <span class="titleline"> 下的 <a> 标签里
Elements titleElements = doc.select("span.titleline > a");
// 3. 遍历提取到的元素,获取文本内容
for (Element element : titleElements) {
titles.add(element.text());
}
} catch (IOException e) {
e.printStackTrace();
System.err.println("抓取网页数据失败: " + e.getMessage());
}
return titles;
}
}
创建定时任务

(图片来源网络,侵删)
创建一个配置类,启用定时任务,并定义抓取任务的执行频率。
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@EnableScheduling // 启用 Spring 的定时任务功能
public class ScheduledTasks {
private final NewsCrawlerService newsCrawlerService;
// 通过构造器注入,推荐方式
public ScheduledTasks(NewsCrawlerService newsCrawlerService) {
this.newsCrawlerService = newsCrawlerService;
}
/**
* 定时执行抓取任务
* fixedRate = 300000 表示每 300,000 毫秒(即 5 分钟)执行一次
* cron = "0 */5 * * * ?" 表示每 5 分钟的整点执行(cron 表达式更灵活)
*/
@Scheduled(fixedRate = 300000) // 5分钟执行一次
public void crawlNews() {
System.out.println("--- 开始执行定时抓取任务: " + new java.util.Date() + " ---");
List<String> titles = newsCrawlerService.fetchNewsTitles();
if (!titles.isEmpty()) {
System.out.println("成功抓取到 " + titles.size() + " 条新闻标题:");
titles.forEach(System.out::println);
} else {
System.out.println("未抓取到任何新闻标题。");
}
System.out.println("--- 定时抓取任务结束 ---");
}
}
启动应用
运行你的 Spring Boot 应用,你会看到控制台每隔 5 分钟打印一次抓取到的新闻标题。
真正的实时抓取 (事件驱动)
这种方法的核心是订阅网页的更新,而不是主动去“拉取”,这需要目标网站支持。
技术栈选择
- WebSocket 客户端:用于与服务器建立持久连接,实现双向通信。
- Java-WebSocket:一个流行的、轻量级的 WebSocket 客户端库。
- Spring WebSocket:如果你在用 Spring Boot,可以使用其集成的 WebSocket 支持。
示例:使用 Java-WebSocket
假设有一个网站通过 WebSocket 实时推送股票价格。
添加依赖
在 pom.xml 中添加 Java-WebSocket:
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.5</version> <!-- 使用最新版本 -->
</dependency>
创建 WebSocket 客户端
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.net.URISyntaxException;
public class RealTimeDataClient extends WebSocketClient {
public RealTimeDataClient(URI serverUri) {
super(serverUri);
}
// 连接建立时调用
@Override
public void onOpen(ServerHandshake handshakedata) {
System.out.println("连接成功!实时数据流已开启。");
// 可以在这里发送订阅消息,如果需要的话
// this.send("SUBSCRIBE_STOCK:AAPL");
}
// 收到服务器消息时调用
@Override
public void onMessage(String message) {
// 这里的 message 就是服务器推送的实时数据
System.out.println("收到实时数据: " + message);
// 在这里解析 message 并处理数据
// parseStockData(message);
}
// 连接关闭时调用
@Override
public void onClose(int code, String reason, boolean remote) {
System.out.println("连接已关闭,原因: " + reason);
}
// 发生错误时调用
@Override
public void onError(Exception ex) {
System.err.println("连接发生错误: " + ex.getMessage());
ex.printStackTrace();
}
public static void main(String[] args) {
try {
// 替换为实际的 WebSocket 服务器地址
String url = "wss://your-realtime-data-server.com/stocks";
RealTimeDataClient client = new RealTimeDataClient(new URI(url));
client.connect();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
如何运行:
直接运行 main 方法,程序会连接到 WebSocket 服务器,并持续监听和打印服务器推送的消息,只要连接不中断,你就能“实时”收到数据。
流式实时抓取 (API 驱动)
很多现代网站(尤其是社交媒体、金融数据平台)不会直接开放 HTML 抓取,而是提供官方的 REST API 或流式 API,这是最稳定、最可靠的方式。
技术栈选择
- HTTP 客户端:与方案一相同,用于调用 REST API。
- Spring RestTemplate 或 Spring WebClient:Spring 生态中的 HTTP 客户端,WebClient 是响应式的,更适合处理流数据。
- API 密钥:通常需要注册开发者账号并获取 API Key。
示例:使用 Spring WebClient 调用流式 API
假设我们要获取 Twitter (X) 的某个公共话题的实时推文流(注意:Twitter API 有严格的限制和认证)。
添加依赖
<!-- Spring Web (包含 RestTemplate 和 WebClient) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 如果你需要更强大的 JSON 处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
编写 API 调用服务
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
@Service
public class StreamingApiService {
// 创建 WebClient 实例 (通常做成单例)
private final WebClient webClient;
public StreamingApiService() {
this.webClient = WebClient.builder()
.baseUrl("https://api.twitter.com/2/tweets/sample/stream") // 示例 API 端点
.defaultHeader("Authorization", "Bearer YOUR_API_KEY_HERE") // 替换为你的 API Key
.build();
}
/**
* 获取推文流
* @return 一个 Flux,每次发射一条新的推文
*/
public Flux<String> getTweetStream() {
return webClient.get()
.retrieve()
.bodyToFlux(String.class) // 将响应体流式地转换为 String
.map(tweet -> {
// 在这里解析 JSON 并提取你需要的信息
// JSONObject json = new JSONObject(tweet);
// String text = json.getJSONObject("data").getString("text");
// return text;
return "收到一条推文: " + tweet.substring(0, Math.min(100, tweet.length())) + "...";
});
}
}
结合定时任务或直接调用
你可以创建一个端点来启动这个流,或者在一个后台线程中订阅它。
// 在一个 Controller 或 ScheduledTask 中
@Autowired
private StreamingApiService streamingApiService;
public void startStreaming() {
System.out.println("开始监听推文流...");
streamingApiService.getTweetStream()
.subscribe(tweet -> {
// subscribe 是一个阻塞操作,会一直等待新数据
System.out.println(tweet);
});
}
总结与对比
| 特性 | 准实时 (定时任务) | 真正的实时 (WebSocket) | 流式实时 (API) |
|---|---|---|---|
| 实现难度 | 简单 | 中等 | 中等 |
| 延迟 | 较高 (取决于轮询间隔) | 极低 (毫秒级) | 极低 (取决于 API) |
| 稳定性 | 一般,易受网站反爬虫影响 | 高,基于标准协议 | 高,官方接口稳定 |
| 适用场景 | 更新不频繁的网站 | 支持实时推送的网站/应用 (如聊天、股票行情) | 提供官方 API 的平台 (如社交媒体、金融) |
| 关键技术 | Jsoup, Spring Scheduler | Java-WebSocket, Spring WebSocket | Spring WebClient, RestTemplate, API Key |
重要注意事项
- 遵守
robots.txt:在抓取任何网站之前,请务必检查并遵守其robots.txt文件(https://example.com/robots.txt),它规定了哪些页面可以抓取,哪些不可以。 - 添加请求头:设置合理的
User-Agent和其他请求头,模拟浏览器行为,避免被识别为爬虫而封禁。 - 处理反爬机制:现代网站有各种反爬手段:
- IP 封禁:使用代理 IP 池。
- 验证码:需要接入打码平台或使用无头浏览器(如 Selenium, Playwright)。
- 频率限制:控制请求频率,在代码中加入
Thread.sleep()。
- 数据存储:抓取到的数据需要持久化,根据数据量和查询需求,可以选择 MySQL, PostgreSQL, MongoDB, Elasticsearch 等。
- 异常处理和重试:网络请求不稳定,必须做好异常捕获和失败重试机制。
给你的建议:
- 新手入门:从 方案一 (Jsoup + 定时任务) 开始,这是理解网页抓取基础的最佳方式。
- 追求低延迟:如果目标网站支持,方案二 (WebSocket) 是最佳选择。
- 企业级应用:优先寻找 方案三 (官方 API),如果没有,再考虑自己抓取,并做好反爬和稳定性设计。
