使用 Java 进行网页抓取:Jsoup、Playwright 和云浏览器工作流
Expert in Web Scraping Technologies
摘要:
- Jsoup 解析提供给它的 HTML。 它不会运行页面的 JavaScript 去创建缺失的记录。
- Playwright Java 提供浏览器采集路径。 渲染后的 HTML 可以复用静态页面所用的同一套 Jsoup 抽取约定。
- Agent Browser 将浏览器执行迁移到云端。 Java 仍然负责选择器、记录校验、分页范围和导出。
- 只有当 CSV 中的记录能标识其预期来源时,它才有用。 保留稳定的记录 ID 和解析后的 URL,并拒绝缺失必填字段的记录。
Java 网络抓取从一个实用问题开始:服务器返回的 HTML 中是否已经包含记录,还是必须通过浏览器来创建?正确选择采集路径可以避免为一个从未包含目标数据的文档编写选择器。
该工作流使用 Jsoup 进行抽取,使用 Playwright Java 进行渲染页面采集。两条路径产生相同的记录结构。当浏览器工作负载应运行在应用宿主之外时,可以使用专用的 Scrapeless Agent Browser 连接作为云端选项。
示例针对具有稳定属性的受控文章列表。在检查你被允许使用的来源之前,不要替换这份约定。Java 编译和浏览器执行需要下面列出的运行时和凭据;本指南并不声称进行了实时的 Java 采集。
从页面的实际内容选择 Jsoup 或浏览器
当所需记录存在于获取到的 HTML 中时,选择 Jsoup。当允许访问的页面在记录出现之前需要 JavaScript 或交互时,选择浏览器。
| 路径 | 提供给解析器的 HTML | 合适的起始任务 | 你保留的责任 |
|---|---|---|---|
| Jsoup HTTP 获取 | 服务器返回的文档 | 静态文章或目录列表 | 源检查和抽取 |
| 本地 Playwright Java | 浏览器渲染的文档 | 受控的动态页面开发 | 浏览器运行时与资源管理 |
| 搭配 Playwright Java 的 Agent Browser | 云浏览器文档 | 专用的远程浏览器工作流 | 连接生命周期、选择器与输出检查 |
不要根据构建站点所用的框架来选择路径。JavaScript 应用可以提供有用的 HTML,而看似简单的列表也可能在加载后插入记录。请检查具体页面及其所需字段。
HTTP 表示语义 描述了客户端接收到的响应。之后由浏览器产生的页面状态是另一种采集观测结果。
前提条件与依赖
该项目需要 JDK 和 Maven,以及 Jsoup 与 Playwright Java 依赖。展示的项目以 JDK 17 为目标;它将 Jsoup 固定为 1.23.2、Playwright 固定为 1.63.0,而不是重用旧教程中的版本。
对于本地渲染,请在你自己的项目环境中安装与你的 Playwright 版本所需匹配的浏览器运行时。对于远程渲染,通过当前账户设置获取专用的 Agent Browser 连接,并将其完整端点保存在 SCRAPELESS_CDP_URL 中。
目标必须是公开的或已获授权的。TARGET_URL 是一个你已经检查过结构的、被允许的文章列表 URL。示例的选择器约定是 main article[data-id],每篇文章内包含一个标题和一个链接。
注意:Java 工具链、依赖安装和浏览器运行时都是执行前提条件。代码片段已经根据当前库接口进行过检查,但并未在可用的验证环境中编译或执行。远程路径同样需要真实的专用 Scrapeless 连接。
将这一 Maven 配置保存为 pom.xml,并将下面的类保存为 src/main/java/ArticleCollector.java:
xml
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>article-collector</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.23.2</version>
</dependency>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.63.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
</plugin>
</plugins>
</build>
</project>
在该项目中保持编译目标和依赖版本不变,以便其他环境在启用任何采集任务之前可以复现这套配置。
为两条路径配置同一个抽取约定
抽取约定独立于传输方式来定义可接受的文章记录。本示例要求非空的 data-id、一个 h2 标题,以及一个可解析为 HTTP 或 HTTPS URL 的链接。
这些是受控示例的属性,而不是对某公共站点当前选择器的声明。针对你的来源,在可用时应优先选择稳定的记录属性或持久的 URL 模式。不要仅仅因为某个装饰性类在一次采集中匹配成功就去复用它。
将必填字段与可选字段区分开来。缺失标识符会导致示例记录被拒绝。可选摘要可以缺失而不改变记录身份。在导出之前,把这类策略明确下来。
基本实现:获取、抽取和导出
下面的类通过所选路径获取 HTML,用 Jsoup 抽取文章记录,并写入 UTF-8 编码的 CSV 文件。其远程分支使用账户提供的 CDP 端点,而不是虚构一个 Java Scrapeless SDK 方法。
注意:这个完整的 Java 示例需要固定版本的依赖、JDK、Maven 配置以及一个被允许的目标。
local分支需要浏览器运行环境;remote分支需要SCRAPELESS_CDP_URL。下面展示的输出均不是实时执行捕获结果。
java
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.WaitUntilState;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.net.URI;
public class ArticleCollector {
private static String csv(String value) {
return "\"" + value.replace("\"", "\"\"") + "\"";
}
public static void main(String[] args) throws Exception {
if (args.length != 2) throw new IllegalArgumentException("mode and URL required");
String mode = args[0];
String target = args[1];
Document doc;
if (mode.equals("static")) {
doc = Jsoup.connect(target).get();
} else {
if (!mode.equals("local") && !mode.equals("remote"))
throw new IllegalArgumentException("Unsupported acquisition mode");
try (Playwright playwright = Playwright.create()) {
String endpoint = System.getenv("SCRAPELESS_CDP_URL");
if (mode.equals("remote") && (endpoint == null || endpoint.isBlank()))
throw new IllegalArgumentException("Dedicated CDP endpoint required");
Browser browser = mode.equals("remote")
? playwright.chromium().connectOverCDP(endpoint)
: playwright.chromium().launch();
try {
Page page = browser.newPage();
page.navigate(target, new Page.NavigateOptions()
.setWaitUntil(WaitUntilState.DOMCONTENTLOADED));
page.locator("main article[data-id] a[href]").first().waitFor();
doc = Jsoup.parse(page.content(), page.url());
} finally {
browser.close();
}
}
}
StringBuilder output = new StringBuilder("id,title,url\r\n");
int accepted = 0;
for (Element article : doc.select("main article[data-id]")) {
Element heading = article.selectFirst("h2");
Element link = article.selectFirst("a[href]");
String id = article.attr("data-id").strip();
if (id.isEmpty() || heading == null || link == null) continue;
String title = heading.text().strip();
String url = link.attr("abs:href");
URI resolved = URI.create(url);
if (title.isEmpty() || resolved.getHost() == null ||
!("https".equals(resolved.getScheme()) || "http".equals(resolved.getScheme())))
continue;
output.append(csv(id)).append(',').append(csv(title)).append(',')
.append(csv(url)).append("\r\n");
accepted++;
}
if (accepted == 0) throw new IllegalStateException("Content contract not satisfied");
Files.writeString(Path.of("articles.csv"), output, StandardCharsets.UTF_8);
System.out.println("Accepted article records: " + accepted);
}
}
使用 Maven 编译项目并复制其依赖项,然后在类路径中包含 target/classes 和依赖目录来运行 ArticleCollector。选择 static、local 或 remote,并提供你已检查的目标 URL。
注意:这些 POSIX Shell 命令需要上述 Java 工具链和依赖。将
TARGET_URL设置为被允许的来源。对本示例来说,编译和依赖收集仍然是执行的前提条件。
bash
mvn -q dependency:copy-dependencies compile
java -cp 'target/classes:target/dependency/*' ArticleCollector static "$TARGET_URL"
在 Windows 环境中使用分号作为类路径分隔符。仅在完成其浏览器运行环境或专用会话配置之后,才选择 local 或 remote 模式。
代码使用 abs:href,因此相对链接会相对于捕获文档的基础 URL 进行解释。相对 URI 解析 解释了为何仅有路径并不是一个完整的来源标识。请在应用程序的捕获清单中,将获取到的页面 URL 与每个发现的文章 URL 分别保存。
使用 Playwright Java 渲染动态页面
浏览器路径会在收集页面 HTML 之前等待一个特定任务的元素。domcontentloaded 是导航的里程碑;文章定位器则建立了示例记录已出现这一单独条件。
不要将这两个条件中的任意一个视为所有记录都已到达的证明。某些列表可能只有在显式操作后才会加载更多行。在接受结果前,请检查首批记录是否已经满足所请求任务的完整性。
Playwright 浏览器连接方法 区分了其原生协议与 CDP。CDP 连接适用于基于 Chromium 的浏览器,其能力与原生的 Playwright 协议连接不同。
在开发真实采集器时,应将超时和来源就绪条件保留在项目配置中。与等待无关的分析流量空闲相比,一个来源特定的可见标记更有用。
从 Scrapeless 开始抓取
借助 Scrapeless 提升你的网页抓取与自动化工作流!
立即注册即可获得 $5 免费额度 —— 无需信用卡。现在就前往 Scrapeless 控制面板 领取你的免费额度。
本地 Java 爬虫的边界
本地 Java 解析器无法提供缺失的渲染内容,而本地浏览器也并不能消除管理浏览器资源和获取状态的需求。这些限制决定了工作流何时需要云端浏览器。
Scrapeless Agent Browser 为这个 Java 工作流提供远程浏览器层。通过 Agent Browser Playwright 配置 获得一个专用连接,然后将完整的连接端点传递给 remote 分支。
连接 URL 可能包含凭据。应将其保存在运行环境中,不要将其写入日志,并且只关闭为该任务分配的专用会话。连接到共享浏览器并关闭它会影响使用该会话的其他作业。
云端路径改变了采集执行的位置。它在 Java 中仍然保持相同的 HTML 解析和记录校验。挑战页面或错误的页面状态依然应当导致内容契约失败,而不是产生看似成功的导出结果。
发现链接与有界分页
分页应遵循来源经检查的导航契约以及有边界的收集范围。应从真实来源中发现下一页 URL 或允许的交互方式,而不是猜测页码参数。
保留当前页面标识、发现的下一页链接以及已访问页面集合。在导航之前,确认该链接属于已批准的来源范围。当任务的页面预算用尽、出现重复 URL,或来源表明不存在后续页面时,应当停止分页。
缺失的下一页链接可能意味着列表已结束,也可能是来源未能渲染其导航。使用页面内容和空状态证据来区分这两种结果。上面的单页类刻意将该决策留给应用程序,而不是暗示存在通用的分页循环。
导出并验证记录
导出步骤应保留已接受记录的标识,并正确编码所选格式。CSV 字段引用规则涵盖了分隔符和引号的处理;示例会将内部引号加倍并对每个字段加引号。
CSV 语法与内容验证是分开的。使用预期的下游解析器重新打开输出,并检查必填字段、唯一标识符和源链接。当工作簿是消费方时,使用让不受信任文本保持为数据的电子表格导入策略。
CSV 头描述了约定:id、title 和 url。这是一个规范性的示例模式,而不是捕获的数据集。没有匹配记录的源会导致示例停止;不会悄然将该结果标记为有效的空列表。
排查采集边界
缺失的记录应根据已获取文档和抽取约定进行诊断。仅凭空的 CSV 无法确定原因。
| 症状 | 检查 | 可能的工程操作 |
|---|---|---|
| 静态 HTML 缺少所需记录 | 原始响应和源就绪情况 | 选择被允许的浏览器路径 |
| 浏览器渲染了不相关内容 | 最终页面和页面状态 | 更正起始 URL 或交互方式 |
| 部分记录缺少 ID 或标题 | 源标记和必填字段 | 更新或收紧约定 |
| 相对链接导出不正确 | 捕获的基础 URL 和链接属性 | 将链接解析到正确的文档上 |
| 无法建立远程连接 | 端点类型和专用会话 | 更正账号和连接配置 |
现有的面向 Jsoup 的 Java 工作流涵盖了以解析器为中心的采集方式。本文在此基础上增加了共享的静态、本地浏览器和云浏览器约定,而不是替换那篇已发布的页面。
结论
当采集与抽取是分开的决策时,Java 网络爬取效果最佳。对你实际拥有的文档使用 Jsoup,在源站需要时获取渲染后的 HTML,并在导出前对同一记录约定进行验证。
补齐工具链和专用会话的先决条件,测试一个被允许的单页,然后在受限范围内添加针对源站的分页逻辑。
准备好将 Java 采集器连接到云浏览器了吗?
使用 Scrapeless 构建浏览器路径,并根据当前定价评估其资源需求。在 Telegram 上讨论 Java 工作流问题。
常见问题
问:Jsoup 能执行网站的 JavaScript 吗?
Jsoup 解析的是提供的 HTML,并不会执行页面 JavaScript。当所需记录仅在渲染之后才存在时,请使用浏览器采集路径。
问:Java 网络爬取可以对任何公开页面进行吗?
公开可见并不意味着对所有采集或使用行为都授予许可。采集前请审查源站条款、robots 排除规则、适用要求以及项目本身的授权情况。
问:这个 Java 工作流是否需要单独的代理?
静态或本地浏览器客户端需要满足源站要求的任何允许路由。云端路径使用已分配的 Agent Browser 配置;通过当前账号设置来配置其出站访问。
问:采集器遇到挑战页或拒绝访问文档时应该怎么做?
采集器应当拒收不合适的内容并保留采集原因。云浏览器连接不能替代对源内容的检查。
问:为什么同一个选择器在页面更新后可能不再返回任何行?
源站可能更改了标记、渲染行为或页面标识。在更改选择器或接受空结果之前,重新检查这些观测点以及最终 URL。
问:Java 试点应使用多大并发度?
使用有界的试点,并采用保守的单主机上限,例如本示例策略中的三个工作线程。扩展由源站的采集规则和观测到的资源使用情况来决定。
问:这个采集器可以在没有 AI Agent 的情况下运行,或者与 Selenium 一起使用吗?
该 Java 工作流可以作为确定性的代码运行,而无需 AI Agent。现有的 Selenium 项目可以保留其浏览器采集层,并将捕获的 HTML 输入同一套验证与导出约定。
在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。



