返回博客
指南
拉卢卡·彭丘克2021年7月8日阅读时间:10分钟

《Java 网页抓取完全指南》

《Java 网页抓取完全指南》

了解网络搜索

什么是网页抓取?许多网站并未通过公开的API提供数据,因此网页抓取工具会直接从浏览器中提取数据。这就像一个人手动复制文本,但整个过程仅需一瞬间。

考虑到更完善的商业智能意味着更明智的决策,这一流程的价值远比乍看之下更为深远。随着网站产生的内容日益增多,完全依靠人工进行这项操作已不再明智。

你可能会想:“这些数据该怎么用呢?”。那么,让我们来看看几个网络爬虫真正能派上用场的应用场景:

  • 潜在客户开发:一个持续运营的企业需要通过开发潜在客户来寻找客户。
  • 价格情报:企业在制定产品定价和营销策略时,会参考竞争对手的定价情况。
  • 机器学习:为了让基于人工智能的解决方案正常运行,开发人员需要提供训练数据。

这篇文笔精湛的文章详细介绍了网络爬虫的价值,其中包含详细说明和更多应用案例。

尽管了解网页抓取的工作原理及其如何提升业务效率,但开发一个抓取工具并非易事。网站拥有多种方法来识别并阻止机器人访问其数据。

以下是一些示例:

  • 全自动公共图灵测试(CAPTCHA):这些逻辑问题对人类来说相对容易解决,但对爬虫来说却是一大难题。
  • IP封锁:如果网站检测到多个请求来自同一个IP地址,它可能会阻止您访问该网站,或者大幅降低您的访问速度。
  • 蜜罐:一种对人类不可见、但对机器人可见的隐形链接;一旦机器人中计,网站就会屏蔽其IP地址。
  • 地理限制:本网站可能会对某些内容实施地理限制。例如,当您查询其他地区的资讯(如机票价格)时,系统可能会向您提供该地区的特定信息。

应对所有这些障碍绝非易事。事实上,虽然开发一个还算不错的机器人并不太难,但要打造一个出色的网页爬虫却难如登天。因此,网页爬取API已成为过去十年中最热门的话题之一。

WebScrapingAPI 可从任何网站抓取 HTML 内容,并自动解决我之前提到的问题。此外,我们采用亚马逊网络服务(AWS),从而确保了速度和可扩展性。听起来是不是很合您的心意?立即开始免费试用 WebScrapingAPI,前 14 天内您将可进行 5000 次 API 调用。

了解网络

要理解万维网,你需要了解超文本传输协议(HTTP),该协议阐述了服务器与客户端之间的通信方式。一条消息中包含多项信息,用于描述客户端及其处理数据的方式,包括方法、HTTP 版本和头部。

网页爬虫使用 GET 方法进行 HTTP 请求,这意味着它们从服务器获取数据。一些高级选项还包括 POST 和 PUT 方法。如需了解详情,您可以在此处查看 HTTP 方法的详细列表

HTTP 头部中还包含有关请求和响应的若干其他详细信息。您可以查阅其完整列表,但与网页抓取相关的包括:

  • User-Agent:用于标识应用程序、操作系统、软件及其版本;网络爬虫依赖此标头,以使请求看起来更真实。
  • 主机:您访问的服务器域名。
  • 来源网址:包含用户访问的来源网站;因此,显示的内容可能会有所不同,这一点也必须予以考虑。
  • Cookie:用于保存有关请求和服务器(例如身份验证令牌)的机密信息。
  • Accept:确保服务器返回的响应采用特定类型(例如:text/plain、application/json 等)。

了解 Java

Java 是一种开源的面向对象编程语言,因此成为最受欢迎的编程语言之一。自我们首次接触 Java 以来,已过去近二十年,这种编程语言也变得越来越易于上手。

Java 的许多改动都旨在减少代码实现的依赖性。正因如此,许多开发者青睐这门语言,但它还具备其他优势:

  • 它是开源的;
  • 它提供了多种 API;
  • 它支持跨平台,提供了更大的灵活性;
  • 它拥有详尽的文档和可靠的社区支持。

制作自己的网络刮刀

现在我们可以开始讨论数据提取了。首先,我们需要一个提供有价值信息的网站。在本教程中,我们选择抓取这个分享意大利食谱的网页

步骤 1:设置环境

要构建我们的 Java 网页爬虫,首先需要确保已满足所有先决条件:

  • Java 8:尽管 Java 11 是当前最新的长期支持(LTS)版本,但 Java 8 仍是开发人员首选的生产环境标准。
  • Gradle:是一款功能丰富的灵活开源构建自动化工具,具备多种功能,包括依赖项管理(需要 Java 8 或更高版本);
  • 一款 Java 集成开发环境(IDE):在本指南中,我们将使用IntelliJ IDEA,因为它与 Gradle 的集成非常简单。
  • HtmlUnit:在抓取数据时可模拟浏览器事件(如点击和提交表单),并支持 JavaScript。

安装完成后,我们应验证是否正确遵循了官方指南。打开终端并运行以下命令:

> java -version
> gradle -v

以下内容应显示您计算机上已安装的 Java 和 Gradle 版本:

终端输出显示已安装的 Java 版本和 JVM 运行时详细信息
终端输出显示 Gradle 版本信息以及 JVM 和操作系统详细信息

如果没有弹出错误提示,那就没问题了。

现在,让我们创建一个项目,以便开始编写代码。幸运的是,JetBrains 提供了一份内容详尽的教程,指导如何入门使用 IntelliJ 和 Gradle,因此我们在配置过程中不会感到迷茫。

请确保在创建项目后,让 IDE 完成首次构建,因为这样会生成一个自动生成的文件树。

完成后,打开“build.gradle”文件,并在“dependencies”块中添加以下内容:

implementation('net.sourceforge.htmlunit:htmlunit:2.51.0')

这将把 HtmlUnit 安装到我们的项目中。别忘了点击右侧 Gradle 工具箱中的“重新加载”按钮,这样就能消除所有“未找到”的警告。

IDE Gradle 工具栏,带有“重新加载所有 Gradle 项目”选项

第 2 步:检查要搜索的页面

太棒了,继续吧!导航到你想抓取的页面,在页面任意位置右键点击,然后选择“检查元素”。开发者控制台会弹出,你应该能在其中看到该网站的 HTML 代码。

使用 Chrome 开发者工具打开 Food Network 食谱列表页面,以便检查页面元素

第 3 步:发送 HTTP 请求并抓取 HTML

现在,为了将该 HTML 获取到本地机器上,我们需要使用 HtmlUnit 发送一个 HTTP 请求,该请求将返回该文档。让我们回到 IDE 中,将这个想法转化为代码。

首先,编写使用 HtmlUnit 所需的导入语句:

import com.gargoylesoftware.htmlunit.*;
import com.gargoylesoftware.htmlunit.html.*;
import java.io.IOException;
import java.util.List;

然后,我们初始化一个 WebClient 对象,并向网站发送一个 HTTP 请求,该请求将返回一个 HtmlPage 对象。请务必记住,在收到响应后要关闭连接,因为进程会继续运行。

WebClient webClient = new WebClient(BrowserVersion.CHROME);

try {
   HtmlPage page = webClient.getPage("https://foodnetwork.co.uk/italian-family-dinners/");

   webClient.getCurrentWindow().getJobManager().removeAllJobs();
   webClient.close();
   recipesFile.close();

} catch (IOException e) {
   System.out.println("An error occurred: " + e);
}

值得一提的是,HtmlUnit 会在控制台抛出一大堆错误信息,让你觉得电脑都要炸了。不过别担心,其中 98% 的信息都可以放心忽略。

这些错误主要是由于 HtmlUnit 试图从网站服务器执行 JavaScript 代码所导致的。不过,其中一些可能是实际错误,表明您的代码存在问题,因此在运行程序时最好多加留意。

您可以通过配置 WebClient 的某些选项,跳过显示其中一部分无用的错误:

webClient.getOptions().setCssEnabled(false);
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
webClient.getOptions().setThrowExceptionOnScriptError(false);
webClient.getOptions().setPrintContentOnFailingStatusCode(false);

步骤 4:提取特定部分

因此,我们有一个 HTML 文档,但我们需要的是数据,这意味着我们应该将之前的响应解析为人类可读的信息。

让我们从小处着手,先提取网站的标题。我们可以借助内置方法getTitleText: 来实现这一点:

String title = page.getTitleText();
System.out.println("页面标题: " + title);

接下来,让我们从网站中提取所有链接。为此,我们可以使用内置的getAnchorsgetHrefAttribute方法,它们会从 HTML 中提取所有<a> 标签,然后获取href 属性的值:

List<HtmlAnchor> links = page.getAnchors();
for (HtmlAnchor link : links) {
   String href = link.getHrefAttribute();
   System.out.println("Link: " + href);
}

如您所见,HtmlUnit 提供了许多内置且一目了然的方法,省去了您花数小时阅读文档的麻烦。

我们来举一个更贴近实际的例子。我们需要从网站中提取所有食谱,更准确地说,是提取它们的标题和网址。

在浏览器检查器中,食谱卡链接的 HTML 锚点标签被高亮显示

如果你查看其中一张食谱卡片,就会发现我们所需的所有信息都包含在链接的属性中,这意味着我们只需查找具有“card-link”类的链接,并获取其属性即可。

List<?> anchors = page.getByXPath("//a[@class='card-link']");
for (int i = 0; i < anchors.size(); i++) {
   HtmlAnchor link = (HtmlAnchor) anchors.get(i);
   String recipeTitle = link.getAttribute("title").replace(',', ';');
   String recipeLink = link.getHrefAttribute();
}

这次,我们将使用一个XPath 表达式,在 HTML 文档中搜索任意深度的链接。然后,我们遍历结果列表,并提取每个链接的标题和 href 属性。

第 5 步:将数据导出为 CSV

当数据需要传递给另一个应用程序(在本例中是配方聚合器)时,这种提取方式会很有帮助。因此,我们需要将解析后的数据导出到外部文件中。

我们将创建一个 CSV 文件,因为它可以很容易地被另一个应用程序读取,并用 Excel 打开进行进一步处理。首先,再进行一次导入:

import java.io.FileWriter;

然后,我们初始化 FileWriter 对象,它将以“追加”模式创建 CSV 文件:

FileWriter recipesFile = new FileWriter("recipes.csv", true);
recipesFile.write("id,name,link\n");

创建完成后,我们还会写入 CSV 文件的第一行,该行将作为表格的表头。现在我们回到之前的循环中——即解析所有食谱卡片的那部分——并用以下代码行完成:

recipesFile.write(i + "," + recipeTitle + "," + recipeLink + "\n");

我们已经写完了文件,现在该关闭它了:

recipesFile.close();

酷,就是这样!现在,我们可以以一种干净、不可怕、易于转发的方式查看所有解析数据了。

从食谱列表页面提取的食谱名称和网址的电子表格

结论和替代方案

本教程到此结束。希望这篇文章对您有所帮助,并让您对网页抓取有了更深入的了解。

想必您也明白,这项技术的作用远不止于为食谱聚合平台提供支持。能否找到正确的数据并加以分析,从而创造新的机遇,全在于您。

不过,正如我在文章开头所说,网络爬虫面临着诸多挑战。对于开发者而言,利用自制的网络爬虫解决这些问题或许充满乐趣,因为这既是一次宝贵的学习经历,又充满乐趣。但如果你手头有项目需要完成,或许还是应该尽量避免由此产生的成本(时间、金钱和人力)。

使用专用的 API 来解决这些问题总是更容易一些。尽管存在 JavaScript 渲染、代理服务器、验证码等各种可能的阻碍,WebScrapingAPI 都能轻松克服这些障碍,并提供可定制的体验。此外,我们还提供免费试用选项,如果您还不太确定,不妨试一试?

关于作者
Raluca Penciuc,WebScrapingAPI 全栈开发工程师
Raluca Penciuc全栈开发工程师

Raluca Penciuc 是 WebScrapingAPI 的全栈开发工程师,主要负责开发爬虫、优化规避机制,并探索可靠的方法以降低在目标网站上的被检测概率。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网页数据。