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

1.4 WebDriver选项配置

在1.3节的简单Web爬虫程序示例中,我们基本没有对WebDriver的选项进行设置。然而,实际上,对于基于Selenium框架开发的Web爬虫程序而言,WebDriver的选项配置非常重要。合理的选项配置不仅能优化数据采集性能,还能有效应对反爬虫机制。

本节主要介绍WebDriver的选项配置。在Selenium 4版本之前,WebDriver的选项配置是通过DesiredCapabilities类在会话中进行设置的。在Selenium 4版本中,相关选项需要在对应浏览器的options类中进行配置。例如,如果我们使用Chrome浏览器,则需要在ChromeOptions中进行配置。一些选项配置适用于所有浏览器,而有些选项配置则仅适用于特定浏览器。

1.4.1 浏览器通用选项配置

WebDriver的通用选项配置定义在CapabilityType接口中,具体包含如下选项:

     String BROWSER_NAME = "browserName";
     String PLATFORM_NAME = "platformName";
     String BROWSER_VERSION = "browserVersion";
     String ACCEPT_INSECURE_CERTS = "acceptInsecureCerts";
     String PAGE_LOAD_STRATEGY = "pageLoadStrategy";
     String PROXY = "proxy";
     String SET_WINDOW_RECT = "setWindowRect";
     String TIMEOUTS = "timeouts";
     String STRICT_FILE_INTERACTABILITY = "strictFileInteractability";
     String UNHANDLED_PROMPT_BEHAVIOUR = "unhandledPromptBehavior";

● browserName:浏览器名称,每个浏览器都有相应的默认值,例如Chrome浏览器的默认值是chrome。

● platformName:操作系统名称。

● acceptInsecureCerts:设置是否接受无效或过期的数字证书。

● pageLoadStrategy:页面的加载策略,通过读取document.readyState状态信息来实现。具有三种加载策略,分别是normal(默认值)、eager和none。这三种加载策略分别对应三种不同的readyState值。normal值意味着document.readyState==='complete',即网页资源已全部准备就绪。eager值表示document.readyState ==='interactive',即网页DOM树已加载完毕,但JavaScript脚本等其他资源尚未加载完毕。none表示document.readyState=== 'any',此时get()方法会立即返回。

● proxy:设置是否通过网络代理访问目标网站。

● setWindowRect:用于设置浏览器窗口的位置和大小。需要注意的是:在Selenium WebDriver Java Client中,该配置选项被拆分为setPosition和setSize。

● timeouts:会话超时时间,由多个不同的超时时间设置共同决定。例如script timeout(脚本执行超时时间)、page load timeout(页面加载超时时间)、implicit wait timeout(隐式等待时间)。

● unHandledPromptBehavior:提示弹窗自动处理办法。

1.4.2 Chrome浏览器特定选项配置

Chrome浏览器特定选项配置,顾名思义,指的是仅针对Chrome浏览器生效的选项配置。接下来介绍常见的选项配置。

● addArguments:这是Chrome浏览器启动时的命令行开关选项,例如--start-maximized(启动浏览器时最大化窗口)、--disable-popup-blocking(禁用弹出窗口阻止功能)等。

● setBinary:指定Chrome浏览器的路径。

● addExtensions:向浏览器中添加扩展程序。

● excludeSwitches: ChromeDriver在启动浏览器时会有一些默认设置。通过excludeSwitches配置项可以将这些默认设置去掉。

更多的Chrome浏览器配置选项及其说明,可以查阅ChromeDriver的官方文档。

1.4.3 Chrome DevTools Protocol

Chrome DevTools Protocol(CDP)是一套用于与基于Chromium内核的浏览器进行通信的API。它允许开发者通过发送命令和接收事件来与浏览器进行交互,以实现自动化测试、性能分析、调试等功能。

CDP在自动化测试、前端开发和爬虫程序开发等领域都发挥着重要作用。接下来介绍Selenium如何与CDP结合使用。

在Selenium框架中,提供了两个与Chrome DevTools进行交互的方法,分别是getDevTools方法和executeCdpCommand方法。

getDevTools方法返回一个DevTools对象,该对象负责管理会话、WebSocket连接和设置事件监听器。最重要的是,通过DevTools对象可以向浏览器发送Selenium内置的CDP命令。

在Maven工程中添加selenium-devtools-v**依赖包后,可以查看对应的内置CDP命令列表。注意选择与本地Chrome浏览器及JDK版本兼容的依赖版本。

     <dependency>
         <groupId>org.seleniumhq.selenium</groupId>
         <artifactId>selenium-devtools-v**</artifactId>
         <version>*.*.*</version>
     </dependency>

Chrome浏览器的开发者将Chrome DevTools的功能领域划分为大约50个,不同版本的浏览器可能会在支持的功能领域上有所差异。具体的功能领域划分可以通过官方文档链接进行查询。相应地,selenium-devtools依赖包中的内置CDP命令也是根据这些功能领域来命名的,如图1-11所示。

所有的内置CDP命令都会返回Command<V>对象,DevTools对象可以通过调用send(Command<V> command)方法与浏览器进行交互。通过executeCdpCommand方法,我们也可以发送CDP command。与DevTools.send方法不同的是,executeCdpCommand方法直接将原始的CDP command发送给浏览器。

图1-11 selenium-devtools功能领域划分

接下来,我们来看一个执行CDP命令的具体示例。

1.模拟设备模式

目前,很多网站会根据不同的设备类型提供不同的页面布局和内容,此时需要使用模拟设备模式的功能。下面的示例代码展示了如何将浏览器模拟成iPhone 12 Pro设备模式。

     ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);
     DevTools devTools = chromeDriver.getDevTools();
     devTools.createSession();
     devTools.send(Emulation.setDeviceMetricsOverride(375, 812, 1.0, true,
            Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(),
            Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()));
     chromeDriver.get("https://www.baidu.com");
     chromeDriver.quit();

执行上述代码后,我们会发现百度网站展示的是移动端样式。如果直接使用executeCdpCommand方法,也可以达到同样的效果。具体代码如下:

     ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);
     Map<String, Object> deviceMetrics = new HashMap<>();
     deviceMetrics.put("width", 375);
     deviceMetrics.put("height", 812);
     deviceMetrics.put("deviceScaleFactor", 5.0);
     deviceMetrics.put("mobile", true);
     String command = "Emulation.setDeviceMetricsOverride";
     chromeDriver.executeCdpCommand(command, deviceMetrics);
     chromeDriver.get("https://www.baidu.com");
     chromeDriver.quit();
2.模拟地理位置

很多网站会基于HTML5 Geolocation API获取用户的地理位置,以展示不同的内容,甚至限制用户对部分内容的访问。通过CDP命令Emulation.setGeolocationOverride,我们可以模拟(mock)浏览器所在的地理位置,从而访问我们希望看到的内容。接下来,我们将以访问百度地图为例,展示该CDP命令的具体效果。在正式开始实验之前,需要先检查一下浏览器的设置。通过下面的JavaScript代码,可以检查浏览器是否启用了Geolocation定位功能。

     navigator.geolocation.getCurrentPosition(
         function(position) {
           // 获取位置成功
           var latitude = position.coords.latitude;
           console.log(latitude);
           var longitude = position.coords.longitude;
           console.log(longitude);
         },
         function(error) {
           // 获取位置失败
           console.error("获取位置失败", error);
         }
     );

如果我们在开发者工具的控制台中看到经纬度坐标的输出,说明浏览器目前已支持并启动了Geolocation功能。通过下面的代码,可以将地理位置修改为美国加州地区:

     ChromeOptions chromeOptions = new ChromeOptions();
     ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);
     Map<String, Object> coordinates = new HashMap<>();
     coordinates.put("latitude", 34);               //加州纬度
     coordinates.put("longitude", -118);            //加州经度
     coordinates.put("accuracy", 100);
     String command = "Emulation.setGeolocationOverride";
     chromeDriver.executeCdpCommand(command, coordinates);
     chromeDriver.get("https://map.baidu.com");
     String js = "return navigator.geolocation.getCurrentPosition(function(position)
{console.log(position);}, function(error) {console.log(error);});";
     chromeDriver.executeScript(js);
     chromeDriver.quit();
3.设置UserAgent

目前,很多网站都设置了针对爬虫程序的识别和拦截措施,其中UserAgent Header是一个重要的识别字段。通过DevTools发送DCP命令给浏览器,我们可以将UserAgent设置为任何我们期望的名称。具体实现可以参考下面的代码:

     ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);
     DevTools devTools = chromeDriver.getDevTools();
     devTools.createSession();
     devTools.send(Network.setUserAgentOverride("userAgent test",Optional.empty(),
Optional.empty(),Optional.empty()));
     chromeDriver.get("https://www.baidu.com");

在上面的代码片段中,我们将UserAgent信息设置为"UserAgent test"。此时,如果我们使用抓包工具来监听浏览器发送的HTTP请求,就会发现User-Agent Header值已经被设置为"UserAgent test"。实际上,DevTools并不是给每一个HTTP请求都单独设置了自定义的UserAgent值,而是修改了浏览器内置的userAgent属性值。如果我们检查navigator.userAgent属性值,就会发现该值已被设置为"UserAgent test"。

4.设置额外HTTP请求头

对于爬虫程序来说,设置额外的HTTP请求头信息是十分必要的措施。当爬虫程序模拟用户行为时,适当的请求头信息可以使网站将爬虫程序视为合法用户。接下来,我们将讲解如何利用CDP命令为HTTP请求设置合适的请求头信息。

本实验的目标是编写一个简单的爬虫程序,在访问博客园网站主页时,将HTTP请求头中的Referer值设置为https://www.baidu.com。具体实现代码如下:

     ChromeOptions chromeOptions = new ChromeOptions();
     ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);
     DevTools devTools = chromeDriver.getDevTools();
     devTools.createSession();
     devTools.send(Network.setExtraHTTPHeaders(new Headers(new HashMap<String, Object>() {{
         put("Referer", "https://www.baidu.com");
     }})));
     chromeDriver.get("https://www.cnblogs.com/");
5.优先执行自定义JavaScript代码

爬虫程序和反爬虫程序之间一直在争夺优先执行的时机。优先执行的一方往往能够隐藏或获取更多的信息。如果我们使用Selenium框架启动浏览器,打印navigator.webdriver属性值,可以发现该值为true。但是,在日常正常使用的浏览器中打印该值时,通常显示为false或undefined。这一属性值经常被网站用来识别爬虫程序。那么,爬虫程序如何隐藏该属性值,从而让网站认为它是一个正常的用户呢?

实际上,只需在navigator的原型链上删除webdriver属性值或将它设置为false,即可实现这一目的。具体的JavaScript脚本为"delete Object.getPrototypeOf(navigator).webdriver"或"Object.getPrototypeOf (navigator).webdriver = false"。

需要注意的是,执行这些脚本的时机至关重要。如果我们修改webdriver属性值的脚本执行时机晚于网站检测webdriver属性值的脚本执行时机,那么脚本就失去了意义。因此,必须尽量提前脚本的执行时机。CDP命令Page.addScriptToEvaluateOnNewDocument为我们提供了一个理想的执行时机——在页面刚刚创建时。具体的实现代码如下:

     ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);
     String jsCode = "if (navigator.webdriver !== false && navigator.webdriver !== undefined)
{ delete Object.getPrototypeOf(navigator).webdriver} ";
     chromeDriver.executeCdpCommand("Page.addScriptToEvaluateOnNewDocument", new
HashMap<String, Object>() {{
         put("source", jsCode);
     }});
     chromeDriver.get("https://www.baidu.com");
     chromeDriver.executeScript("console.log(navigator.webdriver)");

执行上述代码后,我们可以在爬虫程序启动的浏览器控制台中看到undefined的日志信息。 esKTAePidMXzzr8INt+/lhHwOnQ73z1now/WZRiQByb1k8B/UjtM2isIlA6Hdznh

点击中间区域
呼出菜单
上一章
目录
下一章
×