上面的章节中,我们介绍了几个目前比较活跃的Java爬虫框架。在今天的章节中,我们会参考开源爬虫框架,开发我们自己的Java爬虫软件。

首先,我们下载本章节要使用到的源代码,本章节主要提供了基于HTTPClient和WebDriver两种方式的数据抓取器。在运行该库之前,我们还需要准备一下我们的开发环境。

首先,我要给大家介绍一下Selenium webdriver这个开源组件,Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla FirefoxSafariGoogle ChromeOpera,Edge等。Selenium webdriver是编程语言和浏览器之间的通信工具,它的工作流程如下图所示。

我们这里选择的是chrome浏览器,在正式开始编写代码之前,我们需要安装两个重要的程序,一个是chromedriver,一个是chrome。

chrome浏览器的下载地址:https://chrome.en.softonic.com/

chromedriver的下载地址:http://chromedriver.storage.googleapis.com/index.html

注意:在安装这两个软件的时候,它们的版本需要对应起来才能正常工作。

下面的UML类图是我们本章节所使用程序的主要类结构。

例1:创建HTTP采集器和WebDriver采集器,访问URL,打印网页内容

Page page = new Page("http://www.oracle.com/");
//创建基于httpclient的网页内容采集器
IHttpFetcher httpFetcher = new DefaultHttpFetcher();
httpFetcher.fetch(page);
String htmlContentStr = new String(page.getContentData());
System.out.println(htmlContentStr);
//创建基于webdriver的网页内容采集器
httpFetcher = new WebDriverHttpFetcher();
httpFetcher.fetch(page);
htmlContentStr = new String(page.getContentData());
System.out.println(htmlContentStr);

上面的例子中分别创建了两个HttpFetcher,一个是基于httpclient的网页内容采集器DefaultHttpFetcher,另一个是基于webdriver的网页内容采集器WebDriverHttpFetcher。WebDriverHttpFetcher相对于DefaultHttpFetcher的主要优势在于WebDriverHttpFetcher可以捕获异步加载内容的网页详情。

例2:利用XPath表达式获取网页指定元素

XPath(XML Path) 是一个表达式,用于查找XML文档中的元素或节点。在网络爬虫中,它通常用于查找Web元素。对于XPath表达式的具体语法,可以自行查阅

Page page = new Page("http://www.bing.com");
IHttpFetcher httpFetcher = new WebDriverHttpFetcher();
httpFetcher.fetch(page);
String htmlContentStr = new String(page.getContentData());
Document document = Jsoup.parse(htmlContentStr);
Element element = XSoupUtil.getCombineElement("//*[@id=\"sb_form_q\"]", document, "http://www.bing.com");

上面的例子创建了WebDriverHttpFetcher来访问bing.com,使用XPath表达式定位了bing.com页面上面的搜索输入框元素。

例3:模拟输入框内容填写和按钮点击

Page page = new Page("http://www.bing.com");
WebDriverHttpFetcher httpFetcher = new WebDriverHttpFetcher();
WebDriver webDriver = httpFetcher.getWebDriver();
webDriver.get(page.getUrl());
Thread.sleep(2000);
WebElement element = webDriver.findElement(By.xpath("//*[@id=\"sb_form_q\"]"));
element.sendKeys("网络爬虫");
element = webDriver.findElement(By.xpath("//*[@id=\"search_icon\"]"));
element.click();
Thread.sleep(2000);
System.out.println(webDriver.getPageSource());

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

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

Selenium WebDriver主要使用“findElement(By.locator())”方法来查找页面上面的元素。如果页面上面存在定位器指定的元素,该方法会返回一个WebElement对象。

Selenium WebDriver共支持8种元素定位器,除了我们上面使用的Xpath定位器外,还可以使用ID,Name,TagName,CSS等多种元素选择定位器。

例4:采集IFrame元素中的数据

IFrame是可以将一个页面的内容嵌入到另一个页面中的容器,在使用Selenium WebDriver定位和采集元素数据之前,我们需要先将WebDriver切换到对应的IFrame容器下才可以。首先我们看一个使用IFrame容器的网页实例。该网页的IFrame容器嵌套关系如下图所示:

在本例中,我们将使用爬虫技术来选中frame3容器中的checkbox复选框。

Page page = new Page("https://chercher.tech/practice/frames");
WebDriverHttpFetcher httpFetcher = new WebDriverHttpFetcher();
WebDriver webDriver = httpFetcher.getWebDriver();
webDriver.get(page.getUrl());
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();

例5:使用更加优雅的等待方式

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

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

Page page = new Page("http://www.bing.com");
WebDriverHttpFetcher httpFetcher = new WebDriverHttpFetcher();
ChromeDriver webDriver = (ChromeDriver)httpFetcher.getWebDriver();
webDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));
webDriver.get(page.getUrl());
WebElement element = webDriver.findElement(By.xpath("//*[@id=\"sb_form_q\"]"));
element.sendKeys("网络爬虫");
element = webDriver.findElement(By.xpath("//*[@id=\"search_icon\"]"));
element.click();
webDriver.quit();

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

Page page = new Page("http://www.bing.com");
WebDriverHttpFetcher httpFetcher = new WebDriverHttpFetcher();
ChromeDriver webDriver = (ChromeDriver)httpFetcher.getWebDriver();
WebDriverWait wait = new WebDriverWait(webDriver, Duration.ofSeconds(10));
webDriver.get(page.getUrl());
WebElement element = wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[@id=\"sb_form_q\"]")));
element.sendKeys("网络爬虫");
element = wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//*[@id=\"search_icon\"]")));
element.click();
webDriver.quit();

在使用WebDriverWait的时候我们需要手动创建一个WebDriverWait对象,并且设置我们需要等待的时间,WebDriverWait对象的until会帮助我们不断轮询我们期望的条件是否完成,轮询时间间隔是500ms。WebDriverWait对象实际上继承于FluentWait对象。如果我们直接使用Fluent对象,则可以有更加灵活的等待策略和轮询时间间隔。可以假设这样一个场景,我们希望获取到页面中的某个元素,但是这个元素并不是必须的,即使最终这个元素没有加载成功也不影响我们接下来的处理流程。这个时候,FluentWait会是一个不错的选择。

例6:配置WebDriver参数

一般来讲,当我们在使用Selenium WebDriver采集数据的时候,我们需要对WebDriver进行一些设置,例如:隐身模式,关闭弹窗,忽略SSL证书错误等等。在这个例子中,我们来看一看常用的ChromeDriver配置项,更多的配置信息可以参考配置项列表

ArrayList<String> arguments = Lists.newArrayList(
        "--allow-running-insecure-content", 
        "--allow-insecure-localhost", //忽略SSL/TLS errors
        "--disable-gpu", // 关闭GPU硬件加速
        "--headless", //开启无头浏览器模式
        "--window-size=1920,1050", //设置浏览器窗口大小
        "--disable-blink-features=AutomationControlled",
        "--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36", //自定义user-agent名称
        "--cache-control=no-cache");
ChromeOptions options = new ChromeOptions();
options.addArguments(arguments);

例7:Selenium WebDriver设置自定义请求头(custom headers)

很多的时候,我们在采集数据的时候需要设置一些自定义的HTTP请求头,例如:我们想指定对某个网站链接访问是从什么地方跳转过来的,我们就需要设置referrer header。但很遗憾的是Selenium WebDriver并没有给我们提供设置HTTP请求头的接口。

今天就给大家介绍一款可以设置自定义请求头的利器BrowserMob Proxy(简称BMP)。它是一款开源软件,它不仅可以用来监控网络通信而且可以控制网络请求和响应的内容,它还可以将页面内容加载的性能数据导出到HAR文件中以便开发者可以进一步分析优化自己的网站响应速度。CMP不仅可以作为一个独立的代理服务器工作,而且可以和Selenium框架结合在一起使用。因为CMP是基于Java语言开发的,所以它可以很方便地嵌入到我们的Java程序中。CMP的下载地址是:https://github.com/lightbody/browsermob-proxy

在本例中,我们主要示范下如何使用BMP来设置HTTP请求头,具体示例代码如下:

Page page = new Page("http://www.cnblogs.com/kaiblog/");
Map<String, String> headers = new HashMap<String, String>();
headers.put("Referer", "http://www.baidu.com/");
// start the proxy
BrowserMobProxy proxy = new BrowserMobProxyServer();
proxy.addHeaders(headers);
proxy.setMitmDisabled(true);
proxy.start(0);
// get the Selenium proxy object
Proxy seleniumProxy = ClientUtil.createSeleniumProxy(proxy);
// configure it to webdriver
WebDriver webDriver = WebDriverFactory.createWebDriver(seleniumProxy);
webDriver.get(page.getUrl());
webDriver.quit();
proxy.stop();

通过抓取网络数据包我们可以看到Referer请求头已经设置生效了。

总结

在本章节中,我们分别基于HttpClient和Selenium框架对网页内容进行了采集,基于Selenium框架的采集器相对于基于HttpClient的采集器在采集动态网页数据上面具有不小的优势,也避免了对加密Javascript的逆向分析。但是这并意味着使用Selenium WebDriver去采集网页内容就不会被目标网站发现了,使用Selenium WebDriver模拟驱动的浏览器与真实的浏览器在指纹特征上面还是有诸多不同的,在下面的章节中,我们会进一步详细介绍。

Java爬虫实战系列2——动手写爬虫初体验的更多相关文章

  1. scrapy爬虫学习系列一:scrapy爬虫环境的准备

    系列文章列表: scrapy爬虫学习系列一:scrapy爬虫环境的准备:      http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_python_00 ...

  2. cucumber java从入门到精通(1)初体验

    cucumber java从入门到精通(1)初体验 cucumber在ruby环境下表现让人惊叹,作为BDD框架的先驱,cucumber后来被移植到了多平台,有cucumber-js以及我们今天要介绍 ...

  3. Java并发编程:自己动手写一把可重入锁

    关于线程安全的例子,我前面的文章Java并发编程:线程安全和ThreadLocal里面提到了,简而言之就是多个线程在同时访问或修改公共资源的时候,由于不同线程抢占公共资源而导致的结果不确定性,就是在并 ...

  4. 【Python爬虫实战--3】html写正则表达式

    以下是要爬虫的html内容: <div class="article block untagged mb15" id='qiushi_tag_113452216'> & ...

  5. 8天掌握EF的Code First开发系列之动手写第一个Code First应用

    返回<8天掌握EF的Code First开发>总目录 本篇目录 创建控制台项目 根据.Net中的类来创建数据库 简单的CRUD操作 数据库模式更改介绍 本章小结 自我测试 上一篇<8 ...

  6. 记一次python爬虫实战,豆瓣电影Top250爬虫

    import requests from bs4 import BeautifulSoup import re import traceback def GetHtmlText(url): for i ...

  7. Spring Security 实战干货:OAuth2第三方授权初体验

    1. 前言 Spring Security实战干货系列 现在很多项目都有第三方登录或者第三方授权的需求,而最成熟的方案就是OAuth2.0授权协议.Spring Security也整合了OAuth2. ...

  8. 老司机实战Windows Server Docker:1 初体验之各种填坑

    前言 Windows Server 2016正式版发布已经有近半年时间了,除了看到携程的同学分享了一些Windows Server Docker的实践经验,网上比较深入的资料,不管是中文或英文的,都还 ...

  9. python 之前函数补充(__del__, item系列, __hash__, __eq__) , 以及模块初体验

    __str__ :  str(obj) ,  需求必须实现了 __str__, 要求这个方法的返回值必须是字符串  str  类型 __repr__ (意为原型输出):  是 __str__ 的备胎( ...

  10. Docker学习系列(二)Docker初体验

    一.系统要求 Docker的安装,需要在CentOS 7.0+版本,内核至少3.10,64-bit uname --r [randy@randysun ~]$ uname --r -.el7.x86_ ...

随机推荐

  1. ODOO13之12:Odoo 13开发之报表和服务端 QWeb

    报表是业务应用非常有价值的功能,内置的 QWeb 引擎是报表的默认引擎.使用 QWeb 模板设计的报表可生成 HTML 文件并被转化成 PDF.也就是说我们可以很便捷地利用已学习的 QWeb 知识,应 ...

  2. 基于AIGC的京东购物助手的技术方案设想

    灵感来源 随着AIGC的爆火,ChatGPT,GPT-4的发布,我作为一个算法工作者,深感AI发展的迅猛.最近,OpenAI的插件和联网功能陆续向用户公开,我也在第一时间试用了这些最新的功能.在Ope ...

  3. Vue——vuex使用、Router使用、localstorage、sessionstorage和cookie

    vuex使用 # vuex :状态管理器--->存数据(变量)的地方,所有组件都可以操作 在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理( ...

  4. JUC同步锁原理源码解析二--ReentrantReadWriteLock

    JUC同步锁原理源码解析二----ReentrantReadWriteLock 1.读写锁的来源 ​ 在开发场景下,对于写操作我们为了保证原子性所以需要上锁,但是对于读操作,由于其不改变数据,只是单纯 ...

  5. pta第三阶段题目集

    (1)前言 pta第三阶段作业中,主要包含了如下的主要内容: 1.全程贯穿了课程设计的程序,每一次都是上一次的迭代和修改,难度较大,中间涉及到先是类与类之间的多态和继承关系,后面的修改中,转变为了组合 ...

  6. GGTalk 开源即时通讯系统源码剖析之:数据库设计

    自从<开源即时通讯GGTalk 8.0发布,增加Linux客户端,支持在统信UOS.银河麒麟上运行!>一文在博客园发布后,有园友联系我QQ,说能不能整理个更系统更详细地介绍GGTalk源码 ...

  7. ArcMap镶嵌数据集的创建、数据导入与数据范围修改方法

      本文介绍基于ArcMap软件,建立镶嵌数据集(Mosaic Datasets).导入栅格图像数据,并调整像元数值范围的方法.   镶嵌数据集(Mosaic Datasets)是一种用以管理.显示. ...

  8. 查看C语言程序对应的汇编代码

    在终端输入 gcc -S main.c 命令的意思是 编译不汇编 mian.c 可以换成想要汇编的C语言程序 然后生成 main.s 使用文本编辑器查看即可

  9. 信奥赛题1001:Hello,World!

    这个题实在是太简单的了,无法比喻,直接付代码! //c++ #include<bits/stdc++.h> using namespace std; int main() { cout&l ...

  10. CN2 GIA

    搬瓦攻方案库存监控页面  https://stock.bwg.net/ https://bwh81.net/ https://bandwagonhost.com/ https://teddysun.c ...