in

使用Java的Chrome 无头模式简介

使用Java的Chrome 无头模式简介

用Java进行基本的网络爬取的两种不同方法:HtmlUnit用于爬取基本网站,PhantomJS用于爬取大量使用JavaScript的动态网站。

两者都是巨大的工具,PhantomJS在很长一段时间内碰巧成为该市场的领导者是有原因的。尽管如此,偶尔也会有一些问题,无论是性能还是对网络标准的支持。然后,在2017年,这个领域出现了一个真正的游戏规则改变者,当时谷歌和Mozilla都开始在各自的浏览器中原生支持一个叫做无头模式的功能。


欢迎使用Chrome浏览器的脚本

事实证明,Chrome的无头模式(Chrome Headless)是非常实用和强大的。它允许你拥有与HtmlUnit和PhantomJS相同的控制和自动化,但这次是在你每天都在使用的浏览器引擎的背景下,而且你现在很可能正在使用它来阅读这篇文章。

没有更多的问题,只有部分支持的CSS功能,可能稍微不兼容的JavaScript代码,或过于复杂的HTML页面的性能瓶颈。你最喜欢的浏览器引擎就在你的指尖,只需要几个Java命令就可以了。

好了,我们现在已经对理论惊叹了很久,是时候进入编码阶段了。


让我们来看看

我们将从简单的东西开始,在我们的第一个例子中尝试获取Hacker News的截图。在我们可以直接进入编码并让浏览器为我们服务之前,我们首先需要确保我们有合适的环境和所有必要的工具。

至于浏览器,我们要使用的库(破坏者,Selenium)实际上支持许多不同的浏览器,虽然在这里的例子中,我们将专注于Chrome,但应该很容易就能为不同的浏览器引擎切换驱动程序。

因此,不再赘述,这里列出了你运行我们的代码示例所需要的软件包。

准备条件

设置

我们说的是屏幕截图,对吗?但为了使事情变得更有趣,这将是一个有转折的截图。这将是一个 “认证 “的截图,所以我们将需要有一个用户名和密码,并提供给登录对话。

首先,我们需要获得一个WebDriver对象的实例(因为我们使用的是Chrome浏览器,所以在实现上将是ChromeDriver),以便能够首先访问浏览器。我们可以通过在webdriver.chrome.driver系统属性中指定ChromeDriver二进制文件的路径,并通过几个选项实例化一个ChromeDriver类来相对容易地实现这一目标。

// Initialise ChromeDriver
String chromeDriverPath = "/Path/To/Chromedriver" ;
System.setProperty("webdriver.chrome.driver", chromeDriverPath);

ChromeOptions options = new ChromeOptions();
options.addArguments("--headless", "--window-size=1920,1200","--ignore-certificate-errors");

WebDriver driver = new ChromeDriver(options);

至于Chrome,ChromeDriver应该会自动找到它的安装目录,但如果你喜欢自定义路径,你可以通过ChromeOptions.setBinary来设置它。

options.setBinary("/Path/to/specific/version/of/Google Chrome");

所有ChromeDriver选项的完整列表可以在这里找到。


截    图

现在,我们已经准备好获得我们的屏幕截图。我们所需要做的是

  • 获取登录页面
  • 用正确的值填入用户名和密码字段
  • 点击登录按钮
  • 检查认证是否成功
  • 获得我们当之无愧的截图 –PROFIT

由于我们在以前的文章中已经介绍了这些步骤,我们将简单地提供完整的代码。

public class ChromeHeadlessTest {
	private static String userName = "";
	private static String password = "";

	public static void main(String[] args) throws IOException {
		String chromeDriverPath = "/your/chromedriver/path";
		System.setProperty("webdriver.chrome.driver", chromeDriverPath);

		ChromeOptions options = new ChromeOptions();
		options.addArguments("--headless", "--window-size=1920,1200","--ignore-certificate-errors", "--silent");
		WebDriver driver = new ChromeDriver(options);

		// Get the login page
		driver.get("https://news.ycombinator.com/login?goto=news");

		// Search for username / password input and fill the inputs
		driver.findElement(By.xpath("//input[@name='acct']")).sendKeys(userName);
		driver.findElement(By.xpath("//input[@type='password']")).sendKeys(password);

		// Locate the login button and click on it
		driver.findElement(By.xpath("//input[@value='login']")).click();

		if (driver.getCurrentUrl().equals("https://news.ycombinator.com/login")) {
			System.out.println("Incorrect credentials");
			driver.quit();
			System.exit(1);
		}

		System.out.println("Successfully logged in");

		// Take a screenshot of the current page
		File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
		FileUtils.copyFile(screenshot, new File("screenshot.png"));

		// Logout
		driver.findElement(By.id("logout")).click();
		driver.quit();
	}
}

一旦你编译并运行这段代码,你的工作目录中应该有一个名为screenshot.png的文件,其中有一张Hacker News主页的华丽截图,包括你的用户名和一个注销链接。


还有什么?

从你的浏览器中获得实时屏幕截图是很好的,但你知道什么更棒吗?与网站互动。

关于无限滚动,有很多不同的意见。虽然有人喜欢,有人不喜欢,但它是当今网页设计中的一个共同主题,而且由于其动态内容的性质和JavaScript的大量参与,在网页爬取中处理起来总是有点棘手。而这正是一个成熟的浏览器引擎将大显身手的领域,因为它将允许你以完全自然的方式处理页面,而不需要任何变通。让我们来看看,好吗?

无限滚动

外面有很多使用无限滚动的服务,但由于我们这次真的想把重点放在滚动部分,而不是那么多的处理认证,我们遵循任何半成品电视厨师的传统,使用ScrapingBee已经准备好的滚动例子,https://demo.scrapingbee.com/infinite_scroll.html

一句话,那个页面真的是无限滚动的定义,只要你滚动到页面的底部,它就会追加更多的内容。

我们现在如何处理这个问题?

首先,我们设置了与前一个例子中使用的相同环境。这包括设置对ChromeDriver的引用,初始化WebDriver实例,以及用WebDriver.load()加载页面。到目前为止,还不错,没有什么新东西。

第一件新事是,我们要使用新引入的getCurrentHeight()方法来确定页面的初始高度。

private static long getCurrentHeight(JavascriptExecutor js)
{
	Object obj = js.executeScript("return document.body.scrollHeight");
	if (!(obj instanceof Long)) throw new RuntimeException("scrollHeight did not return a long");

	return (long)obj;
}

在这里,我们基本上是在页面的上下文中运行返回document.body.scrollHeight的JavaScript片段,这为我们提供了页面的当前高度。我们把这个信息保存在prevHeight中,以后用它来检查页面的高度是否有变化。接下来是无穷大!♾

我们现在运行一个无限循环(好吧,就像所有这样的循环一样,我们需要一个相当可靠的退出条款),我们首先用XPath表达式(//div[@class=’box’])[last()]获取我们页面上的最后一个内容元素,然后用WebElement.getText()获取其内容。这将作为我们的内容ID。

WebElement lastElement = driver.findElement(By.xpath("(//div[@class='box'])[last()]"));

int id = Integer.parseInt(lastElement.getText());

一旦我们有了我们的内容ID,我们就可以滚动到底部,并希望我们的网站能够加载更多的内容(在我们的例子中,它总是会的)。要做到这一点,我们–再次–运行一个小的JavaScript片段,使用window.scrollTo(),并等待几毫秒。现在我们将检查我们是否已经满足了退出条件,如果没有,继续循环。

js.executeScript("window.scrollTo(0, document.body.scrollHeight);");
Thread.sleep(500);

long height = getCurrentHeight(js);

if (id > 100 || height == prevHeight || height > 30000) break;

prevHeight = height;

我们的退出条件,对吧!?尽管无限滚动真的受到很多人的喜爱,但我们真的想避免从字面上进入一个无限的循环。为了做到这一点,我们假设一旦我们有了至少100个元素或者页面至少有30000像素的高度,我们就会很高兴。当然,一旦高度保持不变,我们也就没有什么可加载的了。

一旦我们退出了我们的循环,我们只需要收集所有的内容就可以了。

List boxes = driver.findElements(By.xpath("//div[@class='box']"));
for (WebElement box : boxes) System.out.println(box.getText());

现在是压轴大戏,完整的代码。

public class InfiniteScrolling
{
	public static void main(String[] args) throws IOException, Exception {
		String chromeDriverPath = "/your/chromedriver/path";
		System.setProperty("webdriver.chrome.driver", chromeDriverPath);

		ChromeOptions options = new ChromeOptions();
		options.addArguments("--headless", "--window-size=1920,1200","--ignore-certificate-errors", "--silent");
		WebDriver driver = new ChromeDriver(options);

		JavascriptExecutor js = (JavascriptExecutor)driver;

		// Load our page
		driver.get("https://demo.scrapingbee.com/infinite_scroll.html");

        // Determine the initial height of the page
		long prevHeight = getCurrentHeight(js);

		while (true)
		{
			// Get the last div element of class box
			WebElement lastElement = driver.findElement(By.xpath("(//div[@class='box'])[last()]"));

			// Consider the element's text content the ID
			int id = Integer.parseInt(lastElement.getText());

			// Scroll to the bottom and wait for 500 milliseconds
			js.executeScript("window.scrollTo(0, document.body.scrollHeight);");
			Thread.sleep(500);

			// Get the current height of the page
			long height = getCurrentHeight(js);

			// Stop if we reached at least 100 elements, the bottom of the page, or a height of 30,000 pixels
			if (id > 100 || height == prevHeight || height > 30000) break;

			prevHeight = height;
		}

		// Get all div elements of class "box" and print their IDs
		List boxes = driver.findElements(By.xpath("//div[@class='box']"));
		for (WebElement box : boxes) System.out.println(box.getText());

		driver.quit();
	}

	private static long getCurrentHeight(JavascriptExecutor js)
	{
		Object obj = js.executeScript("return document.body.scrollHeight");
		if (!(obj instanceof Long)) throw new RuntimeException("scrollHeight did not return a long");

		return (long)obj;
	}
}

禁用图像

虽然图片看起来很可爱,并能真正改善整体的用户体验,但对于网络爬取的目的来说,它们往往是有些多余的。特别是在适当的浏览器引擎的背景下,它们将一直被下载,然而,它们会影响性能和网络流量,特别是在更大的范围内。

一个相对容易的解决方法是指示浏览器不要下载图片,只关注实际的HTML内容,以及JavaScript和CSS。而这正是我们现在要做的事情。

让我们以一个图像非常丰富的网站为例,即Pinterest,把我们原来的截图代码改编一下,只加载pinterest.com的主页并进行截图。

String chromeDriverPath = "/your/chromedriver/path";
System.setProperty("webdriver.chrome.driver", chromeDriverPath);

ChromeOptions options = new ChromeOptions();
options.addArguments("--headless", "--window-size=1920,1200","--ignore-certificate-errors", "--silent");
WebDriver driver = new ChromeDriver(options);

// Load our page
driver.get("https://www.pinterest.com/");

// Take a screenshot of the current page
File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(screenshot, new File("screenshot.png"));
		
driver.quit();

一旦我们编译并运行该代码,我们将得到一个名为screenshot.png的文件。现在,如果我们想运行同样的请求,但让浏览器忽略图像,我们需要做的就是在ChromeOptions列表中添加blink-settings=imagesEnabled参数并将其设置为false。

options.addArguments("--headless", "--window-size=1920,1200","--ignore-certificate-errors", "--silent", "--blink-settings=imagesEnabled=false");
[文中代码源自Scrapingbee]

如果我们现在运行这段代码,我们将得到同样的截图,但没有图像。

即使,我们在这里专门拍了一张截图,如果你的项目依赖于精确的截图,这显然会不太有用,但如果你主要是追求文本数据,它可以为你节省相当多的流量成本。


总    结

正如你所看到的,Chrome headless真的很容易使用,它与PhantomJS几乎没有任何区别,因为我们使用Selenium来运行它。代码可以在这个Github资源库中找到。

blank

Written by 爬取 大师

阿里P12级别选手,能够突破各种反爬, 全能的爬取大师,擅长百万级的数据抓取!没有不能爬,只有你不敢想,有爬取项目可以联系我邮箱 [email protected] (带需求和预算哈, 不然多半不回复)