购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.3 简单Web爬虫程序示例

本节开始基于Selenium框架编写一些简单的爬虫程序示例。在编写具体的功能示例之前,我们需要做一些准备工作。

(1)在IDE中创建一个Maven工程,并将它命名为java-webcrawler。

(2)在pom.xml中添加相关的Selenium Java Client Library依赖项。

     <dependency>
         <groupId>org.seleniumhq.selenium</groupId>
         <artifactId>selenium-api</artifactId>
         <version>${selenium.version}</version>
     </dependency>
     <dependency>
         <groupId>org.seleniumhq.selenium</groupId>
         <artifactId>selenium-chrome-driver</artifactId>
         <version>${selenium.version}</version>
     </dependency>
     <dependency>
         <groupId>org.seleniumhq.selenium</groupId>
         <artifactId>selenium-remote-driver</artifactId>
        <version>${selenium.version}</version>
     </dependency>
     <dependency>
         <groupId>org.seleniumhq.selenium</groupId>
         <artifactId>selenium-support</artifactId>
         <version>${selenium.version}</version>
     </dependency>

1.3.1 获取网页内容并打印

现在我们尝试通过爬虫程序访问百度,并打印页面内容。示例代码如下:

     System.setProperty(ChromeDriverService.CHROME_DRIVER_EXE_PROPERTY,
"/path/to/chromedriver");
     ChromeDriver webDriver = new ChromeDriver();
     webDriver.get("https://www.baidu.com");
     Thread.sleep(2000);
     System.out.println(webDriver.getPageSource());
     webDriver.quit();

上述代码的逻辑很简单,主要分为以下3个步骤:

步骤01 将chromedriver可执行文件的路径绑定到系统变量webdriver.chrome.driver,以便在创建ChromeDriver对象时使用,当然,也可以使用WebDriverManager自动安装和管理WebDriver。

步骤02 创建ChromeDriver对象并访问百度网页地址。

步骤03 获取百度网页的HTML内容并打印。

1.3.2 利用XPath获取指定元素

接下来,我们尝试利用表达式来获取指定的页面元素。在本小节的示例中,使用的是XPath表达式。XPath全称是XML Path,用于查找XML文档中的元素或节点。在网络爬虫中,它通常用于查找Web元素。关于XPath表达式的具体语法和使用技巧,我们将在后续章节中单独讲解。现在,我们先来看一下如何使用Chrome浏览器获取目标元素的XPath表达式。

(1)打开Chrome浏览器的开发者工具。

(2)在开发者工具的Tab栏中切换至Elements选项卡,在该页面中找到自己感兴趣的目标元素,如图1-6所示。

图1-6 在开发者工具中选择目标元素

(3)右击该元素,并且在弹出的快捷菜单中依次选择Copy→Copy XPath命令,如图1-7所示。这样,我们就可以得到相关元素的XPath表达式了。

图1-7 复制XPath表达式

得到Xpath表达式之后,就可以开始编写相关代码了。

     ChromeDriver webDriver = new ChromeDriver();
     webDriver.get("https://www.bing.com");
     Thread.sleep(2000);
     WebElement webElement = webDriver.findElement(By.xpath("//*[@id=\"sb_form_q\"]"));
     webElement.sendKeys("Java网络爬虫精解与实践");
     webDriver.quit();

上面的例子通过Selenium WebDriver去访问www.bing.com网站,使用XPath表达式定位了bing.com页面上的搜索输入框元素,并在搜索框中输入了搜索词“Java网络爬虫精解与实践”。

1.3.3 单击搜索按钮

在该示例中,我们在必应搜索引擎中输入搜索词“Java网络爬虫精解与实践”,然后单击搜索按钮来完成页面搜索操作。具体代码示例如下:

     ChromeDriver webDriver = new ChromeDriver();
     webDriver.get("https://www.bing.com");
     Thread.sleep(2000);
     WebElement input = webDriver.findElement(By.xpath("//*[@id=\"sb_form_q\"]"));
     input.sendKeys("Java网络爬虫精解与实践");
     WebElement searchBtn = webDriver.findElement(By.id("sb_form_go"));
     searchBtn.submit();
     webDriver.quit();

在这个例子中,我们利用Selenium WebDriver获取了必应搜索引擎的搜索输入框和搜索按钮,并实现了输入框内容的填充和搜索按钮的模拟单击功能。

Selenium WebDriver主要通过元素定位器来操作网页上的各个元素,只有定位到元素的位置,才能进一步对其进行操作。

我们可以通过findElement(By.locator())方法来查找页面上的元素。如果页面上存在定位器指定的元素,该方法会返回一个WebElement对象。

Selenium WebDriver共支持8种元素定位器。除了前面使用的Xpath定位器和ID定位器外,还可以使用Name、TagName、CSS等多种元素定位器。其他类型的元素定位器的使用方式将在后面的章节中详细介绍。

1.3.4 获取iframe元素中的数据

iframe元素是可以将一个页面的内容嵌入到另一个页面的容器中。在数据采集过程中,如果需要定位的元素在iframe元素中,那么在使用Selenium WebDriver定位和采集元素数据之前,我们需要先将WebDriver切换到对应的iframe容器中,才能正确采集数据。首先,我们来看一个使用iframe容器的网页实例(https://chercher.tech/practice/frames)。该网页的iframe容器嵌套关系如图1-8所示。

图1-8 iframe容器嵌套关系

在本次实验中,我们将使用爬虫技术来选中frame3容器中的复选框(checkbox)。具体实现代码如下:

     String url = "https://chercher.tech/practice/frames";
     WebDriver = new ChromeDriver();
     webDriver.get(url);
     Thread.sleep(2000);
     WebElement iframe = webDriver.findElement(By.id("frame1"));
     webDriver = webDriver.switchTo().frame(iframe);
     iframe = webDriver.findElement(By.id("frame3"));
     webDriver = webDriver.switchTo().frame(iframe);
     WebElement checkBox = webDriver.findElement(By.id("a"));
     checkBox.click();
     webDriver.quit();

执行结果如图1-9所示。

图1-9 frame3容器复选框被选中的结果

1.3.5 使用更加优雅的等待方式

目前,大部分网页内容都是通过Ajax或JavaScript异步加载的。这样,当用户在浏览器中打开一个网页时,用户想要交互的网页元素可能会在不同的时间间隔内加载出来。在之前的例子中,我们使用了Thread.sleep()这种固定时间的等待方式。然而,这种方式有个弊端:网页加载的速度受到网络质量、服务器状态等多种因素的影响,网页内容的加载速度很难准确评估。如果等待时间较短,用户想要的网页元素可能还没有加载完成;如果等待时间过长,则会降低数据采集的效率。在Selenium WebDriver中,等待方式可以分为显式等待和隐式等待两种,具体情况如图1-10所示。

图1-10 Selenium等待方式

隐式等待(ImplicitWait)通常用于全局的等待设置。设置成功后,在接下来的Selenium命令执行过程中,如果无法立即获取到目标元素,Selenium将会等待一段时间后再抛出NoSuchElementException异常。下面来看一个隐式等待的例子:

     ChromeDriver webDriver = new ChromeDriver();
     webDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));
     webDriver.get("https://www.bing.com");
     WebElement searchBtn = webDriver.findElement(By.id("search-icon"));
     //注:ID为search-icon的页面元素并不存在,在等待2秒钟后程序将会抛出NoSuchElementException

相对于隐式等待,显式等待可以设置更加合理的等待时间。Selenium框架提供了两种显式等待方式,分别是WebDriverWait和FluentWait。接下来,我们来看一个WebDriverWait的应用实例。

     String url = "http://www.bing.com";
     ChromeDriver webDriver = new ChromeDriver();
     WebDriverWait wait = new WebDriverWait(webDriver, Duration.ofSeconds(10));
     webDriver.get(url);
     WebElement element =
wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[@id=\"sb-form_q\"
]")));
     element.sendKeys("Java网络爬虫精解与实践");
     element.click();
     webDriver.quit();

在上面的例子中,我们使用WebDriverWait代替了之前的Thread.sleep方式来等待元素加载完成。在使用WebDriverWait时,我们创建了一个WebDriverWait对象,并设置了期望等待的最大时间,WebDriverWait对象的until方法会不断轮询期望的条件是否完成,轮询时间间隔是500毫秒。假设某个网站中的目标元素期望的最长加载时间是4秒,但由于某段时间内该网站的响应速度较快,在等待1秒后目标元素就已加载完成。在这种情况下,使用WebDriverWait显式等待方式可以节约出3秒的时间。

WebDriverWait继承自FluentWait,且两者都实现了Wait接口。因此,WebDriverWait和FluentWait在功能上基本相同。例如,它们都支持自定义轮询时间间隔,并允许重写apply方法。相较于FluentWait, WebDriverWait提供了更简便的显式等待操作方法。

1.3.6 实现屏幕截图

在采集数据过程中,有时需要对网页内容进行截图保存,以便后续进行数据校验。本节将提供一个基于Selenium屏幕截图的简单示例程序。

     WebDriver driver = new ChromeDriver();
     driver.get("https://top.baidu.com");
     File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
     FileUtils.copyFile(srcFile, new File("$PIC_PATH/top-baidu-page.png"));
     WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
     wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[@class=\"the
me-hot category-item_1fzJW\"]")));
     WebElement topSearchElement = driver.findElement(By.xpath("//*[@class=\"theme-hot
category-item_1fzJW\"]"));
     srcFile = topSearchElement.getScreenshotAs(OutputType.FILE);
     FileUtils.copyFile(srcFile, new File("$PIC_PATH/top-baidu-element.png"));

在上面的例子中,我们提供了两种网页内容截图方式:对整个网页内容进行截图和针对特定元素进行截图。然而,网页内容的截图仅覆盖当前视窗内容的截图,而不是整个网页。如果我们期望获取整个网页的截图,可以通过对网页内容进行滚动操作来逐步截取当前屏幕的内容,最后将所有截图拼接在一起。此外,还有一个更简单的实现方式,即使用Ashot这个第三方开源库来完成这项任务。Ashot是由Yandex公司开发的Java组件,它的主要功能之一就是获取整个网页内容的屏幕快照。

1.3.7 执行JavaScript脚本

Selenium的RemoteWebDriver类实现了JavaScriptExecutor接口,该接口的主要功能是在浏览器中执行JavaScript代码,从而使得WebDriver可以实现更高级的浏览器页面交互操作。

虽然Selenium WebDriver本身已经提供了一些接口来操作Web元素,例如发送数据、单击按钮等。不过,如前所述,有些更加高级的操作需要JavaScriptExecutor的帮助,例如滚动页面操作、获取浏览器窗口innerHeight值等。

JavaScriptExecutor接口主要提供了两个用于执行JavaScript脚本的方法。

● Object executeScript(String script, Object…args):在当前页面执行JavaScript脚本,并支持返回结果。

● Object executeAsyncScript(String script, Object…args):执行JavaScript异步脚本,并支持返回结果。

接下来,我们来看一些executeScript方法的使用示例。 DQYbzzd3xbu7nIUKTslyiEmbXZo4kyZBeU35H7wjuOkpQjCq5z3nnF9LMwNuTxUp

     ChromeDriver webDriver = new ChromeDriver();
     webDriver.get("https://top.baidu.com");
     // 在控制台输出信息
     webDriver.executeScript("console.log('Hello, This is message from WebDriver!')");
     // 获取浏览器窗口的视口高度和宽度
     int innerHeight = (int)webDriver.executeScript("return window.innerHeight;");
     int innerWidth = (int)webDriver.executeScript("return window.innerWidth;");
     // 垂直向下滚动页面500像素
     webDriver.executeScript("window.scrollBy(0,500)");
点击中间区域
呼出菜单
上一章
目录
下一章
×